zoukankan      html  css  js  c++  java
  • IOS高级编程之三:IOS 多线程编程

    多线程的概念在各个操作系统上都会接触到,windows、Linux、mac os等等这些常用的操作系统,都支持多线程的概念。

    当然ios中也不例外,但是线程的运行节点可能是我们平常不太注意的。

    例如:

     1 - (void)viewDidLoad
     2 {
     3     [super viewDidLoad];
     4     for(int i = 0 ; i < 100 ; i++)
     5     {
     6         NSLog(@"===%@===%d" , [NSThread currentThread].name , i);
     7         if(i == 20)
     8         {
     9             // 创建线程对象
    10             NSThread *thread = [[NSThread alloc]initWithTarget:self
    11                 selector:@selector(run) object:nil];
    12             // 启动新线程
    13             [thread start];
    14 //            // 创建并启动新线程
    15 //            [NSThread detachNewThreadSelector:@selector(run) toTarget:self
    16 //                withObject:nil];
    17         }
    18     }
    19 }
    20 - (void)run
    21 {
    22     for(int i = 0 ; i < 100 ; i++)
    23     {
    24         NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
    25     }
    26 }

    上面打印的内容每一次都是不同的,什么意思呢?

    当我们创建了4个线程后,加上UI主线程一共5个线程。

    新的线程在执行start方法之后,并不会立即执行。他们会被cpu随机的执行,只是间隔非常短,以至于我们感觉上是多个线程在同时执行。

    所以线程有这么一个特点:执行的随机性

    但是我们可以设置线程的优先级,让优先级更高的线程获得更多的执行机会。

    那么什么时候要使用多线程编程呢?

    相信有过开发经验的程序员都知道,当我们把代码写完后,程序是一行一行逐行执行代码的,当其中一行代码需要执行较长时间(例如select一个教复杂的语句或者较多的数据时),那么程序就会出现卡顿的现象,不会相应用户的操作。

    因为开启程序后会默认开启一个主线程,即UI线程。当处于刚才那种情况时,比如一个windows程序,就会出现程序暂时无响应的提示,好像电脑卡主的感觉,这是非常不好的一种感受。。。。

    当我们要避免这种情况的时候,最好的方式就是多线程,开启一个新的线程,用来执行一个耗时的操作,执行完成后再让主线程来修改ui页面(如果需要的话)。

    介绍完了线程的一些知识,那么下面来具体看ios中多线程的几种实现方式,主要有一下三种:

    1、NSThread :就是刚刚例子中使用的方式,但是使用上比较繁琐,而且需要控制好数据的同步和异步问题

    2、NSOperation 和 NSOperationQueue : 这种方式代码比较简洁,可读性强,而且使用队列的形式管理多个任务,本人比较喜欢

    3、使用GCD( Grand Central Dispatch ) :相较于NSThread使用简单,使用队列管理任务

    一、首先来介绍NSThread

    1、创建NSThread的两种方式

    -(id) initWithTarget:(id) target selector:(SEL) selector object:(id) arg:

    +(void)detachNewThreadSelector:(SEL) selector toTarget:(id) target withObject:(id) arg:

    第二种方式,创建NSThread后会自动启动

    2、NSThread的常用方法

    +currentThread : 返回当前正在执行的线程对象

    3、线程的状态

    一开始的例子中提了一下,线程创建后,执行了start方法并不是立即就执行了。可能ui线程执行了几毫秒后,cpu才执行它,执行几毫秒后再执行ui线程,但这个过程是随机发生的。

    如果想让线程立即执行,那么可以让ui线程sleep 1毫秒,这样cpu就会执行其他可执行的线程,可以达到立即执行的效果

    1 [NSThread sleepForTimeInterval:0.001];//让当前运行的线程睡眠1毫秒

    线程正在执行时,调用isExecuting方法返回 YES ,线程执行完成后调用 isFinished 方法就会返回 YES

    4、终止子线程

    线程会以一下3种方式之一结束,结束后就处于死亡状态

    1)线程执行的方法体执行完成,线程正常结束

    2)执行过程中出现了错误

    3)调用NSThread 类的 exit 方法来终止当前线程

    在UI 线程中 ,NSThread 并没有提供方法来结束其他的子线程。但是我们可以利用 NSThread 的cancel 方法,执行该方法后, 该线程的状态为 isCancelled = YES,但并不会结束线程。

     1 NSThread* thread;
     2 - (void)viewDidLoad
     3 {
     4     [super viewDidLoad];
     5     // 创建新线程对象
     6     thread = [[NSThread alloc] initWithTarget:self selector:@selector(run)
     7         object:nil];
     8     // 启动新线程
     9     [thread start];
    10 }
    11 - (void)run
    12 {
    13     for(int i = 0 ; i < 100 ; i++)
    14     {
    15         if([NSThread currentThread].isCancelled)
    16         {
    17             // 终止当前正在执行的线程
    18             [NSThread exit];
    19         }
    20         NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
    21         // 每执行一次,线程暂停0.5秒
    22         [NSThread sleepForTimeInterval:0.5];
    23     }
    24 }
    25 - (IBAction)cancelThread:(id)sender
    26 {
    27     // 取消thread线程,调用该方法后,thread的isCancelled方法将会返回NO
    28     [thread cancel]; 
    29 }

    利用例子中代码的形式,我们就可以达到在UI线程中结束其他子线程的目的了。

    5、线程睡眠

    要让线程进入阻塞状态或者睡眠状态,可以执行sleepXXX格式的方法:

    +(void) sleepUntilDate:(NSDate *) aDate  : 让线程睡眠,知道aDate那个时间点再醒过来

    -(void)sleepForTimeInterval :让线程睡眠多少秒

    6、改变线程优先级

    NSThread 提供了如下几个方法来获取和设置线程的优先级

    +threadPriority: 获取当前正在执行的线程的优先级

    -threadPriority:获取线程实例的优先级

    +setThreadPriority :(double) priority : 设置当前正在执行的线程的优先级

    -setThreadPriority :(double) priority : 设置线程实例的优先级

    (double) priority的 取值范围是0.0~1.0;优先级越高的线程获得的执行机会越多

     1 - (void)viewDidLoad
     2 {
     3     [super viewDidLoad];
     4     NSLog(@"UI线程的优先级为:%g" , [NSThread threadPriority]);
     5     // 创建第一个线程对象
     6     NSThread* thread1 = [[NSThread alloc]
     7         initWithTarget:self selector:@selector(run) object:nil];
     8     // 设置第一个线程对象的名字
     9     thread1.name = @"线程A";
    10     NSLog(@"线程A的优先级为:%g" , thread1.threadPriority);
    11     // 设置使用最低优先级
    12     thread1.threadPriority = 0.0;
    13     // 创建第二个线程对象
    14     NSThread* thread2 = [[NSThread alloc]
    15         initWithTarget:self selector:@selector(run) object:nil];
    16     // 设置第二个线程对象的名字
    17     thread2.name = @"线程B";
    18     NSLog(@"线程B的优先级为:%g" , thread2.threadPriority);
    19     // 设置使用最高优先级
    20     thread2.threadPriority = 1.0;
    21     // 启动2个线程
    22     [thread1 start];
    23     [thread2 start];
    24 }
    25 - (void)run
    26 {
    27     for(int i = 0 ; i < 100 ; i++)
    28     {
    29         NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
    30     }
    31 }

    二、使用GCD实现多线程

    GCD简化了多线程的实现,主要有两个核心概念:

    1、队列:队列负责管理开发者提交的任务,以先进先出的方式来处理任务。

    1)串行队列:每次只执行一个任务,当前一个任务执行完成后才执行下一个任务

    2)并行队列:多个任务并发执行,所以先执行的任务可能最后才完成(因为具体的执行过程导致)

    2、任务:任务就是开发者提供给队列的工作单元,这些任务将会提交给队列底层维护的线程池,因此这些任务将会以多线程的方式执行。

    3、创建队列

    1)获取系统默认的全局并发队列:

    1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    2) 获取系统主线程关联的穿行队列

    1 dispatch_queue_t queue = dispatch_get_main_queue();

    如果将任务提交给主线程关联的串行队列,那么就相当于在程序主线程中去执行该任务。

    3)创建穿行队列

    1 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);

    4)创建并发队列

    1 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);

    5)获取当前执行代码所在队列

    dispatch_get_current_queue,返回一个dispatch_queue_t类型的值

    4、提交任务

    使用下面的方法将任务以同步或者异步的方式提交到队列

     1 //将代码块以异步的方式提交给指定队列
     2 void  dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
     3 
     4 //将函数以异步的方式提交给指定队列,一般执行函数的方法与执行代码块的方法比,方法名多了一个_f的后缀
     5 void  dispatch_async_f(dispatch_queue_t queue, void* context, dispatch_function_t work);
     6 
     7 //将代码块以同步的方式提交给指定队列
     8 void  dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
     9 
    10 //将函数以同步的方式提交给指定队列,一般执行函数的方法与执行代码块的方法比,方法名多了一个_f的后缀
    11 void  dispatch_sync_f(dispatch_queue_t queue, void* context, dispatch_function_t work);
     1 //将代码块以异步的方式提交给指定队列,队列的线程池负责在指定时间点 when 之后执行
     2 void  dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
     3 
     4 //将函数以异步的方式提交给指定队列,队列的线程池负责在指定时间点 when 之后执行
     5 void  dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue,  void* context, dispatch_function_t work);
     6 
     7 //将代码块以异步的方式提交给指定队列,队列的线程池将会重复多次执行该任务
     8 void  dispatch_apply(size_t iterations, dispatch_queue_t queue, void(^block)(size_t));
     9 
    10 //将函数以异步的方式提交给指定队列,队列的线程池将会重复多次执行该任务
    11 void  dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void* context, void(*work)(void*, size_t));
    12 
    13 //将代码块提交给指定队列,在应用的某个生命周期内金执行一次
    14 void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

    下面给出一个以异步方式向串行队列、并发队列添加任务的实例

     1 // 定义2个队列
     2 dispatch_queue_t serialQueue;
     3 dispatch_queue_t concurrentQueue;
     4 - (void)viewDidLoad
     5 {
     6     [super viewDidLoad];
     7     // 创建串行队列
     8     serialQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_SERIAL);
     9     // 创建并发队列
    10     concurrentQueue = dispatch_queue_create("fkjava.queue"
    11         , DISPATCH_QUEUE_CONCURRENT);
    12 }
    13 - (IBAction)serial:(id)sender
    14 {
    15     // 依次将2个代码块提交给串行队列
    16     // 必须等到第1个代码块完成后,才能执行第2个代码块。
    17     dispatch_async(serialQueue, ^(void)
    18     {
    19         for (int i = 0 ; i < 100; i ++)
    20         {
    21             NSLog(@"%@=====%d"  , [NSThread currentThread] , i);
    22         }
    23     });
    24     dispatch_async(serialQueue, ^(void)
    25     {
    26         for (int i = 0 ; i < 100; i ++)
    27         {
    28             NSLog(@"%@------%d" , [NSThread currentThread] , i);
    29         }
    30     });
    31 }
    32 - (IBAction)concurrent:(id)sender
    33 {
    34     // 依次将2个代码块提交给并发队列
    35     // 两个代码块可以并发执行
    36     dispatch_async(concurrentQueue, ^(void)
    37     {
    38         for (int i = 0 ; i < 100; i ++)
    39         {
    40             NSLog(@"%@=====%d"  , [NSThread currentThread] , i);
    41         }
    42     });
    43     dispatch_async(concurrentQueue, ^(void)
    44     {
    45         for (int i = 0 ; i < 100; i ++)
    46         {
    47             NSLog(@"%@------%d" , [NSThread currentThread] , i);
    48         }
    49     });
    50 }

    提交同步任务:

     1 - (void)viewDidLoad
     2 {
     3     [super viewDidLoad];
     4 }
     5 - (IBAction)clicked:(id)sender
     6 {
     7     // 以同步方式先后提交2个代码块
     8     dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
     9         , ^(void){
    10             for (int i = 0 ; i < 100; i ++)
    11             {
    12                 NSLog(@"%@=====%d"  , [NSThread currentThread] , i);
    13                 [NSThread sleepForTimeInterval:0.1];
    14             }
    15         });
    16     // 必须等第一次提交的代码块执行完成后,dispatch_sync()函数才会返回,
    17     // 程序才会执行到这里,才能提交第二个代码块。
    18     dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    19         , ^(void){
    20             for (int i = 0 ; i < 100; i ++)
    21             {
    22                 NSLog(@"%@-----%d"  , [NSThread currentThread] , i);
    23                 [NSThread sleepForTimeInterval:0.1];
    24             }
    25         });
    26 }

    多次执行的任务:

     1 - (void)viewDidLoad
     2 {
     3     [super viewDidLoad];
     4 }
     5 - (IBAction)clicked:(id)sender
     6 {
     7     // 控制代码块执行5次
     8     dispatch_apply(5
     9         , dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    10         // time形参代表当前正在执行第几次
    11         , ^(size_t time)
    12         {
    13             NSLog(@"===执行【%lu】次===%@" , time
    14                 , [NSThread currentThread]);
    15         });
    16 }

    只执行一次的任务

     1 @implementation FKViewController
     2 - (void)viewDidLoad
     3 {
     4     [super viewDidLoad];
     5 }
     6 - (IBAction)clicked:(id)sender
     7 {    
     8     static dispatch_once_t onceToken;
     9     dispatch_once(&onceToken, ^{
    10         NSLog(@"==执行代码块==");
    11         // 线程暂停3秒
    12         [NSThread sleepForTimeInterval:3];
    13     });
    14 }

    三、使用NSOperation 和 NSOPerationQueue 实现多线程

    和GCD差不多,也是有队列和任务的概念

    NSOperationQueue:代表一个先进先出的队列,负责管理系统提交的多个NSOperation。底层维护一个线程池,会按顺序启动线程来执行提交给队列的NSOperation

    NSOperation:代表多线程任务。一般不直接使用NSOperation,而是使用NSOperation的子类。或者使用NSInvocationOperation和NSBlockOperation(这两个类继承自NSOperation);

    1、NSOperation的使用

    NSOperation 的使用相较于GCD是面向对象的,OC实现的,而GCD应该是C实现的(看函数的定义和使用)。

    使用NSOperation 只需两步:

    1)创建 NSOperationQueue 队列,并未该队列设置相关属性

    2)创建 NSOperation 子类对象,并将该对象提交给 NSOperationQueue 队列,该队列将会按顺序依次启动每个 NSOperation。

    2、NSOperationQueue的常用方法

     1 +currentQueue //类方法,返回执行当前NSOperation的NSOperationQueue队列
     2 
     3 +mainQueue //返回系统主线程的NSOperationQueue队列
     4 
     5 -(void) addOperation:(NSOperation *) operation //将operation添加到NSOperationQueue队列中
     6 
     7 -(void) addOperations:(NSArray *) ops waitUnitlFinished:(BOLL) wait //将NSArray中包含的所有NSOperation添加到NSOperationQueue。如果第二个参数指定为YES,将会阻塞当前线程,直到提交的所有NSOperation执行完成。如果第二个参数为NO,该方法立即返回,NSArray包含的NSOperation将以异步方式执行,不会阻塞当前线程。
     8 
     9 - operations //只读属性,返回该NSOperationQueue管理的所有NSOperation
    10 -operationCount //只读属性,返回该NSOperationQueue管理的所有NSOperation数量
    11 
    12 -cancelAllOperations: //取消NSOperationQueue队列中所有正在排队和执行的NSOperation
    13 
    14 -waitUntilAllOperationsAreFinished://阻塞当前线程,直到该NSOperationQueue中所有排队和执行的NSOperation执行完成再接触阻塞
    15 
    16 -(NSInteger) maxConcurrentOperationCount://返回该队列最大支持多少个并发线程
    17 
    18 -setMaxConcurrentOperationCount:(NSInteger) count //设置该队列最大支持多少个并发线程
    19 
    20 -setSuspended:(BOOL) suspend: //设置NSOperationQueue是否已经暂停调度正在排队的NSOperation
    21 
    22 -(BOLL) isSuspended: //返回NSOperationQueue是否已经暂停调度正在排队的NSOperation

    3、使用NSInvocationOperation 和 NSBlockOperation

    NSInvocationOperation 和 NSBlockOperation 继承自 NSOperation,所以可以直接使用,用于封装需要异步执行的任务。

    使用它们实现图片异步下载:

     1 NSOperationQueue* queue;
     2 - (void)viewDidLoad
     3 {
     4     [super viewDidLoad];
     5     queue = [[NSOperationQueue alloc]init];
     6     // 设置该队列最多支持10条并发线程
     7     queue.maxConcurrentOperationCount = 10;
     8 }
     9 - (IBAction)clicked:(id)sender
    10 {
    11     NSString* url = @"http://www.......jpg";
    12     // 以传入的代码块作为执行体,创建NSOperation
    13     NSBlockOperation* operation = [NSBlockOperation
    14         blockOperationWithBlock:^{
    15             // 从网络获取数据
    16             NSData *data = [[NSData alloc]
    17                 initWithContentsOfURL:[NSURL URLWithString:url]];
    18             // 将网络数据初始化为UIImage对象
    19             UIImage *image = [[UIImage alloc]initWithData:data];
    20             if(image != nil)
    21             {
    22                 // 在主线程中执行updateUI:方法
    23                 [self performSelectorOnMainThread:@selector(updateUI:)
    24                     withObject:image waitUntilDone:YES];
    25             }
    26             else
    27             {
    28                 NSLog(@"---下载图片出现错误---");
    29             }
    30         }];
    31     // 将NSOperation添加给NSOperationQueue
    32     [queue addOperation:operation];
    33 }
    34 -(void)updateUI:(UIImage*) image
    35 {
    36     self.iv.image = image;
    37 }
     1 NSOperationQueue* queue;
     2 - (void)viewDidLoad
     3 {
     4     [super viewDidLoad];
     5     queue = [[NSOperationQueue alloc]init];
     6     // 设置该队列最多支持10条并发线程
     7     queue.maxConcurrentOperationCount = 10;
     8 }
     9 - (IBAction)clicked:(id)sender
    10 {
    11     NSString* url = @"http://www.......jpg";
    12     // 以self的downloadImageFromURL:方法作为执行体,创建NSOperation
    13     NSInvocationOperation* operation = [[NSInvocationOperation alloc]
    14         initWithTarget:self selector:@selector(downloadImageFromURL:)
    15         object:url];
    16     // 将NSOperation添加给NSOperationQueue
    17     [queue addOperation:operation];
    18 }
    19 
    20 // 定义一个方法作为线程执行体。
    21 -(void)downloadImageFromURL:(NSString *) url
    22 {
    23     // 从网络获取数据
    24     NSData *data = [[NSData alloc]
    25         initWithContentsOfURL:[NSURL URLWithString:url]];
    26     // 将网络数据初始化为UIImage对象
    27     UIImage *image = [[UIImage alloc]initWithData:data];
    28     if(image != nil)
    29     {
    30         // 在主线程中执行updateUI:方法
    31         [self performSelectorOnMainThread:@selector(updateUI:)
    32             withObject:image waitUntilDone:YES];
    33     }
    34     else
    35     {
    36         NSLog(@"---下载图片出现错误---");
    37     }
    38 }
    39 -(void)updateUI:(UIImage*) image
    40 {
    41     self.iv.image = image;
    42 }

    4、自定义NSOperation 的子类

    创建 NSOperation 的子类,需要重写一个方法:-(void) main,该方法的方法体将作为 NSOperationQueue 完成的任务

    下面自定义一个NSOperation 子类来实现下载图片的功能

    1 @interface MyDownImageOperation : NSOperation
    2 @property (nonatomic , strong) NSURL* url;
    3 @property (nonatomic , weak) UIImageView* imageView;
    4 - (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv;
    5 @end
     1 @implementation MyDownImageOperation
     2 - (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv
     3 {
     4     self = [super init];
     5     if (self) {
     6         _imageView = iv;
     7         _url = url;
     8     }
     9     return self;
    10 }
    11 // 重写main方法,该方法将作为线程执行体
    12 - (void)main
    13 {    
    14     // 从网络获取数据
    15     NSData *data = [[NSData alloc]
    16         initWithContentsOfURL:self.url];
    17     // 将网络数据初始化为UIImage对象
    18     UIImage *image = [[UIImage alloc]initWithData:data];
    19     if(image != nil)
    20     {
    21         // 在主线程中执行updateUI:方法
    22         [self performSelectorOnMainThread:@selector(updateUI:)
    23             withObject:image waitUntilDone:YES]; //
    24     }
    25     else
    26     {
    27         NSLog(@"---下载图片出现错误---");
    28     }
    29 }
    30 -(void)updateUI:(UIImage*) image
    31 {
    32     self.imageView.image = image;
    33 }

    viewController代码:

     1 NSOperationQueue* queue;
     2 - (void)viewDidLoad
     3 {
     4     [super viewDidLoad];
     5     queue = [[NSOperationQueue alloc]init];
     6     // 设置该队列最多支持10条并发线程
     7     queue.maxConcurrentOperationCount = 10;
     8 }
     9 - (IBAction)clicked:(id)sender
    10 {
    11     // 定义要加载的图片的URL
    12     NSURL* url = [NSURL URLWithString:@"http://www.crazyit.org/logo.jpg"];
    13     // 创建FKDownImageOperation对象
    14     MyDownImageOperation* operation = [[MyDownImageOperation alloc]
    15         initWithURL:url imageView:self.iv];
    16     // 将NSOperation的子类的实例提交给NSOperationQueue
    17     [queue addOperation:operation];
    18 }
  • 相关阅读:
    多项式插值取模哈希标记法
    AC自助机
    [OIBH] 糖果盒(Candy Box)——又一个最大子矩形
    windows phone 之ListBox数据绑定
    WP学习笔记
    为TextArea添加maxlength属性
    让整个网页(LOGO图片)色调全部变灰的方法(CSS写法)
    JS调试加断点
    Container.ItemIndex 获取到行的序号
    c# Invoke 与 BeginInvoke
  • 原文地址:https://www.cnblogs.com/chengzi/p/4536608.html
Copyright © 2011-2022 走看看