码迷,mamicode.com
首页 > 移动开发 > 详细

iOS 多线程的使用

时间:2017-05-24 19:16:13      阅读:281      评论:0      收藏:0      [点我收藏+]

标签:reads   后台   mac os   界面   安全   启动   super   成功   顺序   

 

iOS 多线程

     先看一篇阮一峰写关于进程和线程的文章,快速了解线程的相关概念。

      随着现在计算机硬件的发展,多核心、高频率的cpu越来越普及,为了充分发挥cpu的性能,在不通的环境下实现cpu的利用最大化,多线程技术在这个时候显得越发重要。同时,在程序中合理的使用多线程,可以让程序变得更加有效、靠谱。所以学习这一知识是一项有意义的事情。

     iOS中,只有主线程跟Cocoa关联,也即是在这个线程中,更新UI总是有效的,如果在其他线程中更新UI有时候会成功,但可能失败。所以苹果要求开发者在主线程中更新UI。但是如果我们吧所有的操作都放置在主线程中执行,当遇到比较耗时的操作的时候,势必会阻塞线程,出现界面卡顿的情况。这时候采取将耗时的操作放入后台线程中操作,且保持主线程只更新UI是我们推荐的做法。

     在iOS中,要实现多线程,一共有四种方式。  它们分别是:

  • pthreads         POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32[1]这篇文章不介绍
  • NSThread       需要管理线程的生命周期、同步、加锁问题,这会导致一定的性能开销
  • NSOperation & NSOperationQueue    
  • GCD               iOS4开始,苹果发布了GCD,可以自动对线程进行管理。极大的方便了多线程的开发使用

备注:本文中相关的代码在https://github.com/lufubinGit/Multithreading。 

一、pthreads 

     pthread是一套基于C的API,它不接受cocoa框架的控制:当手动创建pthread的时候,cocoa框架并不知道。 苹果不建议在cocoa中使用pthread,但是如果为了方便不得不使用,我们应该小心的使用。 

     下面这些方法可以创建pthread

OC
pthread_attr_t qosAttribute;
pthread_attr_init(&qosAttribute);
pthread_attr_set_qos_class_np(&qosAttribute, QOS_CLASS_UTILITY, 0);
pthread_create(&thread, &qosAttribute, f, NULL);
SWIFT var thread = pthread_t() var qosAttribute = pthread_attr_t() pthread_attr_init(&qosAttribute) pthread_attr_set_qos_class_np(&qosAttribute, QOS_CLASS_UTILITY, 0) pthread_create(&thread, &qosAttribute, f, nil)

    并且,可以使用下面的API对一个pthread进行修改。

    苹果的文档中,有一篇文档讲述了GCD中使用pthread的禁忌:Compatibility with POSIX Threads。

OBJECTIVE-C

pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND,0);
SWIFT

pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0)

 

二、NSThread

      对于NSThread,在使用的过程中,我们需要手动完成很多动作才能确保线程的顺利运行。但与此同时,它给我们带来更大的定制化空间。

 1.创建NSThread。

      对于NSThread的创建,苹果给出了三种使用方式。

detachNewThreadSelector(_:toTarget:with:)   detachNewThreadSelector会创建一个新的线程,并直接进入线程执行。

initWith(Target:selector:object:)          iOS10.0之前的创建方式,需要手动执行。                     

initWithBlock                                      iOS10.0之后,可以创建一个执行block的线程。

 

2.NSThread线程通信。

     如果我们想对已经存在的线程进行操作,可以使用 

 

performSelector:onThread:withObject:waitUntilDone:  

 

   跳转到目标线程执行,实现线程间跳转,达到线程通信的目的。但是需要注意的是这个方法不适合频繁的进行通信,尤其是对于一些敏感的操作

 3.NSThread线程的状态。

    在一个线程中,可以通过相关的函数获取到它的当前状态。

+ isMainThread:判断当前线程是不是主线程。
+ mainThread:获取当前的主线程。
+ isMultiThreaded :判断当前环境是不是多线程环境   
+ threadDictionary :获取包含项目中的线程的字典
@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);     是否处于运行状态
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);          是否处于完成状态
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);      是否处于取消状态

 

4.NSThread线程的优先级。

     可通过给NSThread设置优先级。以便让开发者更灵活的控制程序的执行。

+ threadPriority  Returns the current thread’s priority. 返回当前线程的优先级别
threadPriority        The receiver’s priority  消息发送者的优先级,这个发送者是一个NSThread对象
+ setThreadPriority:     Sets the current thread’s priority. 设置线程的优先级

 

5.停止线程/终止线程

+ sleepUntilDate:   Blocks the current thread until the time specified.  直到某时刻执行
+ sleepForTimeInterval:     Sleeps the thread for a given time interval.   暂停线程
+ exit    Terminates the current thread.  关闭线程,这里调用之前,为了确保程序的安全,我们应在明确线程的状态是isFinished 和 isCancelled的时候执行。
- cancel    Changes the cancelled state of the receiver to indicate that it should exit.  主动进入取消状态,如果当时线程没有完成,会继续执行完成。

 

6.使用NSThread

- (void)viewDidLoad {
    [super viewDidLoad];
    _testCount = 100;
    _t1 =  [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
    _t1.name = @"线程一";
    [_t1 start];
    NSInvocationOperation
}

-(void)test{
    for (int i = 0; i < 5 ; i++) {
        [NSThread sleepForTimeInterval:0.05];
        NSLog(@"%ld,%@",(long)_testCount--,[[NSThread currentThread] name]);
    }

}

 

三、NSOperation & NSOperationQueue   

 1.NSOperation

      NSOperation是对于线程对象的抽象封装,不会被直接使用,在日常的开发中,会使用它的两个子类:NSInvocationOperation 和 NSBlockOperationNSInvocationOperation类是NSOperation的具体子类,用于管理指定为调用的单个封装任务的执行。 您可以使用此类来启动包含在指定对象上调用选择器的操作。 此类实现非并发操作。NSBlockOperation类也是NSOperation的具体子类,用于管理一个或多个block块的并发执行。 您可以使用此对象一次执行多个block,而无需为每个块创建单独的操作对象。 当执行多个程序段时,只有当所有程序段执行完毕时,才会将操作本身完成。

    NSInvocationOperation实现非并发操作。

   _invCationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test2:) object:nil];
    _invCationOp.name = @"invocation线程";
    [_invCationOp start];

打印:

    <NSThread: 0x608000076fc0>{number = 1, name = main},

    <NSThread: 0x608000076fc0>{number = 1, name = main}

    从打印结果可以看出,NSInvocationOperation实现的是非并法的操作,至于在哪个线程中操作,取决于start的当前调用时的线程。

    如果我们需要创建一个并发的Queue,可以使用NSBlockOperation。如果我们像这样创建:

- (void)blockOperation{
    NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@,%@",[NSThread currentThread],[NSThread mainThread]);
    }];
    
    [blockOp addExecutionBlock:^{

        NSLog(@"%@,%@",[NSThread currentThread],[NSThread mainThread]);
    }];
    [blockOp addExecutionBlock:^{
        NSLog(@"%@,%@",[NSThread currentThread],[NSThread mainThread]);
    }];
    
    [blockOp start];
}

打印: 

    2017-05-23 15:11:00.289 多线程[5377:589808] <NSThread: 0x60000006fec0>{number = 1, name = main},<NSThread: 0x60000006fec0>{number = 1, name = main}

    2017-05-23 15:11:00.289 多线程[5377:589808] <NSThread: 0x60000006fec0>{number = 1, name = main},<NSThread: 0x60000006fec0>{number = 1, name = main}

    2017-05-23 15:11:00.289 多线程[5377:589808] <NSThread: 0x60000006fec0>{number = 1, name = main},<NSThread: 0x60000006fec0>{number = 1, name =  main}

    显然,这里都在主线程中执行,不能证明NSBlockOperation具有并发的能力,这是因为,每个NSBlockOperation对象的会优先在主线程中执行。如果主线程受到阻塞的时候才会开辟另一个线程去执行其他的操作。比如向下面这样:

 1 //NSBlockOperation
 2 - (void)blockOperation{
 3     NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
 4         NSLog(@"%@,%@",[NSThread currentThread],[NSThread mainThread]);
 5     }];
 6     
 7     [blockOp addExecutionBlock:^{
 8         [NSThread sleepForTimeInterval:2.0];
 9 
10         NSLog(@"%@,%@",[NSThread currentThread],[NSThread mainThread]);
11     }];
12     [blockOp addExecutionBlock:^{
13         [NSThread sleepForTimeInterval:2.0];
14         NSLog(@"%@,%@",[NSThread currentThread],[NSThread mainThread]);
15     }];
16     
17    
18     [blockOp start];
19 }

 打印:

    2017-05-23 15:28:37.780 多线程[5645:617976] <NSThread: 0x60800006f700>{number = 1, name = main},<NSThread: 0x60800006f700>{number = 1, name = main}

    2017-05-23 15:28:39.848 多线程[5645:618027] <NSThread: 0x60800007bf80>{number = 3, name = (null)},<NSThread: 0x60800006f700>{number = 1, name = (null)}

    2017-05-23 15:28:39.848 多线程[5645:618028] <NSThread: 0x60800007bfc0>{number = 4, name = (null)},<NSThread: 0x60800006f700>{number = 1, name = (null)}=

    这里就是异步执行了。 NSBlockOperation在使用的过程中,会针对主线程当前的使用情况,选择性的创建其他的线程。在提升流畅度的同时,还节约了资源。

  2.NSOperationQueue

      NSOperationQueue:手动管理异步执行。  如果我们想使用并发,并且要作到精确掌握并发的线程。可以使用NSOperationQueue。这是一个操作队列,如果将NSOperation的具体子类对象添加进来的时候,开启之后,所有的对象没有先后,会异步执行各自的代码。

- (void)operationQueue{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%ld,%@,%@",(long)_testCount--,[NSThread currentThread],[NSThread mainThread]);
    }];

    NSInvocationOperation *invCationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test2:) object:nil];
    invCationOp.name = @"invocation线程";
    [queue addOperation:invCationOp];
    [queue addOperation:blockOp];

}
 

  打印:

    2017-05-23 15:38:45.110 多线程[5772:633972] 100,<NSThread: 0x6080000773c0>{number = 3, name = (null)},<NSThread: 0x60000006bc80>{number = 1, name = (null)}

    2017-05-23 15:38:45.203 多线程[5772:633975] 99,<NSThread: 0x608000078800>{number = 4, name = (null)},<NSThread: 0x60000006bc80>{number = 1, name = (null)}

   在NSOperationQueue中,正常情况下,所有的operation的执行次序是随机,如果我们想要某个operation被率先执行,可以将这个operation的优先级调高。对于优先级有以下的选择:

 [invCationOp setQueuePriority:NSOperationQueuePriorityVeryHigh];

      对于优先级有以下的选择:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,   最低
    NSOperationQueuePriorityLow = -4L,       次低
    NSOperationQueuePriorityNormal = 0,      普通  不做任何操作的operation的优先级是这个
    NSOperationQueuePriorityHigh = 4,        次高
    NSOperationQueuePriorityVeryHigh = 8     最高
};

      当然,如果有很多operation,使用的优先级不能满足的时候,还可以设置 operation的依赖关系。 设置依赖之后,将会先执行依赖对象。 

 [invCationOp addDependency:blockOp];

    值得注意的是:优先级和依赖关系不是冲突的。 优先级的选择会在依赖关系下发生效果,也就是,在依赖关系成立的情况下,优先级的才会有效。

 

三、GCD  -  Grand Central Dispatch

     Grand Central Dispatch早在Mac OS X 10.6雪豹上就已经存在了。后在iOS4.0的时候被引入。Grand Central Dispatch是OS X中的一个低级框架,用于管理整个操作系统中的并发和异步执行任务。本质上,随着处理器核心的可用性,任务排队等待执行。通过允许系统控制对任务的线程分配,GCD更有效地使用资源,这有助于系统和应用程序运行更快,高效和响应。

     GCD的一个重要的对象是队列:Dispatch Queue。跟Operationqueue类似,通过将Operation加入到队列中,执行相应的单元。在GCD中,大量采用了block的形式创建类似的operation。

  1. Dispatch Queue  创建

      Dispatch Queue 分为两类,主要是根据并行和串行来区分:

      a. Serial Dispatch Queue: 线性执行的线程队列,遵循FIFO(First In First Out)原则; 又叫private dispatch queues,同时只执行一个任务。Serial queue常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然各自是同步,但serial queue之间是并发执行。 main dipatch属于这个类别。

  b. Concurrent Dispatch Queue: 并发执行的线程队列,并发执行的处理数取决于当前状态。又叫global dispatch queue,可以并发的执行多个任务,但执行完成顺序是随机的。系统提供四个全局并发队列,这四个队列有这对应的优先级,用户是不能够创建全局队列的,只能获取。

      我们可以自定义队列,默认创建的队列是串行的,但是也可以指定创建一个并行的队列:

//串行队列
dispatch_queue_create("com.deafultqueue", 0)
//串行队列
dispatch_queue_create("com.serialqueue", DISPATCH_QUEUE_SERIAL)
//并行队列
dispatch_queue_create("com.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)

       除了自定义队列,系统其实也为有一些已经公开的队列。这些队列不需要我们显示的创建,只能通过获取的方式得到:

dispatch_get_main_queue()   获取当前的APP主队列,这个队列在主线程中,通常我们调用它进行界面的刷新。

dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)   获取全局的Concurrent队列,这里苹果提供了四种不同的优先级,

         #define DISPATCH_QUEUE_PRIORITY_HIGH 2

        #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0

        #define DISPATCH_QUEUE_PRIORITY_LOW (-2)

        #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

也即时有四个不同的并行队列。

2. Dispatch Queue  执行

     GCD的队列有串行和并行两种队列,同时我们可以同步和异步两种方式执行队列,所以最多有四种不同的场景。

     (1)串行同步。 凡涉及到同步的的都会阻塞线程。 UI线程—也即是我们的所说的主线程默认情况下其实就是执行同步的。这个时候如果有一些耗时间的操作,则会出现卡顿的现象。这种方式大部分情况用于能快速响应和后台线程的耗时场景中。

  //串行同步
    dispatch_queue_t  serialQ = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);  //创建一个串行队列
    NSLog(@"%@",[NSThread currentThread].description);
    
    dispatch_sync(serialQ, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"%@ --  %@队列",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(serialQ)]);
    });
    dispatch_sync(serialQ, ^{
        NSLog(@"%@ --  %@队列",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(serialQ)]);
    });   

 

     (2)串行异步。 这种情况下,GCD会开辟另一个新的线程,让队列中的内容在新的线程中按顺序执行。

 //串行异步
    dispatch_async(serialQ, ^{
        NSLog(@"%@ 1-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(serialQ)]);
    });
    dispatch_async(serialQ, ^{
        NSLog(@"%@ 2-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(serialQ)]);
    });
    dispatch_async(serialQ, ^{
        NSLog(@"%@ 3-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(serialQ)]);
    });
    dispatch_async(serialQ, ^{
        NSLog(@"%@ 4-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(serialQ)]);
    });

 

     (3)并行同步。 因为是同步执行,所以实际上这里的并行是没有意义的。 依然在当前的线程中按顺序执行,并阻塞。

  dispatch_queue_t  conCurrentQ = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);  //创建一个并行行队列
    //并行同步
    dispatch_sync(conCurrentQ, ^{
        [NSThread sleepForTimeInterval:0.2];
        NSLog(@"%@ 1-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(conCurrentQ)] );
    });
    dispatch_sync(conCurrentQ, ^{
        [NSThread sleepForTimeInterval:0.2];
        NSLog(@"%@ 2-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(conCurrentQ)] );
    });
    dispatch_sync(conCurrentQ, ^{
        [NSThread sleepForTimeInterval:0.2];
        NSLog(@"%@ 3-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(conCurrentQ)] );
    });

 

     (4)并行异步。 并行异步将极大的利用资源。首先会开辟新的线程,并且,当所有线程备占用的情况下,会继续开辟(如果没有限制的话)。所以这里还涉及线程的最大值的问题。

 //并行异步
    dispatch_async(conCurrentQ, ^{
        NSLog(@"%@ 1-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(conCurrentQ)] );
    });
    dispatch_async(conCurrentQ, ^{
        NSLog(@"%@ 2-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(conCurrentQ)] );
    });
    dispatch_async(conCurrentQ, ^{
        NSLog(@"%@ 3-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(conCurrentQ)] );
    });
    dispatch_async(conCurrentQ, ^{
        NSLog(@"%@ 4-- 队列:%@",[NSThread currentThread].description,[NSString stringWithUTF8String:dispatch_queue_get_label(conCurrentQ)] );
    }); 

 

3. Dispatch Queue  暂停和继续

     我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。  

4. Dispatch Queue  的销毁

     每个队列在执行完添加到其中的所有的block事件的时候,在ARC模式下,会被自动销毁。 但是在手动管理内存的时候,我们需要调用 

     dispatch_release(queue);

     来释放。    

5.队列组  Dispatch Group (这些内容来自http://blog.csdn.net/q199109106q/article/details/8566300

     多数情况下,我们可能会遇到这种问题: 对一个页面中的多张图片,每张图片要单独的进行网络请求,我们没有办法保证每次的请求时间是一样的,但是项目经理说必须要在获取所有的图片的情况下,才可以进行对页面的刷新。这里有个很好例子可以解决这个问题。

// 根据url获取UIImage  
- (UIImage *)imageWithURLString:(NSString *)urlString {  
    NSURL *url = [NSURL URLWithString:urlString];  
    NSData *data = [NSData dataWithContentsOfURL:url];  
    return [UIImage imageWithData:data];  
}  
  
- (void)downloadImages {  
    // 异步下载图片  
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
        // 下载第一张图片  
        NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";  
        UIImage *image1 = [self imageWithURLString:url1];  
          
        // 下载第二张图片  
        NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";  
        UIImage *image2 = [self imageWithURLString:url2];  
          
        // 回到主线程显示图片  
        dispatch_async(dispatch_get_main_queue(), ^{  
            self.imageView1.image = image1;  
              
            self.imageView2.image = image2;  
        });  
    });  
}  

      但是我们发现,事实上,图片一和 二两者在请求的过程中是完全独立的, 但是这里明显的,图片一的下载将阻塞,直到下载完才会开始图片二的下载。这种方式毕竟还是有瑕疵的啊哈。

      Dispatch Group可以帮助解决这个问题。 它是Dispatch Queue的组合,被加入到group的queue会在组内其他的queue也执行完操作的时候,有group统一调用预设好的一个block。最重要的是,在group中的内容是可以异步执行的。也即是多个队列在不同的线程执行。 如果图片大小差不多的话,这种方式将节省我们不一半的时间。  我们来看看这个模型。         

 //dispaach group
    dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue1, ^{
        [NSThread sleepForTimeInterval:5.0];
        NSLog(@"第一个项目执行完成。");
    });

    dispatch_group_async(group, queue2, ^{
        [NSThread sleepForTimeInterval:10.0];
        NSLog(@"第二个项目执行完成。");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"集体回调");
    });

 

6.GCD的其他的用法 

     (1)控制一段代码只执行一次。用在创建单例的时候再好不过了。

 //控制代码只执行一次数
    for(int i = 1 ;i <= 10 ;i++){
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"被执行 %d次",i);
        });
    }

        打印结果:2017-05-24 18:15:17.704 多线程[10542:898532] 被执行 1次  

       (2) 只能控制执行一次是不是有点不够完美 。dispatch_apply 可以让你控制一段代码执行任意多次。这里的执行是异步执行的,如果为了确保顺序执行,应该对执行的内容进行加锁。

   //控制执行任意多次
    dispatch_queue_t queueX = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    __block int count = 0;
    NSLock *lock = [[NSLock alloc]init];
    dispatch_apply(5, queueX, ^(size_t index) {
        [lock lock];
        NSLog(@"%d,%zu",count++,index);
        [lock unlock];
    }); 

    (3)做一个block式的延时。 除了使用performSelector之外,我们还以使用dispatch_after进行延时,并且是以block的形式进行。

  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"五秒钟之后执行的代码。");
    });

 

 

 

相关参考:

http://blog.csdn.net/feisongfeiqin/article/details/50282273

http://blog.csdn.net/q199109106q/article/details/8566300

iOS 多线程的使用

标签:reads   后台   mac os   界面   安全   启动   super   成功   顺序   

原文地址:http://www.cnblogs.com/FBiOSBlog/p/6864042.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!