iOS中的多线程网络

不论是什么开发,多线程网络始终是学习的重点。

多线程网络

NSthread

  1. 创建和启动线程

//创建一个新线程对象,实例方法,返回一个NSThread对象必须调用start方法启动线程 (实例方法)
-(id)initWithTarget:(id)target selector:(SEL)selector object:(id)arg:
//创建并启动新线程,不会返回NSThread对象。会直接创建并启动线程(类方法)
+(void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)arg:

target对象的selector方法的方法体代表了线程需要完成的任务,相当于把target对象的selector方法转换为线程执行体.

//创建多线程对象
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
//启动多线程
[thread start];

//创建并启动多线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
  1. 线程的状态
    线程的状态会在运行、就绪状态之间切换。
    启用线程使用了start方法后,该线程进入就绪状态,系统会为其创建方法调用栈和程序计数器。当系统调度线程后,线程才会进入运行状态。
    注意:若想要子线程的start方法后子线程立即开始执行,可以调用[NSThread sleepForTimeInterval:0.01];
    从而让子线程立即获得执行。

  2. 终止子线程
    1>线程执行体方法执行完成,线程正常结束。
    2>线程执行过程中出现了错误
    3>直接调用NSThread类的exit方法来中止当前正在执行的线程。

    1
    2
    3
    4
    if ([NSThread currentThread].isCancelled) {
    //中止当前正在执行的线程
    [NSThread exit];
    }
  1. 线程睡眠
    NSThread的两种类方法:
    //让当前正在执行的线程暂停到aDate代表的时间,并进入阻塞状态
    + (void)sleepUntilDate:(NSDate *)aDate:
    //让当前正在执行的线程暂停ti秒,并进入阻塞状态
    + (void)sleepForTimeInterval:(NSTimeInterval)ti:

例子:
使用线程下载网络图片:

1
2
3
4
5
6
7
8
//创建多线程对象
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImageFromURL:) object:nil];
//启动多线程
[thread start];
downloadImageFromURL:方法作为线程执行体,当线程启动调用该方法。
//在主线程中执行updateUI:方法 来更新iv控件显示的图片
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
}

iOS规定只能在UI线程中修改UI控件的属性

  1. 改变优先级
    NSThread中的实例方法和类方法:
    +threadPriority
    -threadPriority
    +setThreadPriority:(double)priority:
    -setThreadPriority:(double)priority:

线程同步与线程通信

  1. 在多线程中引入同步解决线程安全问题
    使用方法: @synchronized (obj) {

    //同步代码块
    

    }
    obj是同步监视器
    任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完毕的后,该线程会释放对同步监视器的锁定。
    可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,程序可以采用:

    • 不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步。
    • 如果可变类有两种运行环境:单线程和多线程环境,则应该为该可变类提供两种版本-单线程环境中使用线程不安全版本以保证性能,多线程环境中使用线程安全版本。
  2. 释放对同步监视器的锁定
    任何线程在进入同步代码块之前,必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?

    • 当前线程的同步代码块执行结束
    • 当前代码块在同步代码块中遇到了goto.return终止了该代码块、该方法继续执行时,当前线程会释放同步监视器。
    • 当线程在同步代码块中出现了错误,导致该代码块异常结束时,将会释放同步监视器。
  1. 同步锁(NSLock)
    Foundation提供NSLock,通过显式定义同步锁对象来实现同步。
    NSlock是控制多个线程对共享资源进行访问的工具,通常锁提供了对共享资源的独占访问。在实现线程安全的控制中,使用NSlock对象可以显示地加锁,释放锁。

  2. 使用NSCondition控制线程通信
    NSCondition实现了NSLocking协议,因此也可以调用lock,unlock来实现线程同步。也可以让那些已经锁定NSCondition对象却无法继续执行的线程释放NSCondition对象,NSCondition对象也可以唤醒其他处于等待状态的线程。
    方法:-wait,知道其他线程调用NSCondition的signal方法或broadcast方法来唤醒该线程。
    -(BOOL)waitUntilDate:(NSDate *)limiteout,

参考:http://blog.csdn.net/totogo2010/article/details/8010231

GCD

  1. Grand Central Dispatch “牛逼的中枢调度器”
  • 是苹果公司为多核的并行运算提出的解决方案
  • GCD会自动利用更多CPU内核
  • 会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
  • 程序员只需要告诉GCD该做什么,而不需要写代码
  1. 使用2个步骤:

    • 定制任务(执行什么操作)
    • 将任务添加到队列中(GCD会自动将队列中的任务取出,放到对应的线程中执行)(先进先出,后进后出)
  2. 创建队列
    队列:把任务放到队列里面,队列先进先出的原则,
    串行队列:顺序,一个一个执行(必须一个任务执行完了,才能从队列里面取出下一个任务)main queue用来更新UI
    并发队列:同时执行很多个任务(可以同时取出很多个任务,只要有线程去执行)global queue
    1>获取全局并发队列:
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    2>获取系统主线程关联的串行队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    3>创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("mmmm.queue", DISPATCH_QUEUE_SERIAL);
    4>创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("mmmm.queue", DISPATCH_QUEUE_CONCURRENT);

  3. 同步:dispatch_sync(dispatch_queue_t, dispatch_block_t block);
    异步:dispatch_async(dispatch_queue_t, dispatch_block_t block);
    同步和异步的区别:
    同步:在当前线程中执行,不具备开启新线程的能力
    异步:在另一条新线程中执行,具备开启新线程的能力

  4. 使用GCD下载图片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"%@",[NSThread currentThread]);
    //耗时操作
    //1.url,确定一个网络上的资源文件路径
    NSURL *url = [NSURL URLWithString:@"http://p18.qhimg.com/t0144d6a0802f22be4f.jpg"];
    //2.通过URL下载对应的网络资源,网络资源传输的都是二进制
    NSData *data = [NSData dataWithContentsOfFile:url];
    //3.把二进制转成图片
    UIImage *image = [UIImage imageWithData:data];
    //4.更新UI,在主线程->直接把主线程添加到主队列,就会在主队列执行
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    self.iconView.image = image;
    });
    });
  5. 多次执行的任务
    控制的代码块执行了5次。
    ` dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t time){

    NSLog(@"==执行【%lu次】==%@",time,[NSThread currentThread]);
    

    }`

  6. 只执行一次的任务
    `static dispatch_once_t oneceToken;
    dispatch_once(&oneceToken,^{

    NSLog(@"==执行代码块==");
    //线程暂停3秒
    [NSThread sleepForTimeInterval:3];
    

    });`
    dispatch_once_t控制函数只执行一次

后台运行

进入后台,系统会自动回调系统程序委托的applicationDidEnterBackground:方法。所有应用需要做:

- 释放所有可以释放的内存  
- 保存用户数据或状态信息,所有没写入磁盘的文件或信息,在进入后台之前,都应该写入磁盘,因为程序可能在后台被杀死。
  1. 进入后台时释放内存
    如果应用没有启用ARC机制,程序需要在进入后台是,将那些需要释放的资源的引用计数变为0,从而让系统回收这些资源,当应用转入前台,系统需要重新恢复这些资源。
    如果应用启用了ARC机制,程序只要在应用进入后台时,将应用那些需要释放的资源的变量赋值为nil即可。当应用转入前台是,系统需要重新恢复这些资源。
  2. 进入后台时保存状态
    1
    2
    3
    4
    5
    //使用NSUserDefault读取系统已经保存的积分
    NSNumber* scoreNumber;
    if ((scoreNumber = [[NSUserDefaults standardUserDefaults]setInteger:score forKey:@"score"])) {
    score = scoreNumber.integerValue;
    }
  1. 请求更多后台时间
    为了请求更多后台时间,应该使用如下步骤:
    • 调用UIApplication对象的beginBackgroundTaskWithExpirationHandler:方法请求获取更多的后台执行时间。
    • 调用dispatch_async()方法将指定代码块提交给后台执行
    • 后台任务执行完成是,调用UIApplication对象的endBackgroundTask:方法结束后台任务。

使用NSOperation与NSOperationQueue实现多线程

使用步骤:

- 创建NSOperationQueue队列,并为该队列设置相关属性。  
- 创建NSOperation子类的对象,并将该对象提交给NSOperationQueue队列,该队列将会按顺序启动每个NSOperation。   

MaxConcurrentOperationCount 设置最大并发线程数。

  1. 使用NSblockOperation下载图片
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //创建NSBlock
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    //从网络获取数据
    NSData *data = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:url]];
    //将网络数据初始化为UIImage对象
    UIImage *image = [[UIImage alloc]initWithData:data];
    if(image!= nil){
    //在主线程中updateUI:方法
    [self performSelectorOnMainThread:@selector(updateUI:)withObject:image waitUntilDone:YES];
    }
    else
    NSLog(@"下载图片出现错误");
    }];
  1. 定义NSOperation子类
    创建NSOperation的子类需要重写一个方法:-(void)main,该方法将作为NSOpQueue完成任务。

面试题

  1. 什么是多线程
    进程:是指在系统中正在运行的一个应用程序。1个进程要想执行任务,必须得有线程。(一个进程至少要有一个线程)特征:独立性,动态性,并发性。
    线程:是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程的。
    多线程:在一个进程中可以开启多个线程,每条线程可以并发(同时)执行不同的任务。
    多线程编程的优点:
    • 进程间不能共享内存,但线程之间共享内存非常容易
    • 系统创建进程需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。
    • iOS提供了多种多线程实现方式,从而简化了iOS的多线程编程。
      参考:http://1108038.blog.51cto.com/1098038/420330
      进程间通信—>管道(pipe)、信号(signal)、消息队列、共享内存、信号量、套接字;
      线程间通信—>信号量、消息、事件event
  2. iOS中如何实现多线程
    • pthread(一套通用的多线程API,使用难度大 C语言(底层))程序员管理,几乎不用
      #include <pthread.h>
    • NSThread(面向对象,简单易用,可直接操作多线程对象,OC),偶尔使用
    • GCD(旨在替代NSThread等线程技术,充分利用设备的多核,C语言)自动管理,经常使用
    • NSOperation(基于GCD,比GCD多了一些简单实用的功能,OC)自动管理,经常使用.
  3. 线程间通讯的方法
    控制器在子线程发送请求给服务器
    服务器在主线程刷新UI界面到控制器

    还可以使用GCD,主线程和子线程的通信。
    dispath_async(dispatch_get_main_queue(),^{ });
  4. 网络图片处理问题中怎么解决一个相同的网络地址重复请求的问题?

    利用字典(图片地址为key,下载操作为value)

  5. 多线程安全的几种解决办法及多线程安全怎么控制?
    线程安全的概念: 就是在多个线程同时执行的时候,能够保证资源信息的准确性.

    • 苹果约定,所有程序的更新UI都在主线程进行,也就不会出现多个线程同时改变一个资源。在主线程更新UI,有什么好处?只在主线程更新UI,就不会出现多个线程同时改变同一个UI控件;主线程的优先级最高。也就意味UI的更新优先级高。 会让用户感觉很流畅 .
    • 如果要防止资源抢夺,得用synchronized进行加锁保护.
      线程同步:多条线程按顺序的执行任务(互斥锁)互斥锁使用格式
      @synchronized(锁对象){ //需要锁定的代码 }
    • 如果异步操作要保证线程安全等问题, 尽量使用GCD(有些函数默认就是安全的)
  6. GCD内部怎么实现的
    1> iOS和OS X的核心是XNU内核,GCD是基于XNU内核实现的
    2> GCD的API全部在libdispatch库中
    3> GCD的底层实现主要有Dispatch Queue和Dispatch Source

    • Dispatch Queue :管理block(操作)
    • Dispatch Source :处理事件(比如线程间的通讯)
      补充:GCD:Grand Central Dispatch “牛逼的中枢调度器”,自动利用更多CPU内核,自动管理线程的生命周期(创建线程,调度任务,销毁线程).使用2个步骤:
    • 定制任务(执行什么操作)用block来封装任务
    • 将任务添加到队列中(自动将队列中的任务取出,放到对应的线程中执行)(先进先出,后进后出)
  7. GCD和NSoperation区别
    1>GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装
    2>GCD只支持FIFO的队列,NSOperationQueue可以很方便地调整执行顺序、设置最大并发数量
    3>NSOperationQueue可以在轻松在Operation间设置依赖关系,而GCD需要写很多的代码才能实现
    4>NSOperationQueue支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
    5>GCD的执行速度比NSOperationQueue快
    任务之间不太互相依赖:GCD
    任务之间有依赖(或者要监听任务的执行情况):NSOperationQueue

  8. Socket的实现原理以及Socket是如何实现通信的?
    Socket:称之为套接字,是一种用于网络传输的“工具”。
    socket的实现原理:是基于TCP/UDP的(TCP:传输控制协议,是一种面向连接的,安全的,基于IP传输层的协议,三次握手。例如:XMPP等网络聊天)(UDP:传输控制协议,不是面向连接的,不安全的,基于IP传输层的协议,特点:快,只管发,不管收到没有。例如:游戏,QQ视频,红蜘蛛)
  9. http协议的实现
    HTTP:是一种超文本协议,定义了网络传输的格式(短连接)
    如果利用HTTP做聊天,每次都要重新创建连接,因为HTTP是短连接,一次回话后就断开了,如果利用HTTP做聊天,如果聊天特别频繁,会不断的创建连接,消耗资源,性能不好,服务端不会主动给客户端发送请求。

  10. runloop定时源和输入源?
    1>你创建的程序不需要显示的创建run loop;每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象, 主线程会自行创建并运行run loop
    2>Run loop处理的输入事件有两种不同的来源:输入源(input source)(异步)(处理其他线程回到主线程的消息)和定时源(timer source)(同步)(定时检查界面上有没有点击事件,检查主线程的事件)
    3>输入源传递异步消息,通常来自于其他线程或者程序。定时源则传递同步消息,在特定时间或者一定的时间间隔发生

  11. NSRunLoop的实现机制,及在多线程中如何使用
    NSRunLoop是IOS消息机制的处理模式.

    1.NSRunLoop的主要作用:控制NSRunLoop里面线程的执行和休眠,在有事情做的时候使当前NSRunLoop控制的线程工作,没有事情做让当前NSRunLoop的控制的线程休眠。
    2.NSRunLoop 就是一直在循环检测,从线程start到线程end,检测inputsource(如点击,双击等操作)异步事件,检测timesource同步事件,检测到输入源会执行处理函数,首先会产生通知,corefunction向线程添加runloop observers来监听事件,意在监听事件发生时来做处理。
    3.runloopmode是一个集合,包括监听:事件源,定时器,以及需通知的runloop observers

  1. 只有在为你的程序创建次线程的时候,才需要运行run loop。对于程序的主线程而言,run loop是关键部分。Cocoa提供了运行主线程run loop的代码同时也会自动运行run loop。IOS程序UIApplication中的run方法在程序正常启动的时候就会启动run loop。如果你使用xcode提供的模板创建的程序,那你永远不需要自己去启动run loop
  2. 在多线程中,你需要判断是否需要run loop。如果需要run loop,那么你要负责配置run loop并启动。你不需要在任何情况下都去启动run loop。比如,你使用线程去处理一个预先定义好的耗时极长的任务时,你就可以毋需启动run loop。Run loop只在你要和线程有交互时才需要
文章目录
  1. 1. 多线程网络
    1. 1.1. NSthread
      1. 1.1.1. 线程同步与线程通信
  2. 2. GCD
  3. 3. 后台运行
  4. 4. 使用NSOperation与NSOperationQueue实现多线程
  5. 5. 面试题
本站总访问量 本站访客数人次 ,