iOS(3)多线程编程技术(Thread Cocoa opreations GCD(Grand Central Dispatch ))

简介

      什么是多线程?多线程有什么作用?

      有了多线程我们可以同时做多个事情,而不是一个一个任务的完成.比如:前段和后台的交互 大任务(学要消耗一定时间和资源)等等,也就是说 我们可以使用线程把占据时间长的任务放到后台中处理.而不影响用户的使用.

      线程间通讯

      有一个非常重要的队列 就是主队列.在这个主队列中处理多点触控及所有与UI有关的操作等等.他非常特殊,原因有两点:(1) 我们绝对不想他阻塞,我们不会将需要执行很长时间的任务放在主队列上执行.(2)我们将其用于所有与UI相关的同步,也就是线程间通讯要注意的地方.所有有可能会是屏幕UI发生变化的.都应放在主队列上执行

     线程的定义:

     每个正在系统上运行的程序都是一个进程,每个进程包含一个到多个线程.进程也可能是整个程序或者是部分程序的动态执行.线程是一组指令的集合,或者是程序的特殊段,他可以在程序里独立执行,也可以把它理解为代码运行的上下文.所以县城基本上轻量级的进程,他负责再单个程序里执行多任务.通常有操作系统负责多个线程的调度和执行.

     iOS支持的多线程技术

一 Thread

     1)显示创建线程:NSThreed

     2)隐式创建多线程:NSObject

二 Cocoa opreations

     NSOpreation 类是一个抽象类,因此我们必须使用他的两个子类

     1)NSInvocationOpreation

     2)NSBlockOpreations

     3)NSOpreationQueue(继承于NSObject)

三 Grand Central Dispatch (GCD)

     1)GCD的创建

     2)重复执行线程及一次性执行:dispath_apply & dispatch_once

     3)操作(串行)队列:dispatch_queue_create

     4)GCD群组通知: dispatch_group_t

     5)GCD实现计时器

     6)后台运行

     7)延迟执行

四比较多线程技术


一 Thread

我们可以使用NSthread 和NSobject类去调用

1)显示创建线程:NSthread

    创建NSthread有两中办法

  

//第一种:创建之后需要使用start方法,才会执行方法
    NSThread *threadAlloc = [[NSThread alloc]initWithTarget:self selector:@selector(thread:) object:nil];
    [threadAlloc start];
    //第二种:创建并马上执行方法
    [NSThread detachNewThreadSelector:@selector(thread) toTarget:self withObject:nil];
2)隐式创建线程:NSobject

我们也可以使用NSobject类直接调用方法

 [self performSelectorInBackground:@selector(thread) withObject:nil];

取消线程的方法

  实际上并没有真正提供取消线程的API.苹果提供了一个cancel的API.蛋挞不能作用于取消线程,它只能改变运行状态,我们可以使用它来进行条件判断

- (void)threadCancel
{
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadCancelNow) object:nil];
    [thread start];
}

- (void)threadCancelNow
{
    int a = 0;
    while (![[NSThread currentThread] isCancelled]) {
        NSLog(@"a - %d", a);
        a++;
        if (a == 5000) {
            NSLog(@"终止循环");
            [[NSThread currentThread] cancel];
            break;
        }
    }
}

程序效果:循环输出5000次,线程就会被终止.

NSThread线程间通讯 -- 调用主线程修改UI:

只需要传递一个selector和它的参数.withObject参数可以为nil waitUntilDone代表是否要等待调用他的这个线程执行之后再将它从主队列调出,并在主队列上运行,通常设为NO,不需要等待

    [self performSelectorOnMainThread:@selector(reload) withObject:nil waitUntilDone:NO];

NSThread相关属性及方法
g *name NS_AVAILABLE(10_5, 2_0);
    
    /**
     *  获取当前线程的线程对象
     *
     *  通过这个属性可以查看当前线程是第几条线程,主线程为1。
     *  可以看到当前线程的序号及名字,主线程的序号为1,依次叠加。
     */
    + (NSThread *)currentThread;
    
    // 线程休眠(秒)
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    
    // 线程休眠,指定具体什么时间休眠
    + (void)sleepUntilDate:(NSDate *)date;
    
    // 退出线程 
    // 注意:这里会把线程对象销毁!销毁后就不能再次启动线程,否则程序会崩溃。
    + (void)exit;



二Cocoa operations

1)NSInvocationOperation

创建NSInvocationOperation线程,附带一个NSString参数

 NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction:) object:@"abc"];
    //需要启动线程,默认是不启动的
    [operation start];
如果在创建时定义了参数,那么接受的时候,可以对sender进行转换,如字符串 数组等

-(void)invocationAction:(NSInvocationOperation *)sender{
    NSLog(@"sender - %@",sender); //输出params
    NSString *str = (NSString *)sender;
    NSLog(@"str - %@e", str);           // params
}

    输出结果:    

            2015-10-30 21:11:22.283多线程[43996:509231] sender - abc

      2015-10-30 21:11:22.284多线程[43996:509231] str - abce

附带一提,线程的普通创建一般为并发执行的,因为串行队列是需要显示创建的,如没看见此类代码,那么就是并发队列线程,因此 上述代码也就是并发线程,关于并发和串行队列(线程),我将会在下面详细说明,我们继续往下看


你也可以用NSOperationQueue来创建一个线程队列,用来添加子线程:

    // 获取/设置线程的名字
    @property (copy) NSStrin
 NSOperationQueue * queue = [[NSOperationQueue alloc]init];
    //线程A
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction:) object:@"operation1"];
    //线程B
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationAction:) object:@"operation2"];
    //向队列中添加子线程
    [queue addOperations:@[operation1,operation2] waitUntilFinished:YES];

必须使用addOperations:方法八线程添加至队列,不然线程不会执行,队列是并行执行.或者,你也可以使用addoperation:方法添加单个线程

2)NSBlockOperation

创建NSBlockOperation

  //创建线程任务
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"one _ %@",[NSThread currentThread]);
    }];//执行线程任务
    [blockOperation start];

2015-10-31 11:12:22.401多线程[7704:81963] one _ <NSThread: 0x7fbb53d1d610>{number = 1, name = main}

注意:这会在当前线程中执行 因为他是根据调用的线程决定的

比如说你在主线程中调用它.那么他就在主线程中执行.如果你在子线程中调用它.那么他就在子线程中执行

做个简单的实验,我们新建一条子线程,然后在子线程中调用NSBlockOperation

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"one _ %@",[NSThread currentThread]);
        }];//执行线程任务
        [blockOperation start];

    });

2015-10-31 11:20:29.167多线程[8206:87771] one _ <NSThread: 0x7fe0b8632b10>{number = 2, name = (null)} 因此这个理论是正确的

我们也可以使它并发执行,通过使用addExecutionBlock方法添加多个Block,这样就能使它在主线程和其他线程中工作

    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"one _ %@",[NSThread currentThread]);
    }];//执行线程任务
  
   [blockOperation addExecutionBlock:^{
       NSLog(@"two_%@",[NSThread currentThread]);
   }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"three_%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"four_%@",[NSThread currentThread]);
    }];
      [blockOperation start];

2015-10-31 11:28:58.493多线程[8760:94808] two_<NSThread: 0x7ff4404176c0>{number = 1, name = main}

2015-10-31 11:28:58.493多线程[8760:94832] three_<NSThread: 0x7ff440681820>{number = 2, name = (null)}

2015-10-31 11:28:58.493多线程[8760:94833] four_<NSThread: 0x7ff4405267b0>{number = 3, name = (null)}

2015-10-31 11:29:00.497多线程[8760:94831] one _ <NSThread: 0x7ff44043a2e0>{number = 4, name = (null)}

大家都看到,即使我们通过使用addExecutionBlock方法使它并发执行任务,但是它也依旧会在主线程执行,因此我们就需要使用NSOperationQueue了。


3)NSOperationQueue

这里介绍一下NSOperation的依赖关系,依赖关系会影响现成的执行顺序

   //创建操作队列
    NSOperationQueue * queue = [[NSOperationQueue alloc]init];
    //线程A
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"one_%@",[NSThread currentThread]);
    }];
    //线程B
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"two_%@",[NSThread currentThread]);
    }];
    //线程C
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"three_%@",[NSThread currentThread]);
    }];
  //线程B依赖线程C 也就是等线程C执行完之后才会执行线程B
    [op2 addDependency:op3];
  //线程C依赖线程A,同上只不过依赖对象改成了线程A
    [op3 addDependency:op1];
    //为队列添加线程
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];

2015-10-31 11:41:20.021多线程[9491:103289] one_<NSThread: 0x7f9a49f169a0>{number = 2, name = (null)}

2015-10-31 11:41:20.022多线程[9491:103289] three_<NSThread: 0x7f9a49f169a0>{number = 2, name = (null)}

2015-10-31 11:41:20.022多线程[9491:103289] two_<NSThread: 0x7f9a49f169a0>{number = 2, name = (null)}

当你没有添加依赖关系时,队列是并行执行的.注意:依赖关系可以多重依赖,但不要建立循环依赖

不添加依赖关系运行结果(并行执行)

2015-10-31 11:45:28.088多线程[9762:106647] two_<NSThread: 0x7fc312798130>{number = 4, name = (null)}

2015-10-31 11:45:28.088多线程[9762:106649] three_<NSThread: 0x7fc312714260>{number =2, name = (null)}

2015-10-31 11:45:28.088多线程[9762:106646] one_<NSThread: 0x7fc31263d4d0>{number =3, name = (null)}


Cocoa operations线程间通信 --调用主线程修改UI

    // 创建线程对象(并发)
    NSBlockOperation *blockOperation = [[NSBlockOperation alloc] init];
    
    // 添加新的操作
    [blockOperation addExecutionBlock:^{
        NSLog(@"two - %@", [NSThread currentThread]);
    }];
    
    // 添加新的操作
    [blockOperation addExecutionBlock:^{
        NSLog(@"three - %@", [NSThread currentThread]);
        // 在主线程修改UI
        NSOperationQueue *queue = [NSOperationQueue mainQueue];
        [queue addOperationWithBlock:^{
            //在这里修改UI
            NSLog(@"%@",[NSThread currentThread]);
        }];
    }];
    
    [blockOperation start];

2015-10-31 11:51:04.167多线程[10109:110581] two - <NSThread: 0x7f8169e0a6e0>{number = 1, name = main}

2015-10-31 11:51:04.167多线程[10109:110669] three - <NSThread: 0x7f8169e1b610>{number = 2, name = (null)}

2015-10-31 11:51:04.174多线程[10109:110581] <NSThread: 0x7f8169e0a6e0>{number = 1, name = main}


NSOperation方法及属性

// 设置线程的最大并发数
@property NSInteger maxConcurrentOperationCount;

// 线程完成后调用的Block
@property (copy) void (^completionBlock)(void);

// 取消线程
- (void)cancel;

只列举上面那些,其它的方法就不全列出来了。

注意:在NSOperationQueue类中,我们可以使用cancelAllOperations方法取消所有的线程。这里需要说明一下,不是执行cancelAllOperations方法时就会马上取消,是等当前队列执行完,下面的队列不会再执行


最大并发Operation数目

在一个Operation Queue中是可以同时执行多个Operation的,Operation Queue会动态的创建多个线程来完成相应Operation。具体的线程数是由Operation Queue来优化配置的,这一般取决与系统CPU的性能,比如CPU的核心数,和CPU的负载。但我们还是可以设置一个最大并发数的,那么Operation Queue就不会创建超过最大并发数量的线程。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
如果我们将 maxConcurrentOperationCount设置为 1,那么在队列中每次只能执行一个任务。这就是一个串行的执行队列了。

Grand Central Dispatch  (GCD)

1)GCD异步线程的创建:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程 -- %@",[NSThread currentThread]);
    });
GCD也可以创建同步线程,只需要把async改成sync即可

2)重复执行线程:dispatch_apply

以下代码会执行4次

    dispatch_apply(4, DISPATCH_QUEUE_PRIORITY_DEFAULT, ^(size_t index) {
        //index则为执行的次数 0开始递增
        NSLog(@"one--%ld",index);
        NSLog(@"%ld---%@",index,[NSThread currentThread]);
    });
其中需要注意的是 每次执行都会开辟一条子线程 因为是异步的原因 他们不会顺序的

2015-10-31 15:20:08.307 多线程[22413:262136] one--0 -- <NSThread: 0x7fc7516165f0>{number = 1, name = main}

2015-10-31 15:20:08.307 多线程[22413:262224] one--3 -- <NSThread: 0x7fc751518310>{number = 4, name = (null)}

2015-10-31 15:20:08.307 多线程[22413:262222] one--2 -- <NSThread: 0x7fc75170c640>{number = 3, name = (null)}

2015-10-31 15:20:08.307 多线程[22413:262223] one--1 -- <NSThread: 0x7fc75143da20>{number = 2, name = (null)}


然而,GCD还有一次性执行的方法,如下

    dispatch_once_t once;
    dispatch_once(&once, ^{
        NSLog(@"once -- %@",[NSThread currentThread]);
    });

以上代码执行一次,如果你把它放在循环的地方,运行时就会崩溃

3)操作队列:dispatch_queue_create

使用GCD也能创建串行队列,具体代码如下

  dispatch_queue_t queue = dispatch_queue_create("com.GarveyCalvin.queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 线程A
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"sleep async - %@", [NSThread currentThread]);
    });
    
    // 线程B
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"sleep barrier2 - %@", [NSThread currentThread]);
    });
    
    // 线程C
    dispatch_async(queue, ^{
        NSLog(@"async");
    });

运行效果:

以上会先执行 线程A-》线程B-》线程C,它是一个串行队列。

dispatch_queue_create的第二个参数:

1)DISPATCH_QUEUE_SERIAL(串行)

2)DISPATCH_QUEUE_CONCURRENT(并发)



4)GCD群组通知:dispatch_group_t

GCD的高级用法,等所有线程都完成工作后,再作通知。

    // 创建群组
    dispatch_group_t group = dispatch_group_create();
    
    // 线程A
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"group1");
        [NSThread sleepForTimeInterval:2];
    });
    
    // 线程B
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"group2");
    });
    
    // 待群组里的线程都完成之后调用的通知
    dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"group success");
    });

群组里的线程也是并行队列。线程A和线程B都执行完之后,会调用通知打印group success

5)GCD实现计时器

__block int time = 30;
CGFloat reSecond = 1.0;
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_TIME_NOW, reSecond * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
    time--;
    NSLog(@"%d", time);
    if (time == 0) {
        dispatch_source_cancel(timer);
    }
});
dispatch_resume(timer);

代码效果:创建了一个计时器,计时器运行30秒,每过一秒会调用一次block,我们可以在block里面写代码。因为dispatch_source_t默认是挂起状态,因此我们使用时需要使用dispatch_resume方法先恢复,不然线程不会执行。

GCD线程间通信-调用主线程修改UI:

有时候我们请求后台作数据处理,数据处理是异步的,数据处理完成后需要更新UI,这时候我们需要切换到主线程修改UI,例子如下

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"异步数据处理 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:2];
    NSLog(@"数据处理完成");
    
    // 调用主线程更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"更新UI - %@", [NSThread currentThread]);
        [self editUINow];
    });
});

因为是在主线程修改UI,所以我们最好是使用同步的GCD方法dispatch_sync。但这还不够,我们还需要使用dispatch_get_main_queue()方法来获得主线程,之后就是作UI的更新工作了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值