ios开发进阶之多线程03 RunLoop 网络编程
2015-08-28 22:55
621 查看
一 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
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,通常用不到
一个 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的,通过内核和其他线程相互发送消息
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后面附带的参数是有限制的,通常不能超过1KB
POST:发给服务器的参数全部放在请求体中
理论上,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]; } }]; }
相关文章推荐
- 虚拟机网络模式
- hdoj 4292 Food 【拆点 网络流】
- Android键盘的显示控制、网络检查、网络监测
- HTTP文件传输
- Android 网络请求超时处理方案
- linux下的apache在httpd.conf配置目录别名和include引入自配置文件配置的方法
- Android中的几种网络请求方式详解
- 《TCP/IP详解卷1:协议》——第2章:链路层(转载)
- Android(java)学习笔记201:网络图片浏览器的实现(ANR)
- 衡量企业网络广告效果的九个指标
- 8成企业不满网络广告评估方法
- 初识Java,基本名字的了解(摘自网络)
- 网络广告效果提高九要诀
- TCP 的那些事儿(下)
- TCP 的那些事儿(上)
- TCP通信的三次握手和四次撒手的详细流程
- HttpClient和HttpURLConnection的区别
- Linux下基于opencv的神经网络字符识别
- linux apache httpd安装(安装全部modules)
- android httpClient 支持HTTPS的2种处理方式