iOS开发高级进阶(13-16)-多线程

这周就学了多线程,现在还在云里雾里....

线程的概念

  • 执行调度单位
  • 并发资源访问控制
    • 原子操作 Atomic(任务不可分隔,保证数据一致性)
    • 用锁同步,锁的类型
      • Semaphore 信号量:系统多个资源
      • Mutex 互斥量:单个资源
      • Critical Section 临界区:反复访问的情况
      • Read-Write Lock 读写锁 只有1个写,其它是读,不受锁的影响
      • Condition Variable 条件变量 符合条件后激活

iOS中多任务API

iOS有三种多线程编程的技术:

  1. NSThread
  2. Cocoa NSOperation
  3. GCD 全称:Grand Central Dispatch
    抽象度层次是从低到高的,抽象度越高的使用越简单

三种方式的优缺点介绍:

NSThread:

优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销

NSThread实现的技术有下面三种:

  • Technology
  • Description
  • Cocoa threads
    一般使用cocoa thread 技术。

Cocoa operation

优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。

Cocoa operation 相关的类是 NSOperation ,NSOperationQueue。

NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。

创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。

GCD

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。

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


Main Thread (主线程)

主线程管理界面,也叫 UI 线程
长时间操作线程的应该另外开一个线程进行

NSThread

//新开线程执行 
//import
@import Foundation;

//方法一:直接创建线程并且开始运行线程
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];

//方法二:
NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                    selector:@selector(doSomething:)
                                    object:nil];
[myThread start];

参数说明:

  • selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。

  • target :selector消息发送的对象

  • argument:传输给target的唯一参数,也可以是nil

    //通知主线程更新界面
      NSObject *Obj = [[NSObject alloc]init];
      [Obj performSelectorOnMainThread:@selector(doSomething:) withObject:self waitUntilDone:NO];
    
    //查询是否是主线程
      NSLog(@"%@",[NSThread isMainThread]?@"YES":@"NO");
    

Demo

  1. 为了访问 HTTP 协议,在Info.plist中添加

     NSAppTransportSecurity
     
         NSAllowsArbitraryLoads
         
     
    
  2. 在 View 中添加imageView

  3. .m文件
    #import "ViewController.h"
    #define kURL @"http://upload.jianshu.io/daily_images/images/myb4DiWJtEcFVgHwgdhh.jpg"
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    @end

    @implementation ViewController
    
    -(void)loadImage:(NSString *) url{
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
        UIImage *image = [UIImage imageWithData:data scale:2.0];
        [self performSelectorOnMainThread:@selector(dispalyImage:) withObject:image waitUntilDone:NO];
    }
    
    -(void)dispalyImage:(UIImage*) image{
        self.imageView.image = image;
    }
    
    - (void)viewDidLoad{
        [super viewDidLoad];
    
        NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:kURL];
       [thread start];
    }
    
    - (void)didReceiveMemoryWarning{
        [super didReceiveMemoryWarning];
    }
    
    @end
    

创建一个线程的开销

  1. 内核中占据1KB 内存保存线程资料
  2. stack space堆中占据 512KB 内存( osx 主线程8MB\ios 主线程1MB)
  3. 需要耗费90毫秒,费电
    因此线程越少越好

线程属性设定

thread.name = @"image";
thread.stackSize = 1024 * 4 * 5 ;//20K
thread.threadPriority=1.0;//0.0-1.0 ,1.0为高优先级
//?thread.threadDictionary;//没弄清楚怎么用

线程的控制

   [thread start];  
   [thread cancel];  
   NSLog(@"1--%@",[NSThread currentThread]);//当前线程
   [NSThread sleepForTimeInterval:2];
   NSDate * sleepTime =[[NSDate alloc]initWithTimeIntervalSinceNow:60];
   [NSThread sleepUntilDate:sleepTime];
   [NSThread exit];//直接取消,慎用

Runloop (循环执行)

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里

线程刚创建时并没有 RunLoop

RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。

只能在一个线程的内部获取其 RunLoop(主线程除外)

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer

Timer :CFRunLoopTimerRef/NSTimer

Observer:CFRunLoopObserverRef:观察 runloop 状态变化

每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。

如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

两种 source :

  • source0:

    • 只包含一个回调
    • 不能主动触发事件
    • 需要手工标记和唤醒
      CFRunLoopSourceSignal(<#CFRunLoopSourceRef source#>)
      CFRunLoopWakeUp(<#CFRunLoopRef rl#>)
  • source1:

    • 一个 mach_port 和一个回调
    • mach消息到达时,主动唤醒 Runloop 线程

实际上 RunLoop 内部是一个 do-while 循环。

当调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。

Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode

这两个 Mode 都已经被标记为"Common"属性

DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态

当创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。

有时需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 "commonModeItems" 中。

"commonModeItems" 被 RunLoop 自动更新到所有具有"Common"属性的 Mode 里去。

NSRunloop 生命周期

待补充


NSOperation

NSThread中,依赖\同步\调度需要写的东西比较多
参考:http://blog.csdn.net/totogo2010/article/details/8013316

  • NSOperation:封装任务部分
    • 用 selector(选择器)编写任务: NSInvocationOperation
    • 用 block 编写任务:NSBlockOperation
    • 任务可以用依赖关系串起来
  • NSOperationQueue: 封装线程管理部分

demo

  1. 为了访问 HTTP 协议,在Info.plist中添加

     NSAppTransportSecurity
     
         NSAllowsArbitraryLoads
         
     
    
  2. 在 View 中添加imageView

  3. .m文件

@interface NSOperationTemplateVC ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation NSOperationTemplateVC


-(void)loadImage:(NSString *) url{
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
    self.image = [UIImage imageWithData:data scale:2.0];

}

-(void)dispalyImage{
    self.imageView.image = self.image;
}


- (void)viewDidLoad
{
    [super viewDidLoad];


    NSInvocationOperation *opLoadImage = [[NSInvocationOperation alloc]initWithTarget:self
                                                                       selector:@selector(loadImage:)
                                                                         object:kURL];
    NSInvocationOperation *opDispalyImage = [[NSInvocationOperation alloc]initWithTarget:self
                                                                       selector:@selector(dispalyImage)
                                                                         object:nil];
    [opDispalyImage addDependency:opLoadImage];

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    [queue addOperation:opLoadImage];
    [[NSOperationQueue mainQueue] addOperation:opDispalyImage];

    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
    }

    @end

NSOperation 的直接使用

是个抽象基类,一般不直接创造实例

NSOperation *op = [[NSOperation alloc]init];
[op start];
[op waitUntilFinished];
[op cancel];

[op main];
//默认不做
//在 autoreleasepool 里运行
//定义子类时覆盖
//可以通过继承重写NSOperation 的main 使用

NSInvocationOperation

创建

//方法一
     NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self
                             selector:@selector(loadImage:)
                             object:kURL];
//方法二
    NSInvocation * invocation =[[NSInvocation alloc]init];
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithInvocation:invocation];

NSBlockOperation

//创建
[NSBlockOperation blockOperationWithBlock:<#^(void)block#>);

//追加
 NSBlockOperation *blockOperation = [[NSBlockOperation alloc]init];
 [blockOperation addExecutionBlock:<#^(void)block#>];

NSOperation 依赖关系

[op addDependency:<#(nonnull NSOperation *)#>];

依赖完成后本Operation才会执行

移除(必须手工移除):

[op removeDependency:<#(nonnull NSOperation *)#>];

NSOperation 状态

op.name = @"image";

NSLog(@"%@",op.ready?@"YES":@"NO");
NSLog(@"%@",op.finished?@"YES":@"NO");
NSLog(@"%@",op.cancelled?@"YES":@"NO");

NSLog(@"%@",op.asynchronous?@"YES":@"NO");

[op setCompletionBlock:<#(void (^ _Nullable)(void)()completionBlock#>];

NSOperationQueue

//add 操作
    [queue addOperation:operation];
    [queue addOperations:<#(nonnull NSArray *)#> waitUntilFinished:NO];
    [queue addOperationWithBlock:<#^(void)block#>];


    [queue setMaxConcurrentOperationCount:5];//可跨队列,但必须保证没有阻塞和挂起
    [queue cancelAllOperations];
    [queue setSuspended:YES];//暂停

    [queue waitUntilAllOperationsAreFinished];

mainQueue

  NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];

GCD

(作业相关内容,必须好好学)

用函数的方法调用进程,是一个 C语言 API

Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统

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

dispatch queue分为下面三种:

  • Serial
    又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。

  • Concurrent
    又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。

  • Main dispatch queue
    它是全局可用的serial queue,它是在应用程序主线程上执行任务的。
    我们看看dispatch queue如何使用

用法:

  1. 拿到一个 queue
  2. 把 block 放到 queue 中去

用dispatch_async处理后台任务

dispatch_sync:等执行结束再返回
dispatch_async:不等结束就返回
如果要获知任务结束信息,只要自己写就好了
现在在执行的 queue
dispatch_get_current_queue()

全局队列
      dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{...}

  //1
  DISPATCH_QUEUE_PRIORITY_HIGH
  DISPATCH_QUEUE_PRIORITY_DEFAULT
  DISPATCH_QUEUE_PRIORITY_LOW
  DISPATCH_QUEUE_PRIORITY_BACKGROUND

  //2
   QOS_CLASS_USER_INTERACTIVE//响应用户         
   QOS_CLASS_USER_INITIATED//用户发起               
   QOS_CLASS_DEFAULT//
   QOS_CLASS_UTILITY//程序内部维护
   QOS_CLASS_BACKGROUND//后台
   QOS_CLASS_UNSPECIFIED//
主队列(串行)
dispatch_get_main_queue(), ^{...}
//dispatch_main(); 在 OSX 服务程序中使用,iOS 不使用

Demo

 - (void)viewDidLoad
{
    [super viewDidLoad];

//新建线程,然后通知主线程更新界面
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSURL * url = [NSURL URLWithString:@"http://upload.jianshu.io/daily_images/images/myb4DiWJtEcFVgHwgdhh.jpg"];
    NSData * data = [[NSData alloc]initWithContentsOfURL:url];
    UIImage *image = [[UIImage alloc]initWithData:data];
    if (data != nil) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });
    }
});

}

dispatch_queue_create(串行)

dispatch_queue_create("com.my.q",NULL);//name=com.my.q

操作
dispatch_suspend()/dispatch_resume()
dispatch_set_finalizer_f(func)//clean

传递参数

dispatch_set_context(q,voidPtr)
dispatch_get_context(q,voidPtr)

dispatch_once让单例线程安全

dispatch_once(<#dispatch_once_t *predicate#>, <#^(void)block#>)

 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); // 1

用 dispatch_after 延后工作

 dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // 2
});

dispatch_apply(5, globalQ, ^(size_t index) {
// 执行5次
});

dispatch_group_async

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, ^{
    [NSThread sleepForTimeInterval:1];
    NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:3];
    dispatch_group_wait(group,10);
    NSLog(@"group3");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"updateUi");
});

用 dispatch_barrie\dispatch_semaphore 处理读者与写者的冲突(临界区)

  • dispatch_barrier_async
  • dispatch_barrier_sync
iOS开发高级进阶(13-16)-多线程_第1张图片
dispatch_barrier

用 Dispatch Sources 来监视事件(进度条)

用dispatch_semaphore(信号量)来处理多个资源

在GCD中有三个函数是semaphore的操作,分别是:
  dispatch_semaphore_create   创建一个semaphore
  dispatch_semaphore_signal   发送一个信号
  dispatch_semaphore_wait    等待信号
第一个函数有一个整形的参数,可以理解为信号的总量
dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1
dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1

int data = 3;
__block int mainData = 0;
__block dispatch_semaphore_t sem = dispatch_semaphore_create(0);


dispatch_queue_t queue = dispatch_queue_create("StudyBlocks", NULL);

dispatch_async(queue, ^(void) {
    int sum = 0;
    for(int i = 0; i < 5; i++)
    {
        sum += data;
        
        NSLog(@" >> Sum: %d", sum);
    }
    
    dispatch_semaphore_signal(sem);
});
//dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//可以看看取消注释前后的区别

for(int j=0;j<5;j++)
{
    mainData++;
    NSLog(@">> Main Data: %d",mainData);
}

用 Dispatch Sources 来监视事件(进度条)

//1
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0,     dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
    [progressIndicator incrementBy:dispatch_source_get_data(source)];
});
dispatch_resume(source);

dispatch_apply([array count], globalQueue, ^(size_t index) {
// do some work on data at index
dispatch_source_merge_data(source, 1);
});


//2
dispatch_queue_t globalQueue =     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t stdinSource =    dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                                   STDIN_FILENO,
                                                   0,
                                                   globalQueue);
dispatch_source_set_event_handler(stdinSource, ^{
    char buf[1024];
    int len = read(STDIN_FILENO, buf, sizeof(buf));
    if(len > 0)
        NSLog(@"Got data from stdin: %.*s", len, buf);
});
dispatch_resume(stdinSource);


//创建

dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                                   STDIN_FILENO,
                                                   0,
                                                   globalQueue);

DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_MACH_SEND/RECV
DISPATCH_SOURCE_TYPE_PROC 监控进程
DISPATCH_SOURCE_TYPE_VNODE 监控文件系统对象
DISPATCH_SOURCE_TYPE_SIGNAL
DISPATCH_SOURCE_TYPE_READ/WRITE 从描述符中读取数据 向描述符中写入字符

//配置
dispatch_source_set_event_handler(stdinSource, ^{...}]
dispatch_source_set_event_get_data/handle/mask(stdinSource)
dispatch_source_set_cancel_handler(stdinSource, ^{...})
//启动
dispatch_resume(stdinSource, ^{...})

//撤销
dispatch_source_cancel(stdinSource)

计时器Demo

__block int timeout=300; //倒计时时间
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒执行
dispatch_source_set_event_handler(_timer, ^{
    if(timeout<=0){ //倒计时结束,关闭
        dispatch_source_cancel(_timer);

        dispatch_async(dispatch_get_main_queue(), ^{
            
          
        });
    }else{
        int minutes = timeout / 60;
        int seconds = timeout % 60;
        NSString *strTime = [NSString stringWithFormat:@"%d分%.2d秒后重新获取验证码",minutes, seconds];
        dispatch_async(dispatch_get_main_queue(), ^{
           
            self.lbTimer.text = strTime;
         
        });
        timeout--;
        
    }  
});  
dispatch_resume(_timer);

你可能感兴趣的:(iOS开发高级进阶(13-16)-多线程)