iOS

iOS多线程与网络

iOS多线程与网络相关

Posted by 袁平 on February 24, 2019

前言

iOS多线程编程各种方式总结


正文


一. 概述

iOS中多线程的实现方式有如下四种:

本文主要总结常用的两种方式


二. GCD

2.1 GCD概述

GCD即Grand Central Dispatch,是苹果公司为多核的并行运算提出的解决方案,能自动合理地利用多核CPU(比如双核、四核),同时还能自动管理线程的生命周期(创建线程、调度任务、销毁线程),不需要手动管理;由C语言实现

2.2 GCD使用

2.2.1 主要方法

  1. dispatch_sync(dispatch_queue_t queue, dispatch_block_t block) :同步
  2. dispatch_async(dispatch_queue_t queue, dispatch_block_t block) :异步
  3. dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block) :等待前面的任务执行完成之后才执行,且它后面的任务等它执行完成之后才会执行;这里的 queue 不能是全局的并发队列(可用于解决读写锁的问题;如数据库操作中,读操作可以并行提高效率,写操作需要等待前面的读操作完成之后再执行,写操作执行完之后,之后的读操作又可以实现并行,如下)
	dispatch_async(queue, ^{
            // reading
        });
        dispatch_async(queue, ^{
            // reading
        });
        dispatch_barrier_sync(queue, ^{
            // writing
        });
        dispatch_async(queue, ^{
            // reading
        });
        dispatch_async(queue, ^{
            // reading
        });

2.2.2 队列

GCD中有两个重要的概念:任务和队列

  1. 任务有两种执行方式:同步执行和异步执行,他们之间的区别是否会创建新的线程
  2. 队列:用于存放任务。有两种队列, 串行队列和并行队列;区别如下:

可以通过如下方式获取或者创建队列:

  1. 使用 dispatch_queue_create 创建(串行或并行)
  2. 使用 dispatch_get_global_queue 获取全局并发队列(所有应用程序都可用,且区分优先级)
  3. 使用 dispatch_get_main_queue 获得主队列
// 自己创建队列:第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列;
// 第二个参数用来表示创建的队列是串行的还是并行的,传入 DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列

  //串行队列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
  //并行队列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
  // 全局并行队列:
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

2.2.3 队列组(Dispatch Group)

可用于实现当并行执行block时,监听所有block执行结束

	dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, queue, ^{
            NSLog(@"Block 1");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"Block 2");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"Block 3");
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"Done");
        });

应用举例:分别执行两个耗时的操作,等两个异步操作都执行完之后,回到主线程执行操作;如果想要高效实现上述需求,可用队列组;如下

dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程...
});

2.2.4 其他

  • 延迟执行:通过dispatch_walltime生成绝对时间
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC); // ull表示:unsigned long long 
        dispatch_after(time, dispatch_get_main_queue(), ^{
            
});
  • dipatch_apply:相当于dipatch_sync和dispatch group的关联API,该函数将指定的block追加到指定的queue中,并等待全部处理执行结束;其也会阻塞直到执行完毕,可用其替代for循环
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) { // index表示当前索引,自动增加
      NSLog(@"%zu", index);
    });
    NSLog(@"Done"); // 最后执行
    
  • dispatch_suspend / dispatch_resume:用于挂起和恢复queue

  • Dispatch Semaphore:计数为0时等待,计数为1或大于1时,减1而不是等待

  • dispatch_once:保证在应用程序中只执行一次指定处理的API

  • Dispatch IO:当读取大文件时,将文件分成合适的大小并使用Global Queue读取的话,会快很多,如果想尝试提高文件读取速度,可以使用Dispatch IO

2.2.5 备注

在iOS6.0之前,在GCD中每当使用带creat单词的函数创建对象之后,都应该对其进行一次release操作。在iOS6.0之后,GCD被纳入到了ARC的内存管理机制中,在使用GCD的时候我们就像对待普通OC对象一样对待GCD,因此不再需要我们调用release方法


三. NSOperation

  1. 本身是个抽象类,需要使用其子类:NSInvocationOperation,NSBlockOperation,或自定义子类
  2. 默认情况下,调用 NSInvocationOperation 的 start 方法并不会开一条新线程去执行,而是在当前线程执行;需要把 NSOperation 放到一个 NSOperationQueue 中才会执行异步操作
  3. NSBlockOperation:只要 NSBlockOperation 封装的操作数 > 1,就会异步执行操作
  4. 支持取消,暂停和恢复,还可以设置依赖来保证执行顺序,如 [operationB addDependency:operationA]; // 操作B依赖于操作A;还可以在不同 queue 的 NSOperation 之间创建依赖关系
  5. 支持监听一个操作的执行完毕:通过 completionBlocksetCompletionBlock 实现

四. 网络请求

网络请求常用类:

  1. NSURL:请求地址
  2. NSURLRequest:一个NSURLRequest对象就代表一个请求,它包含的信息有:一个 NSURL 对象,请求方法,请求头,请求体,请求超时等
  3. NSMutableURLRequest:NSURLRequest的子类
  4. NSURLConnection:负责发送请求;支持发送同步请求(sendSynchronousRequest)和异步请求(sendAsynchronousRequest);还可以设置代理监听网络请求过程(开始响应,接收数据,结束响应,请求出错)

第三方框架:

  1. ASIHttpRequest
  2. AFNetworking
  3. MKNetworkKit

iOS中发送 HTTP 请求的方案:

  • 苹果原生
    1. NSURLConnection:用法简单,坑较多
    2. NSURLSession:功能比 NSURLConnection 强大,苹果推荐使用(但是NSURLSessionTask是一个抽象类,本身不能使用,只能使用它的子类:NSURLSessionDataTask\NSURLSessionUploadTask\NSURLSessionDownloadTask)
    3. CFNetwork:NSURL*的底层实现,纯C语言

两种为NSURLConnection设置代理方式的区别:

    //第一种设置方式:
    //通过该方法设置代理,会自动的发送请求
    // [[NSURLConnection alloc]initWithRequest:request delegate:self];

    //第二种设置方式:
    //设置代理,startImmediately为NO的时候,该方法不会自动发送请求
    NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];
    //手动通过代码的方式来发送请求
    //注意该方法内部会自动的把connect添加到当前线程的RunLoop中在默认模式下执行
    [connect start];

五. 数据解析

Json解析(转OC数据类型):

  1. 第三方框架:JSONKit、SBJson、TouchJSON(性能从左到右,越差)
  2. 苹果原生:NSJSONSerialization(性能最好)

XML解析:

  • 解析方式有两种:
    1. DOM:一次性将整个XML文档加载进内存,比较适合解析小文件
    2. SAX:从根元素开始,按顺序一个元素一个元素往下解析,比较适合解析大文件
  • 解析API:
    1. 官方原生:NSXMLParser,SAX方式解析
    2. libxml2:纯C语言,默认包含在iOS SDK中,同时支持DOM和SAX方式解析
    3. GDataXML:DOM方式解析,由Google开发,基于libxml2

六. RunLoop

RunLoop是iOS中比较重要的一点,较短篇幅无法讲完,后面有机会再另起新篇总结


七. 参考博客

  1. https://www.jianshu.com/p/0b0d9b1f1f19