ios开发进阶之多线程03 RunLoop 网络编程

一 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,通常用不到
  1. 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
  2. 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
  3. 如果需要切换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的状态改变
    CFRunLoopObserverRef监听时间点

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

    }];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值