iOS多线程知识点

最近整理的iOS多线程方面的知识点,iOS中总共有4种实现多线程的方案,但是pthread是基于C语言并且不太好用,所以很少人用,所以也没啥好讲的。
以下内容包含NSThread、GCD和NSOperation
欢迎指错以及补充

NSThread

3种创建线程方法

  • NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
  • [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
  • [self performSelectorInBackground:@selector(run) withObject:nil];

3种方法对比

  • 第一种方法可以拿到线程对象thread,可以设置thread的一些特性,比如优先级,名称;后两种方法均拿不到线程对象。

GCD

1. 6种组合:

  • 同步函数+串行队列:不会开启子线程,所有的任务在主线程串行执行
  • 同步函数+并发队列:不会开启子线程,所有的任务在主线程串行执行
  • 同步函数+主队列:会发生死锁
  • 异步函数+串行队列:会开启一条子线程,所有的任务在子线程串行执行
  • 异步函数+并发队列:会开启n条子线程,所有的任务在n条子线程并发执行
  • 异步函数+主队列:不会开启子线程,所有的任务在主线程中串行执行

2. 一次性函数:

static dispatch_once_t onceToken;
    // NSLog(@"%ld", onceToken);
    // 原理是判断onceToken的值,若等于0则执行,若等于-1则不执行
    dispatch_once(&onceToken, ^{
        NSLog(@"once");
    });

3. 延迟执行:

[self performSelector:@selector(run) withObject:nil afterDelay:2.0];

[NSTimer scheduledTimerInterval:2.0 target:self selector:(run) userInfo:nil repeats:YES];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@", [NSThread currentThread]);
    });

4. 队列组

  • 创建一个队列组
dispatch_group_t group = dispatch_group_create();
  • 创建一个队列
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
  • 封装任务
dispatch_group_async(group, queue, ^{
        NSURL *url1 = [NSURL URLWithString:@"https://ww3.sinaimg.cn/mw1024/bfa7a89ejw1er5utf4cr9j20qo0zke4v.jpg"];
        
        NSData *imageData1 = [NSData dataWithContentsOfURL:url1];
        
        self.image1 = [UIImage imageWithData:imageData1];
        
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
---------------------------------------------------------------
dispatch_group_async(group, queue, ^{
        NSURL *url2 = [NSURL URLWithString:@"https://ww4.sinaimg.cn/mw1024/bfa7a89ejw1erp7r1nttgj20qo0zkq8g.jpg"];
        
        NSData *imageData2 = [NSData dataWithContentsOfURL:url2];
        
        self.image2 = [UIImage imageWithData:imageData2];
        
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
  • 监听通知(当队列组中的任务执行完毕后,创建图形上下文)
dispatch_group_notify(group, queue, ^{
        UIGraphicsBeginImageContext(CGSizeMake(300, 300));
        
        [self.image1 drawInRect:CGRectMake(0, 0, 150, 300)];
        [self.image2 drawInRect:CGRectMake(150, 0, 150, 300)];
        
        NSLog(@"combine-----%@", [NSThread currentThread]);
        
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        UIGraphicsEndImageContext();
        
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            
            NSLog(@"UI-----%@", [NSThread currentThread]);
        });
    });

5. 快速迭代

- (void)test2
{
    NSString *from = @"/Users/AngusGray/Desktop/from";
    NSString *to = @"/Users/AngusGray/Desktop/to";
    
    NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
    
    dispatch_apply(subPaths.count, dispatch_get_global_queue(0, 0), ^(size_t i) {
        NSString *fromFullPath = [from stringByAppendingPathComponent:subPaths[i]];
        NSString *toFullPath = [to stringByAppendingPathComponent:subPaths[i]];
        
        [[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
        
        NSLog(@"%@-----%@-----%@", fromFullPath, toFullPath, [NSThread currentThread]);
    });
}

6. 线程间通信

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 异步函数 + 串行|并发队列
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 设置URL
        NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1552289582393&di=bcd6e443745c4839107b46e260e9ea32&imgtype=0&src=http%3A%2F%2Fimg.12584.cn%2Fimages%2F3718642-20170112230345230.jpg"];
        
        // 将URL中的图片转换为二进制数据
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        
        NSLog(@"download-----%@", [NSThread currentThread]);
        // 将二进制数据转化为图片(在主线程中进行)
        // 异步函数|同步函数 + 主队列
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = [UIImage imageWithData:imageData];
            NSLog(@"UI-----%@", [NSThread currentThread]);
        });
    });
}

7. 栅栏函数

dispatch_barrier_async(queue, ^{
        NSLog(@"+++++");
    });

NSOperation

1. 自定义队列和主队列

  • 自定义队列:[[NSOperationQueue alloc] init];
    特点:默认并发队列,但是可以控制让它成为一个串行队列。
  • 主队列:[NSOperationQueue mainQueue];
    特点:串行队列,和主线程相关(凡是放在主队列中的任务都在主线程执行)。

2. 创建步骤

  • 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  • 封装操作对象
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op1-----%@", [NSThread currentThread]);
    }];
  • 把操作添加到队列中
// 该方法内部会自动调用start方法
[queue addOperation:op1]; 
  • 简便方法:该方法内部首先会把block中的任务封装成一个操作(Operation),然后把该操作直接添加到队列
[queue addOperationWithBlock:^{
        NSLog(@"op1-----%@", [NSThread currentThread]);
    }];

3. 特点

  • 可以设置最大并发数
queue.maxConcurrentOperationCount = 1; // 等同于串行执行
  • 可以暂停队列中的操作(只能暂停正在执行操作后面的操作,当前操作不可分割必须执行完毕
[self.queue setSuspended:YES];
  • 恢复暂停的操作
[self.queue setSuspended:NO];
  • 取消所有操作(只能取消队列中所有正在等待的操作
[self.queue cancelAllOperations];
  • 将操作添加到队列可使用数组的方式(如果使用YES,则当前线程阻塞直到所有操作完成)
[queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
  • 可以通过重写内部的main方法类可以自定义操作任务(自定义操作的好处:代码复用)
-(void)main
{
    for (int i = 0; i < 10000; i++) {
        NSLog(@"main1--%d--%@", i, [NSThread currentThread]);
    }
    
    //官方建议:在自定义操作的时候每执行完一次耗时操作就判断一下当前操作是否被取消,如果被取消则直接返回
    if (self.cancelled == YES) {
        return;
    }
    for (int i = 0; i < 10000; i++) {
        NSLog(@"main2--%d--%@", i, [NSThread currentThread]);
    }
    
    if (self.cancelled == YES) {
        return;
    }
    
    for (int i = 0; i < 10000; i++) {
        NSLog(@"main3--%d--%@", i, [NSThread currentThread]);
    }
}

4. NSBlockOperation

  • 本身不开子线程,添加额外任务时会开子线程
  • 当操作中任务数量>1时,会开启多条子线程和当前线程一起工作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op1-----%@", [NSThread currentThread]);
    }];
    
[op1 addExecutionBlock:^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    }];

[op1 start]; // 需要手动开始

5. 线程间通信

  • 在子线程中调用主线程执行任务
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
          do something;
        }];

6. 操作依赖和监听

  • 给操作添加依赖op4->op3->op2->op1
[op1 addDependency:op2];
[op2 addDependency:op3];
[op3 addDependency:op4];
  • 当操作完成可使用completionBlock监听
op4.completionBlock = ^{
        do something;
    };

GCD对比NSOperation

  1. GCD是纯C语言的API,而NSOperation是OC的对象。
  2. GCD中任务用块(block)封装,而NSOperation中任务用操作封装,block比操作更加轻量级。
  3. GCD有栅栏函数、延迟执行和快速迭代
  4. NSOperationQueue可以方便地调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的。
  5. NSOperation可以方便地指定操作间的依赖关系
  6. NSOperation可以通过KVO达到对操作的精细控制,比如监听操作是否被取消或者已经完成。
  7. NSOperation可以方便地指定操作优先级。
  8. 通过自定义NSOperation的子类可以实现操作复用。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • iOS多线程编程 基本知识 1. 进程(process) 进程是指在系统中正在运行的一个应用程序,就是一段程序的执...
    陵无山阅读 5,996评论 1 14
  • iOS中多线程 首先看一道面试题 iOS中多线程有哪些实现方案? iOS中,多线程一般有三种方案GCD、NSOpe...
    木子奕阅读 647评论 0 1
  • 厨娘是一家小餐馆的老板,朋友聚餐比较喜欢选择她家小店,仿木镶嵌斑驳印痕的餐桌在一缕阳光洒落下晶莹透亮,满盆叶片像...
    雨夜沐瑶阅读 411评论 0 5
  • 热气腾腾的是生活,是包子,是大花卷,一抹夕阳红,照的她的脸庞美极了。水里来水里去,十个手指双双浸没在柔白的米里,“...
    野鸡_是凤凰呐阅读 167评论 1 2
  • 生活中,人们多半都会对那些复杂的大事很上心,而对于简单的工作却是一副漫不经心、不屑一顾的态度。事实上,越是简单的事...
    金椰子阅读 306评论 0 0