一 RunLoop简介
什么是RunLoop
- 运行循环
- 一个线程对应一个RunLoop,主线程的RunLoop默认已经启动,子线程的RunLoop得手动启动(调用run方法)
- RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop
RunLoop作用
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
……
模拟RunLoop内部实现
- 其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)
void message(int num)
{
printf("执行第%i个任务", num);
}
int main(int argc, const char * argv[]) {
do {
printf("有事吗? 没事我睡了");
int number;
scanf("%i", &number);
message(number);
} while (1);
return 0;
}
二 RunLoop对象
获得RunLoop对象
- RunLoop对象
- NSRunLoop
- CFRunLoopRef
- RunLoop对象
Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
- Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
三 RunLoop相关类
- RunLoop结构
- CFRunLoopRef对应RunLoop对象
- CFRunLoopModeRef代表RunLoop的运行模式, 系统默认注册了5个Mode
- NSDefaultRunLoopMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- NSRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
- CFRunLoopModeRef代表RunLoop的运行模式, 系统默认注册了5个Mode
- 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
- 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
- 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
- CFRunLoopTimerRef是基于时间的触发器
- CFRunLoopTimerRef基本上说的就是NSTimer,它受RunLoop的Mode影响
// 创建一个NSTimer之后, 必须将NSTimer添加到RunLoop中, 才能执行
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];
// 将NSTimer添加到主线程NSRunLoop的默认模式下, 只有主线程NSRunLoop当前是默认模式才会执行timer
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 这是一个占位用的Mode,不是一种真正的Mode
// 其实Common是一个标识, 它是将NSDefaultRunLoopMode和UITrackingRunLoopMode标记为了Common
// 所以, 只要将timer添加到Common占位模式下,timer就可以在NSDefaultRunLoopMode和UITrackingRunLoopMode模式下都能运行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- GCD的定时器不受RunLoop的Mode影响
- (void)viewDidLoad {
[super viewDidLoad];
// 1.创建tiemr
// queue: 代表定时器将来回调的方法在哪个线程中执行
// dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
self.timer = timer;
// 2.设置timer
/*
第一个参数: 需要设置哪个timer
第二个参数: 指定定时器开始的时间
第三个参数: 指定间隔时间
第四个参数: 定时器的精准度, 如果传0代表要求非常精准
*/
// 定时器开始时间
// dispatch_time_t startTime = DISPATCH_TIME_NOW;
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
// 定时器间隔的时间
uint64_t timerInterval = 2.0 * NSEC_PER_SEC;
dispatch_source_set_timer(timer, startTime, timerInterval, 0 * NSEC_PER_SEC);
// 3.设置timer的回调
dispatch_source_set_event_handler(timer, ^{
NSLog(@"我被调用了 %@", [NSThread currentThread]);
});
// 4.开始执行定时器
dispatch_resume(timer);
}
CFRunLoopSourceRef是事件源(输入源)
- 按照函数调用栈,Source的分类
- Source0:非基于Port的, 用于用户主动触发事件
- Source1:基于Port的,通过内核和其他线程相互发送消息
- 按照函数调用栈,Source的分类
CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变
- (void)viewDidLoad {
[super viewDidLoad];
// 0.创建一个监听对象
/*
第一个参数: 告诉系统如何给Observer对象分配存储空间
第二个参数: 需要监听的类型
第三个参数: 是否需要重复监听
第四个参数: 优先级
第五个参数: 监听到对应的状态之后的回调
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// NSLog(@"%lu", activity);
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"进入RunLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"刚刚从睡眠中醒来");
break;
case kCFRunLoopExit:
NSLog(@"退出RunLoop");
break;
default:
break;
}
});
// 1.给主线程的RunLoop添加监听
/*
第一个参数:需要监听的RunLoop对象
第二个参数:给指定的RunLoop对象添加的监听对象
第三个参数:在那种模式下监听
*/
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 如果通过scheduled方法创建NSTimer, 系统会默认添加到当前线程的默认模式下
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];
}
四 RunLoop应用场景
- RunLoopRunLoop处理逻辑
- RunLoop应用
- NSTimer
- ImageView显示
- PerformSelector
- 常驻线程
- 自动释放池
- NSTimer
- 只能在指定的model下运行
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
- ImageView显示
- 只能在指定的model下设置图片
- PerformSelector
- 只能在指定的model下调用
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageNamed:@"lnj"] waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
- 常驻线程
- 必须调用run才会执行死循环
- NSRunLoop的model中必须有source/timer,死循环才不会退出
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runloop run]
- 自动释放池
activities = 0x1 = 1
1: 即将进入RunLoop : 创建一个自动释放池
activities = 0xa0 = 160 = 128 + 32
32:即将休眠 : 释放上一次的自动释放池, 创建一个新的自动释放池
128:即将退出RunLoop : 释放自动释放池
五 网络基本概念
基本概念
- 客户端(Client):移动应用(iOS、android等应用)
- 服务器(Server):为客户提供服务、提供数据、提供资源的机器
- 请求(Request):客户端向服务器索取数据的一种行为
- 响应(Response):服务器对客户端请求做出的反应,一般指返回数据给客户
服务器按照软件开发阶段来分,有2种:
- 远程服务器:外网服务器、正式服务器,应用上线后使用的服务器
- 本地服务器:内网服务器、测试服务器,应用处于开发、测试阶段使用的服务器
怎么找到服务器?—-URL
- URL的基本格式 = 协议://主机地址/路径
URL中常见的协议
- HTTP:Hypertext Transfer Protocol,超文本传输协议,访问远程的网络资源,格式http://
- file:访问本地计算机上的资源,格式file://
- mailto:访问电子邮件地址,格式是mailto:
- FTP:访问共享主机的文件资源,格式ftp://
六 HTTP基本通信过程 GET-POST请求
GET和POST的主要区别表现在数据传递上
GET:在请求URL后面以?的形式跟上发给服务器的参数,多个参数之间用&隔开,比如:
http://ww.test.com/login?username=123&pwd=234&type=JSON
由于浏览器和服务器对URL长度有限制,因此在URL后面附带的参数是有限制的,通常不能超过1KBPOST:发给服务器的参数全部放在请求体中
理论上,POST传递的数据量没有限制(具体还得看服务器的处理能力)
- 选择GET和POST的建议
- 如果要传递大量数据,比如文件上传,只能用POST请求
- GET的安全性比POST要差些,如果包含机密\敏感信息,建议用POST
- 如果仅仅是索取数据(数据查询),建议使用GET
- 如果是增加、修改、删除数据,建议使用POST
七 iOS中发送HTTP请求的方案
苹果原生(自带)
- NSURLConnection:用法简单,最古老最经典最直接的一种方案
- NSURLSession:功能比NSURLConnection更加强大,苹果目前比较推荐使用这种技术
- CFNetwork:NSURL*的底层,纯C语言
第三方框架
- ASIHttpRequest:外号“HTTP终结者”,功能极其强大,可惜早已停止更新
- AFNetworking:简单易用,提供了基本够用的常用功能,维护和使用者多
- MKNetworkKit:简单易用,产自三哥的故乡印度,维护和使用者少
1.HTTP通信过程 - 请求
HTTP协议规定:1个完整的由客户端发给服务器的HTTP请求中包含以下内容
请求头:包含了对客户端的环境描述、客户端请求信息等
- GET /minion.png HTTP/1.1 // 包含了请求方法、请求资源路径、HTTP协议版本
- Host: 120.25.226.186:32812 // 客户端想访问的服务器主机地址
- User-Agent: Mozilla/5.0 // 客户端的类型,客户端的软件环境
- Accept: text/html, / // 客户端所能接收的数据类型
- Accept-Language: zh-cn // 客户端的语言环境
- Accept-Encoding: gzip // 客户端支持的数据压缩格式
请求体:客户端发给服务器的具体数据,比如文件数据(POST请求才会有)
2.HTTP通信过程 - 响应
HTTP协议规定:1个完整的HTTP响应中包含以下内容
响应头:包含了对服务器的描述、对返回数据的描述
- HTTP/1.1 200 OK // 包含了HTTP协议版本、状态码、状态英文名称
- Server: Apache-Coyote/1.1 // 服务器的类型
- Content-Type: image/jpeg // 返回数据的类型
- Content-Length: 56811 // 返回数据的长度
- Date: Mon, 23 Jun 2014 12:54:52 GMT // 响应的时间
响应体:服务器返回给客户端的具体数据,比如文件数据
- 常见响应状态码
八 NSURLConnection
- 作用
- 负责发送请求,建立客户端和服务器的连接
- 发送数据给服务器,并收集来自服务器的响应数据
- NSURLConnection的使用步骤
- 创建一个NSURL对象,设置请求路径
- 传入NSURL创建一个NSURLRequest对象,设置请求头和请求体
- 使用NSURLConnection发送请求
NSURLRequest
- 用于保存请求地址/请求头/请求体
- 默认情况下NSURLRequest会自动给我们设置好请求头
- request默认情况下就是GET请求
同步请求
- 如果是调用NSURLConnection的同步方法, 会阻塞当前线程
// 1.创建一个URL
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
// 2.根据URL创建NSURLRequest对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3.利用NSURLConnection对象发送请求
/*
第一个参数: 需要请求的对象
第二个参数: 服务返回给我们的响应头信息
第三个参数: 错误信息
返回值: 服务器返回给我们的响应体
*/
NSHTTPURLResponse *response = nil; // 真实类型
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
NSLog(@"response = %@", response.allHeaderFields);
- 异步请求
// 1.创建一个URL
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
// 2.根据URL创建NSURLRequest对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3.利用NSURLConnection对象发送请求
/*
第一个参数: 需要请求的对象
第二个参数: 回调block的队列, 决定了block在哪个线程中执行
第三个参数: 回调block
*/
// 注意点: 如果是调用NSURLConnection的同步方法, 会阻塞当前线程
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
- POST方法
// 1.创建一个URL
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
// 2.根据URL创建NSURLRequest对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 2.1设置请求方式
// 注意: POST一定要大写
request.HTTPMethod = @"POST";
// 2.2设置请求体
// 注意: 如果是给POST请求传递参数: 那么不需要写?号
request.HTTPBody = [@"username=520it&pwd=520it&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
// 3.利用NSURLConnection对象发送请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
- 请求服务器响应
// 1.创建URL
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];
// 2.根据URL创建NSURLRequest
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3.利用NSURLConnection发送请求
/*
// 只要调用alloc/initWithRequest, 系统会自动发送请求
[[NSURLConnection alloc] initWithRequest:request delegate:self];
*/
/*
// startImmediately: 如果传递YES, 系统会自动发送请求; 如果传递NO, 系统不会自动发送请求
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[conn start];
*/
[NSURLConnection connectionWithRequest:request delegate:self];
- 代理方法
#pragma mark - NSURLConnectionDataDelegate
/*
只要接收到服务器的响应就会调用
response:响应头
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(@"%s", __func__);
}
/*
接收到服务器返回的数据时调用(该方法可能调用一次或多次)
data: 服务器返回的数据(当前这一次传递给我们的, 并不是总数)
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@"%s", __func__);
}
/*
接收结束时调用
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"%s", __func__);
}
/*
请求错误时调用(请求超时)
*/
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"%s", __func__);
}
八 URL中文问题
// 1.创建URL
NSString *urlStr = @"http://120.25.226.186:32812/login2?username=xmg&pwd=520it&type=JSON";
NSLog(@"转换前:%@", urlStr);
// 2.对URL进行转码
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
九 综合案例–登陆界面
- (IBAction)loginBtnClick:(UIButton *)sender {
// 1.拿到账号密码
NSString *username = self.usernameField.text;
NSString *pwd = self.pwdField.text;
// 2.判断用户是否数据了用户名和密码
if (username.length <= 0) {
[SVProgressHUD showErrorWithStatus:self.usernameField.placeholder maskType:SVProgressHUDMaskTypeBlack];
return;
}
if (pwd.length <= 0) {
[SVProgressHUD showErrorWithStatus:self.pwdField.placeholder maskType:SVProgressHUDMaskTypeBlack];
return;
}
// 提示用户正在登录
[SVProgressHUD showWithStatus:@"正在拼命登录ing..." maskType:SVProgressHUDMaskTypeBlack];
// 2.生成URL
NSString *urlStr = @"http://120.25.226.186:32812/login";
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlStr];
// 3.根据URL生成NSRULRequest
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 3.1设置请求头的请求方式为POST
request.HTTPMethod = @"POST";
// 3.2设置请求体
NSString *content = [NSString stringWithFormat:@"username=%@&pwd=%@&type=JSON", username, pwd];
request.HTTPBody = [content dataUsingEncoding:NSUTF8StringEncoding];
// 4.利用NSURLConnection发送请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
[SVProgressHUD dismiss];
NSString *responseObj = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",responseObj);
// 截取字符串
// 注意: 在企业开发中, 千万不要给用户详细的提示信息
NSUInteger start = [responseObj rangeOfString:@":\""].location + 2;
NSUInteger length = [responseObj rangeOfString:@"\"" options:NSBackwardsSearch].location - start;
NSRange range = NSMakeRange(start, length);
NSString *res = [responseObj substringWithRange:range];
if ([responseObj containsString:@"error"]) {
[SVProgressHUD showErrorWithStatus:res maskType:SVProgressHUDMaskTypeBlack];
}else
{
[SVProgressHUD showSuccessWithStatus:res maskType:SVProgressHUDMaskTypeBlack];
}
}];
}