阅读理解iOS面试题iOS开发

多线程在iOS 中的应用

2017-06-14  本文已影响255人  快乐的tomato

一、进程

1.1 什么是进程

二、线程

2.1 什么是线程

2.2 线程的串行

三、多线程

3.1 what

3.2 多线程的原理

3.3 多线程的优点

3.4 多线程的缺点

四、多线程在iOS中的应用

4.1 什么是主线程

4.2 主线程的主要作用

4.3 主线程的使用注意

4.4 实现方案


4种方案.png

五、NSThread

5.1 what
一个NSThread对象就代表一个线程

六、线程的状态

线程一共有5种状态,如图:


线程的状态流程图.png

6.1新建状态和就绪状态
现在以NSThread为例,黄色为例。
[tehread start]之前是新建状态,start之后进入了就绪状态(cpu在调度其他进程的时候也是处于调度状态),这个时候就进入了可调度线程池,放进可调度的线程池的线程是可以来回进行调度的。
6.2运行状态
线程经过cpu调度之后就进入了运行状态。
6.3阻塞状态
调用了sleep方法/等待同步锁的时候,就进入了阻塞状态。
6.4死亡状态
线程任务执行完毕,或者异常/强制退出等,就进入了死亡状态。

七、多线程的安全隐患

7.1 why

@synchronized(锁对象){
}
注意:锁定一份代码只用一把锁,用多把锁是无效的
*互斥锁的优点、缺点
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的cpu资源

八、线程之间的通信

8.1 what
在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
8.2 体现

import "ViewController.h"

@interface HMViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation HMViewController
-(void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event
{
// 在子线程中调用download方法下载图片
[self performSelectorInBackground:@selector(download) withObject:nil];
}
/

下载图片 : 子线程
*/
-(void)download
{
// 1.根据URL下载图片
NSURL *url = [NSURL URLWithString:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
NSLog(@"-------begin");
NSData data = [NSData dataWithContentsOfURL:url]; // 这行会比较耗时
NSLog(@"-------end");
UIImage image = [UIImage imageWithData:data];
// 2.回到主线程显示图片
// [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];
// setImage: 1s
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
// [self performSelectorOnMainThread:@selector(settingImage:) withObject:image waitUntilDone:NO];
}
/

设置(显示)图片: 主线程
*/
//- (void)settingImage:(UIImage *)image
//{
// self.imageView.image = image
//}
@end

九、GCD

9.1 what

9.2 优势

9.3 队列和任务

GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO原则:先进先出,后进后出
9.4 并发对列
GCD默认已经提供了全局的并发对列,供整个应用使用,不需要手动创建
// 1.获得全局的并发队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
9.5串行队列
GCD中获得串行有2中途径
// 1.创建串行队列 dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", NULL);
//2.使用主队列(跟主线程相关联的队列) dispatch_queue_t queue = dispatch_get_main_queue();
各队列的执行效果

e.png

上代码

#import "ViewController.h"

@interface HMViewController ()

@end

@implementation HMViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self performSelectorInBackground:@selector(test) withObject:nil];
    
//    [self syncMainQueue];
}

- (void)test
{
    NSLog(@"test --- %@", [NSThread currentThread]);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"任务 --- %@", [NSThread currentThread]);
    });
}

/**
 * 使用dispatch_async异步函数, 在主线程中往主队列中添加任务
 */
- (void)asyncMainQueue
{
    // 1.获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.添加任务到队列中 执行
    dispatch_async(queue, ^{
        NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
    });
}

/**
 * 使用dispatch_sync同步函数, 在主线程中往主队列中添加任务 : 任务无法往下执行
 */
- (void)syncMainQueue
{
    // 1.获得主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.添加任务到队列中 执行
    dispatch_sync(queue, ^{
        NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
    });
//    dispatch_sync(queue, ^{
//        NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
//    });
//    dispatch_sync(queue, ^{
//        NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
//    });
    
    // 不会开启新的线程, 所有任务在主线程中执行
}

// 凡是函数名种带有create\copy\new\retain等字眼, 都需要在不需要使用这个数据的时候进行release
// GCD的数据类型在ARC环境下不需要再做release
// CF(Core Foundation)的数据类型在ARC环境下还是需要再做release

/**
 * 用dispatch_sync同步函数往串行列中添加任务
 */
- (void)syncSerialQueue
{
    // 1.创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", NULL);
    
    // 2.添加任务到队列中 执行
    dispatch_sync(queue, ^{
        NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
    });
    
    // 3.释放资源
//    dispatch_release(queue);   // MRC(非ARC)
    
    // 总结: 不会开启新的线程
}

/**
 * 用dispatch_sync同步函数往并发队列中添加任务
 */
- (void)syncGlobalQueue
{
    // 1.获得全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2.添加任务到队列中 执行
    dispatch_sync(queue, ^{
        NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
    });
    
    // 总结: 不会开启新的线程, 并发队列失去了并发的功能
}

/**
 * 用dispatch_async异步函数往串行队列中添加任务
 */
- (void)asyncSerialQueue
{
    // 1.创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.baidu.queue", NULL);
    
    // 2.添加任务到队列中 执行
    dispatch_async(queue, ^{
        NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
    });
    
    // 总结: 只开1个线程执行任务
}

/**
 * 用dispatch_async异步函数往并发队列中添加任务
 */
- (void)asyncGlobalQueue
{
    // 1.获得全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2.添加任务到队列中 执行
    dispatch_async(queue, ^{
        NSLog(@"----下载图片1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----下载图片2-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----下载图片3-----%@", [NSThread currentThread]);
    });
    
    // 总结: 同时开启了3个线程
}

@end

9.6 线程间的通信
从子线程回到主线程
上代码

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"--download--%@", [NSThread currentThread]);
        // 下载图片
        NSURL *url = [NSURL URLWithString:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url]; // 这行会比较耗时
        UIImage *image = [UIImage imageWithData:data];
        
        // 回到主线程显示图片
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"--imageView--%@", [NSThread currentThread]);
            self.imageView.image = image;
        });
    });
}

9.7 延时执行
2种方法

- (void)delay
{
    //    NSLog(@"----touchesBegan----%@", [NSThread currentThread]);
    
    //    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self performSelector:@selector(run) withObject:nil afterDelay:2.0];//第一种
    //    });
    // 1.全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2.计算任务执行的时间
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    
    // 3.会在when这个时间点, 执行queue中的任务。第二种
    dispatch_after(when, queue, ^{
        NSLog(@"----run----%@", [NSThread currentThread]);
    });
}
//- (void)run
//{
//    NSLog(@"----run----%@", [NSThread currentThread]);
//}

9.8一次性代码
保证某段代码在程序运行的过程中只被执行1次

   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       NSLog(@"-------touchesBegan");
   });
}

9.9 队列组
首先有一个需求,分别异步执行2个耗时的操作,等2个异步操作都执行完毕后,再回到主线程执行操作。
dispatch_group_t group = dispatch_group_create();
上代码

 /**
     1.下载图片1和图片2
     
     2.将图片1和图片2合并成一张图片后显示到imageView上
     
     思考:
     * 下载图片 : 子线程
     * 等2张图片都下载完毕后, 才回到主线程
     */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 创建一个组
    dispatch_group_t group = dispatch_group_create();
    
    // 开启一个任务下载图片1
    __block UIImage *image1 = nil;
    dispatch_group_async(group, global_queue, ^{
        image1 = [self imageWithURL:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];
    });
    
    // 开启一个任务下载图片2
    __block UIImage *image2 = nil;
    dispatch_group_async(group, global_queue, ^{
        image2 = [self imageWithURL:@"http://news.baidu.com/z/resource/r/image/2014-06-22/b2a9cfc88b7a56cfa59b8d09208fa1fb.jpg"];
    });
    
    // 同时执行下载图片1\下载图片2操作
    
    // 等group中的所有任务都执行完毕, 再回到主线程执行其他操作
    dispatch_group_notify(group, main_queue, ^{
        self.imageView1.image = image1;
        self.imageView2.image = image2;
        
        // 合并
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
        [image1 drawInRect:CGRectMake(0, 0, 100, 100)];
        [image2 drawInRect:CGRectMake(100, 0, 100, 100)];
        self.bigImageView.image = UIGraphicsGetImageFromCurrentImageContext();
        // 关闭上下文
        UIGraphicsEndImageContext();
    });

}
- (UIImage *)imageWithURL:(NSString *)urlStr
{
    NSURL *url = [NSURL URLWithString:urlStr];
    NSData *data = [NSData dataWithContentsOfURL:url]; // 这行会比较耗时
    return [UIImage imageWithData:data];
}

十、NSOperation

10.1 what
作用:配合使用NSOperation和NSOperationQueue也能实现多线程编程
实现步骤:

10.2 how
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
使用NSOperationa子类有3种

10.2.1 NSInvocationOperation

-(void)invocationOperation
{
    // 1.创建操作对象, 封装要执行的任务
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
    // 2.执行操作(默认情况下, 如果操作没有放到队列queue中, 都是同步执行)
    [operation start];
}
-(void)download
{
    for (int i = 0; i<10; i++) {
        NSLog(@"------download---%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.1];
    }
}

10.2.2 NSBlockOperation
如下代码会开启3个线程,线程数取决于任务的个数

-(void)blockOperation
{
    // 1.封装操作
//    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//        NSLog(@"NSBlockOperation------下载图片1---%@", [NSThread currentThread]);
//    }];
    NSBlockOperation *operation = [[NSBlockOperation alloc] init];
    
    [operation addExecutionBlock:^{
        NSLog(@"NSBlockOperation------下载图片1---%@", [NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
        NSLog(@"NSBlockOperation------下载图片2---%@", [NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
        NSLog(@"NSBlockOperation------下载图片3---%@", [NSThread currentThread]);
    }];
    
    // 2.执行操作
    [operation start];
}

添加到NSOperationQueue的操作

- (void)operationQueue
{
    // 1.封装操作
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
//    operation1.queuePriority = NSOperationQueuePriorityHigh
    
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i<10; i++) {
            NSLog(@"NSBlockOperation------下载图片---%@", [NSThread currentThread]);
            [NSThread sleepForTimeInterval:0.1];
        }
    }];
//    [operation3 addExecutionBlock:^{
//        for (int i = 0; i<10; i++) {
//            NSLog(@"NSBlockOperation------下载图片2---%@", [NSThread currentThread]);
//            [NSThread sleepForTimeInterval:0.1];
//        }
//    }];
    
    // 2.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//设置并发数
    queue.maxConcurrentOperationCount = 2; // 2 ~ 3为宜
    
    // 设置依赖
    [operation2 addDependency:operation3];
    [operation3 addDependency:operation1];
    
    // 3.添加操作到队列中(自动执行操作, 自动开启线程)
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    
//    [queue setSuspended:YES];
}

10.3 对列的暂停、取消和恢复
[queue setSuspended:YES];//yes代表暂停,no代表恢复
[queue cancel]// 取消
10.4 操作依赖(代码在上边)
10.5 NSOperation下载图片的思路

h.png

github上有一个比较好的下载图片框架,sdwebimage,可以去研究一下。

上一篇 下一篇

猜你喜欢

热点阅读