iOS开发-多线程知识

多线程方案


创建线程一般不应超过5条(包括本来存在的主线程)即非主线程不应超过4条

NSThread

1、创建线程

// 方式1,创建线程,设置,启动线程,如果调用的方法需要参数,可以用object这个参数传参
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download) object:nil];
    thread.name = @"下载东西";
    [thread start];
    // 方式2,创建线程后自启动线程
    [NSThread detachNewThreadSelector:@selector(download) toTarget:self withObject:nil];
    
    // 方式3,隐式创建并启动线程
    [self performSelectorInBackground:@selector(download) withObject:nil];
    // 后面两种方式优点在于简单快捷,缺点就是没法做详细设置,如设置name

2、获得当前线程

- (void)download
{
    // 打印当前所在的线程,会显示name和线程标号,1为主线程,其他为子线程
    NSLog(@"---%@---",[NSThread currentThread]);
}

3、判断线程是否主线程

+ (NSThread *)mainThread; // 获得主线程
- (BOOL)isMainThread; // 是否为主线程
+ (BOOL)isMainThread; // 是否为主线程

控制线程的方法

// 进入就绪状态 -> 运行状态,当线程任务执行完毕,自动进入死亡状态
- (void)start;

// 进入阻塞状态
1.直到某刻
+ (void)sleepUntilDate:(NSDate *)date;
2.几秒之后
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 进入死亡状态
+ (void)exit;

多线程的安全隐患

当多个线程访问同一块资源的时候会出现数据错乱的问题,所以使用多线程会存在安全隐患。
解决方法:使用互斥锁
互斥锁格式:(※锁对象可以使任何对象,但是不要频繁创建,否则浪费资源;※一份代码对应一把锁)

@synchronized(锁对象) { // 需要锁定的代码  }

其实互斥锁的本质就是让线程同步,使线程按顺序执行。

优点:能有效防止因多线程抢夺资源造成的数据安全问题

缺点:需要消耗大量的CPU资源

原子和非原子属性

原子属性:atomic,为setter方法加锁(默认就是atomic

非原子属性:nonatomic,不会为setter方法加锁

对比:
atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备
建议定义属性都用nonatomic属性,在需要加锁的地方才用互斥锁,这样能节省资源,更加适合移动设备上的开发,并且需要注意的是,加锁和资源抢夺等业务逻辑都交给服务器端去处理。

线程间的通信

1.主线程:UI线程,用于显示、刷新UI界面,处理UI控件的事件
2.子线程:后台线程,异步线程,用于耗时操作
如何通信?
1.A线程传递数据给B线程
2.一个线程执行完特定任务后,转到另一个线程执行,如下载网络图片,一般由子线程从网络上下载图片,然后让主线程刷新UIImageView上的图片。

NSOperation
配合使用NSOperation和NSOperationQueue也能实现多线程
实现步骤:
1.先将需要执行的操作封装到一个NSOperation对象中
2.然后将NSOperation对象添加到NSOperationQueue中
3.系统会自动将NSOperationQueue中的NSOperation取出来
4.将取出的NSOperation封装的操作放到一条新线程中执行

但是,NSOperation是一个抽象类,并不具备封装操作的能力,所以我们必须使用它的子类

 使用NSOperation子类的方式有下面3种:
 1.NSInvocationOperation(不常用)
2.NSBlockOperation
3.自定义子类继承NSOperation,实现内部相应的方法

NSInvocationOperation的基本实现

    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
     // operation直接调用start,是同步执行(在当前线程执行,不会另开线程
//    [operation start];)
    // 要创建一个operation队列然后add进去才能实现多线程
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation];

- (void)download
{
    NSLog(@"---%@---",[NSThread currentThread]);
}


NSBlockOperation的基本实现
没使用队列

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"---下载1---%@",[NSThread currentThread]);
    }];
    
    //addExecutionBlock方法可以往operation中添加任务
    [operation1 addExecutionBlock:^{
        NSLog(@"--下载11---%@",[NSThread currentThread]);
    }];
    
    [operation1 addExecutionBlock:^{
        NSLog(@"--下载12---%@",[NSThread currentThread]);
    }];
    
    // 如果调用start,operation的任务数大于1的话,第1个任务在主线程执行,其他的异步(另开线程)串行执行(按顺序执行)
    [operation1 start];

添加到队列

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"---下载1---%@",[NSThread currentThread]);
    }];
    
    //addExecutionBlock方法可以往operation中添加任务
    [operation1 addExecutionBlock:^{
        NSLog(@"--下载11---%@",[NSThread currentThread]);
    }];
    
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"---下载2---%@",[NSThread currentThread]);
    }];
    
    // 放进下面队列的任务都是异步(不同线程)并发执行(同时执行)
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];

直接使用队列,不用新建NSBlockOperation对象

// 1.创建队列(非主队列)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.调用addOperationWithBlock方法,直接往队列里面添加任务
    [queue addOperationWithBlock:^{
        NSLog(@"---下载1---%@",[NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"---下载2---%@",[NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"---下载3---%@",[NSThread currentThread]);
    }];
// 以上都是异步并发执行的

自定义NSOperation

重写- (void)main方法,在里面实现想执行的任务

重写- (void)main方法的注意点
1.自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
2.经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

NSOperationQueue的使用
类型:
1.主队列[NSOperationQueue mainQueue];
2.非主队列[[NSOperationQueue alloc] init];
可以设置线程的最大并发数,设置之后可以优化性能,因为它会重复利用用过的线程

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 2;
//或者
[queue setMaxConcurrentOperationCount:2];

NSOperation的其他使用
>可以通过设置依赖来保证执行顺序,注意两个operation不能互相依赖
>两个不同队列中的operation之间也能设置依赖

/**
     假设有A、B、C三个操作,要求:
     1. 3个操作都异步执行
     2. 执行完A再执行B再执行C
     */
    
    // 1.创建一个队列(非主队列)
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2.创建3个操作
    NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"A1---%@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"B---%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"C---%@", [NSThread currentThread]);
    }];
    
    // 设置依赖
    [operationB addDependency:operationA];
    [operationC addDependency:operationB];
    
    // 3.添加操作到队列中(自动异步执行任务)
    [queue addOperation:operationC];
    [queue addOperation:operationA];
    [queue addOperation:operationB];

>可以为某个operation设置监听,等它执行完后执行想要的代码

NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"A1---%@", [NSThread currentThread]);
    }];
[operationA setCompletionBlock:^{
     NSLog(@"AAAAA---%@", [NSThread currentThread]);
 }];

>线程间的通信,NSThread,GCD,NSOperation之间可以混合使用

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        // 1.异步下载图片
        NSURL *url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/37d3d539b6003af3290eaf5d362ac65c1038b652.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        
        // 2.回到主线程,显示图片
// 2.1 NSThread
//        [self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>];
// 2.2 GCD
//        dispatch_async(dispatch_get_main_queue(), ^{
//            
//        });
// 2.3
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = image;
        }];
    }];

>队里的取消,暂停,恢复及一般应用的地方

* 取消所有的操作
- (void)cancelAllOperations;

* 暂停所有的操作
[queue setSuspended:YES];

* 恢复所有的操作
[queue setSuspended:NO];

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    
//    [queue cancelAllOperations]; // 取消队列中的所有任务(不可恢复)
}

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
//    [queue setSuspended:YES]; // 暂停队列中的所有任务
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
//    [queue setSuspended:NO]; // 恢复队列中的所有任务
}

如何避免cell中的图片重复下载(思路)
需要:创建字典images(根据URL(key)存储图片(value))、字典operations(根据URL存储操作)

用第三方框架:SDWebImage即可实现

 

把图片写入沙盒

    // UIImage --> NSData --> File
    NSData *data = UIImagePNGRepresentation(image);
    
    // 一般存在沙盒中Library的caches文件夹里面,Document和Library中的preferences会影响iTunes的软件同步,而tmp文件夹不安全,里面的数据随时会被系统删除
    // 获得caches的文件路径
    NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, NO)];
    // 获得网络图片的URL
    NSString *filename = [imageURL lastpathComponent];
    // 拼接文件路径
    NSString *file = [caches stringByAppendingPathComponent:filename];
    [data writeToFile:file atomically:YES];

从沙盒中读取图片

    // 获得caches的文件路径
    NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, NO)];
    // 获得网络图片的URL
    NSString *filename = [imageURL lastpathComponent];
    // 拼接文件路径
    NSString *file = [caches stringByAppendingPathComponent:filename];
    
    NSData *data = [NSData dataWithContentsOfFile:file];
    UIImage *image = [UIImage imageWithData:data];

 

原文地址:https://www.cnblogs.com/jierism/p/6106702.html