您的位置:首页 > 理论基础 > 计算机网络

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];
}

}];
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: