關于iOS多線程

看了簡書里伯恩的遺產大大的文章關于iOS多線程晃痴,你看我就夠了,感覺很不錯泣侮,自己做下筆記活尊。筆記中的代碼只用了OC漏益,更多詳細內容請看原文遭庶。

在 iOS 中其實目前有4套多線程方案,他們分別是:

Pthreads
NSThread
GCD
NSOperation&NSOperationQueue

Pthreads

簡單地說翎苫,這是一套在很多操作系統(tǒng)上都通用的多線程API煎谍,所以移植性很強(然并卵)龙屉,當然在 iOS 中也是可以的。不過這是基于c語言的框架唆垃,使用起來這酸爽痘儡!感受一下:

當然第一步要包含頭文件

/ #import<pthread.h>

然后創(chuàng)建線程沉删,并執(zhí)行任務

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
    
    pthread_t thread;//創(chuàng)建一個線程并自動執(zhí)行
    
    pthread_create(&thread,NULL, start,NULL);
    
}

Pthreads需要 c語言函數(shù)矾瑰,而且需要手動處理線程的各個狀態(tài)的轉換即管理生命周期,比如渔彰,這段代碼雖然創(chuàng)建了一個線程推正,但并沒有銷毀植榕。
Pthreads只寫這么多了尊残,因為它在iOS開發(fā)幾乎用不到。

很遺憾顷扩,在我目前的 swift1.2中無法執(zhí)行這套方法隘截,原因是這個函數(shù)需要傳入一個函數(shù)指針 CFunctionPointer<T>
類型汹胃,但是目前 swift 無法將方法轉換成此類型着饥。聽說 swift 2.0
引入一個新特性 @convention(c)
, 可以完成 Swift 方法轉換成 c 語言指針的。在這里可以看到
(PS:這部分我還沒搞懂呵哨,以后搞懂會更新出來)

NSThread

這套方案是經過蘋果封裝后的孟害,完全面向對象纹坐。你可以直接操控線程對象舞丛,非常直觀和方便球切。但是,它的生命周期還是需要我們手動管理捍歪,所以這套方案也是偶爾用用糙臼,比如 [NSThread currentThread]恩商,它可以獲取當前線程類怠堪,你就可以知道當前線程的各種屬性,用于調試十分方便凰棉。下面來看看它的一些用法撒犀。

創(chuàng)建并啟動

  • 先創(chuàng)建線程類绘证,再啟動
// 創(chuàng)建
  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];

  // 啟動
  [thread start];
  • 創(chuàng)建并自動啟動
 [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
  • 使用NSObject方法創(chuàng)建比自動啟動
[self performSelectorInBackground:@selector(run:) withObject:nil];

很遺憾 too! 蘋果認為 performSelector: 不安全嚷那,所以在 Swift 去掉了這個方法杆煞。

Note: The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.

其他方法

除了創(chuàng)建啟動外,NSThread 還以很多方法派桩,下面我列舉一些常見的方法蚌斩,當然我列舉的并不完整送膳,更多方法大家可以去類的定義里去看叠聋。

//取消線程
- (void)cancel;

//啟動線程
- (void)start;

//判斷某個線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//設置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//獲取當前線程信息
+ (NSThread *)currentThread;

//獲取主線程信息
+ (NSThread *)mainThread;

//使當前線程暫停一段時間,或者暫停到某個時刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

NSThread 用起來也挺簡單的虏束,因為它就那幾種方法镇匀。同時汗侵,我們也只有在一些非常簡單的場景才會用 NSThread, 畢竟它還不夠智能囊骤,不能優(yōu)雅地處理多線程中的其他高級概念也物。所以接下來要說的內容才是重點滑蚯。

GCD

Grand Central Dispatch。它是蘋果為多核的并行運算提出的解決方案坤次,所以會自動合理地利用更多的CPU內核(比如雙核缰猴、四核)滑绒,最重要的是它會自動管理線程的生命周期(創(chuàng)建線程、調度任務杠览、銷毀線程)踱阿,完全不需要我們管理钦铁,我們只需要告訴干什么就行育瓜。同時它使用的也是 c語言躏仇,不過由于使用了 Block(Swift里叫做閉包)焰手,使得使用起來更加方便怀喉,而且靈活躬拢。所以基本上大家都使用 GCD 這套方案聊闯。

任務和隊列

GCD 中,加入了兩個非常重要的概念:任務隊列篷帅。

  • 任務:即操作魏身,你想要干什么箭昵,說白了就是一段代碼回季,在 GCD 中就是一個 Block,所以添加任務十分方便卓囚。任務有兩種執(zhí)行方式: 同步執(zhí)行(sync) 和 異步執(zhí)行(async)哪亿,他們之間的區(qū)別是會不會阻塞當前線程贤笆,直到 Block 中的任務執(zhí)行完畢芥永。
    如果是 同步(sync) 操作埋涧,它會阻塞當前線程并等待 Block 中的任務執(zhí)行完畢棘催,然后當前線程才會繼續(xù)往下運行。
    如果是 異步(async)操作邑跪,當前線程會直接往下執(zhí)行画畅,它不會阻塞當前線程轴踱。

  • 隊列:用于存放任務寇僧。一共有兩種隊列沸版, 串行隊列并行隊列视粮。

串行隊列:放到串行隊列的任務,GCDFIFO(先進先出) 地取出來一個岛啸,執(zhí)行一個坚踩,然后取下一個瓤狐,這樣一個一個的執(zhí)行础锐。

并行隊列:放到并行隊列的任務皆警,GCD 也會 FIFO的取出來信姓,但不同的是财破,它取出來一個就會放到別的線程从诲,然后再取出來一個又放到另一個的線程系洛。這樣由于取的動作很快描扯,忽略不計绽诚,看起來,所有的任務都是一起執(zhí)行的卒落。不過需要注意儡毕,GCD 會根據系統(tǒng)資源控制并行的數(shù)量腰湾,所以如果任務很多费坊,它并不會讓所有任務同時執(zhí)行附井。

雖然很繞羡忘,但請看下表:

同步執(zhí)行 異步執(zhí)行
串行隊列 當前線程卷雕,一個一個執(zhí)行 其他線程漫雕,一個一個執(zhí)行
并行隊列 當前線程浸间,一個一個執(zhí)行 開很多線程,一起執(zhí)行

創(chuàng)建隊列

  • 主隊列:這是一個特殊的串行隊列囊扳。它用于刷新 UI锥咸,任何需要刷新 UI 的工作都要在主隊列執(zhí)行搏予,所以一般耗時的任務都要放到別的線程執(zhí)行雪侥。
//OBJECTIVE-C
  dispatch_queue_t queue = ispatch_get_main_queue();

//SWIFT
  let queue = ispatch_get_main_queue()
  • 自己創(chuàng)建的隊列:自己可以創(chuàng)建串行隊列, 也可以創(chuàng)建并行隊列速缨。 它有兩個參數(shù)鸟廓,其中第一個參數(shù)是標識符引谜,用于 DEBUG 的時候標識唯一的隊列员咽,可以為空贝室。大家可以看xcode的文檔查看參數(shù)意義滑频。第二個才是最重要的峡迷。第二個參數(shù)用來表示創(chuàng)建的隊列是串行的還是并行的绘搞,傳入 DISPATCH_QUEUE_SERIALNULL 表示創(chuàng)建串行隊列夯辖。傳入DISPATCH_QUEUE_CONCURRENT表示創(chuàng)建并行隊列蒿褂。
//串行隊列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
  //并行隊列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
  • 全局并行隊列:并行任務一般都加入到這個隊列贮缅。這是系統(tǒng)提供的一個并發(fā)隊列。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

創(chuàng)建任務

  • 同步任務:會阻塞當前進程(sync
 dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });
  • 異步任務:不會阻塞當前進程(async)
 dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

NOTE:
為了更好的理解同步和異步齿坷,和各種隊列的使用永淌,下面看兩個示例:

示例一:
以下代碼在主線程調用遂蛀,結果是什么?

NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
NSLog("sync - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())

答案:
只會打印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main} 蛮瞄,然后主線程就卡死了挂捅,你可以在界面上放一個按鈕闲先,你就會發(fā)現(xiàn)點不了了。
解釋:
同步任務會阻塞當前線程无蜂,然后把 Block 中的任務放到指定的隊列中執(zhí)行伺糠,只有等到 Block 中的任務完成后才會讓當前線程繼續(xù)往下運行。
那么這里的步驟就是:打印完第一句后斥季,dispatch_sync 立即阻塞當前的主線程退盯,然后把 Block 中的任務放到 main_queue 中,可是 main_queue 中的任務會被取出來放到主線程中執(zhí)行泻肯,但主線程這個時候已經被阻塞了渊迁,所以 Block 中的任務就不能完成灶挟,它不完成琉朽,dispatch_sync 就會一直阻塞主線程,這就是死鎖現(xiàn)象稚铣。導致主線程一直卡死箱叁。

>示例二:
以下代碼會產生什么結果?

>```
let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)
  NSLog("之前 - %@", NSThread.currentThread())
    dispatch_async(queue, { () -> Void in
        NSLog("sync之前 - %@", NSThread.currentThread())
        dispatch_sync(queue, { () -> Void in
             NSLog("sync - %@", NSThread.currentThread())
        })
        NSLog("sync之后 - %@", NSThread.currentThread())
   })
NSLog("之后 - %@", NSThread.currentThread())

答案:
2015-07-30 02:06:51.058 test[33329:8793087] 之前 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
2015-07-30 02:06:51.059 test[33329:8793356] sync之前 - <NSThread: 0x7fe32062e9f0>{number = 2, name = (null)}
2015-07-30 02:06:51.059 test[33329:8793087] 之后 - <NSThread: 0x7fe32050dbb0>{number = 1, name = main}
很明顯 sync - %@ 和 sync之后 - %@ 沒有被打印出來惕医!這是為什么呢耕漱?我們再來分析一下:

分析:
我們按執(zhí)行順序一步步來哦:

使用 DISPATCH_QUEUE_SERIAL 這個參數(shù),創(chuàng)建了一個 串行隊列抬伺。
打印出 之前 - %@ 這句螟够。
dispatch_async 異步執(zhí)行,所以當前線程不會被阻塞峡钓,于是有了兩條線程妓笙,一條當前線程繼續(xù)往下打印出 之后 - %@這句, 另一臺執(zhí)行 Block 中的內容打印 sync之前 - %@ 這句。因為這兩條是并行的能岩,所以打印的先后順序無所謂寞宫。
注意,高潮來了±椋現(xiàn)在的情況和上一個例子一樣了辈赋。dispatch_sync同步執(zhí)行鲫忍,于是它所在的線程會被阻塞,一直等到 sync 里的任務執(zhí)行完才會繼續(xù)往下钥屈。于是 sync 就高興的把自己 Block 中的任務放到 queue 中悟民,可誰想 queue 是一個串行隊列,一次執(zhí)行一個任務焕蹄,所以 sync 的 Block 必須等到前一個任務執(zhí)行完畢逾雄,可萬萬沒想到的是 queue 正在執(zhí)行的任務就是被 sync 阻塞了的那個。于是又發(fā)生了死鎖腻脏。所以 sync 所在的線程被卡死了鸦泳。剩下的兩句代碼自然不會打印。

隊列組

隊列組可以將很多隊列添加到一個組里永品,這樣做的好處是做鹰,當這個組里所有的任務都執(zhí)行完了,隊列組會通過一個方法通知我們鼎姐。下面是使用方法钾麸,這是一個很實用的功能。

//1.創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//3.多次使用隊列組的方法執(zhí)行任務, 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"group-01 - %@", [NSThread currentThread]);
    }
});

//3.2.主隊列執(zhí)行8次循環(huán)
dispatch_group_async(group, dispatch_get_main_queue(), ^{
    for (NSInteger i = 0; i < 8; i++) {
        NSLog(@"group-02 - %@", [NSThread currentThread]);
    }
});

//3.3.執(zhí)行5次循環(huán)
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"group-03 - %@", [NSThread currentThread]);
    }
});

//4.都完成后會自動通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"完成 - %@", [NSThread currentThread]);
});

打印結果

2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.277 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.277 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319271] group-03 - <NSThread: 0x7f9772536f00>{number = 3, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}

2015-07-28 03:40:34.278 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.278 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number = 2, name = (null)}

2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

2015-07-28 03:40:34.279 test[12540:3319146] 完成 - <NSThread: 0x7f977240ba60>{number = 1, name = main}

這些就是 GCD 的基本功能炕桨,但是它的能力遠不止這些饭尝,等講完 NSOperation 后,我們再來看看它的一些其他方面用途献宫。而且钥平,只要你想象力夠豐富,你可以組合出更好的用法姊途。

dispatch_semaphore(信號量)

信號量:就是一種可用來控制訪問資源的數(shù)量的標識(比如3個線程爭搶2個資源)涉瘾,設定了一個信號量(2個資源),在線程訪問之前捷兰,加上信號量的處理(加/減資源)立叛,則可告知系統(tǒng)按照我們指定的信號量數(shù)量來執(zhí)行多個線程。

其實贡茅,這有點類似鎖機制了秘蛇,只不過信號量都是系統(tǒng)幫助我們處理了,我們只需要在執(zhí)行線程之前友扰,設定一個信號量值彤叉,并且在使用時,加上信號量處理方法就行了村怪。

信號量的3個主要函數(shù)

//創(chuàng)建信號量,參數(shù):信號量的初值浮庐,如果小于0則會返回NULL
dispatch_semaphore_create(信號量值)
 
//等待降低信號量
dispatch_semaphore_wait(信號量甚负,等待時間)
 
//提高信號量
dispatch_semaphore_signal(信號量)

**NOTE:正常的使用順序是先降低然后再提高柬焕,這兩個函數(shù)通常成對使用∷笥颍“呔佟(具體可參考下面的代碼示例)

舉個??:3個線程搶2個資源

-(void)dispatchSignal{
    //crate的value表示,最多幾個資源可訪問
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);   
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     
    //任務1
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);       
    });<br>
    //任務2
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);       
    });<br>
    //任務3
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);       
    });   
}

執(zhí)行結果:

image.png

以上信號量部分來源于iOS GCD中級篇 - dispatch_semaphore(信號量)的理解及使用

NOTE:關于GCD病涨,還有兩個需要說的:

  • func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
    這個方法重點是你傳入的 queue富玷,當你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數(shù)自己創(chuàng)建的 queue 時,這個方法會阻塞這個 queue(注意是阻塞 queue 既穆,而不是阻塞當前線程)赎懦,一直等到這個 queue 中排在它前面的任務都執(zhí)行完成后才會開始執(zhí)行自己,自己執(zhí)行完畢后幻工,再會取消阻塞励两,使這個 queue 中排在它后面的任務繼續(xù)執(zhí)行。
    如果你傳入的是其他的 queue, 那么它就和 dispatch_async 一樣了囊颅。
  • func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
    這個方法的使用和上一個一樣当悔,傳入 自定義的并發(fā)隊列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法一樣的阻塞 queue踢代,不同的是 這個方法還會 阻塞當前線程盲憎。
    如果你傳入的是其他的 queue, 那么它就和 dispatch_sync 一樣了。

NSOperation和NSOperationQueue

NSOperation 是蘋果公司對GCD的封裝胳挎,完全面向對象饼疙,所以使用起來更好理解。 大家可以看到NSOperationNSOperationQueue分別對應GCD任務隊列串远。操作步驟也很好理解:

  1. 將要執(zhí)行的任務封裝到一個NSOperation對象中宏多。
  2. 將此任務添加到一個NSOperationQueue對象中。

然后系統(tǒng)就會自動在執(zhí)行任務澡罚。至于同步還是異步伸但、串行還是并行請繼續(xù)往下看:

添加任務

值得說明的是,NSOperation只是一個抽象類留搔,所以不能封裝任務更胖。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperationNSBlockOperation隔显。創(chuàng)建一個Operation后却妨,需要調用start方法來啟動任務,它會默認在當前隊列同步執(zhí)行括眠。當然你也可以在中途取消一個任務彪标,只需要調用其cancel方法即可。

  • NSInvocationOperation : 需要傳入一個方法名掷豺。
//1.創(chuàng)建NSInvocationOperation對象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

  //2.開始執(zhí)行
  [operation start];

NOTE:NSInvocationOperation不是類型安全的,swift已棄用捞烟。蘋果如是說薄声。這里有相關解釋

  • NSBlockOperation
 //1.創(chuàng)建NSBlockOperation對象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

  //2.開始任務
  [operation start];

之前說過這樣的任務,默認會在當前線程執(zhí)行题画。但是NSBlockOperation還有一個方法:addExecutionBlock:默辨,通過這個方法可以給Operation添加多個執(zhí)行Block。這樣 Operation 中的任務會并發(fā)執(zhí)行苍息,它會在主線程和其它的多個線程執(zhí)行這些任務缩幸,注意下面的打印結果:

 //1.創(chuàng)建NSBlockOperation對象
      NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
          NSLog(@"%@", [NSThread currentThread]);
      }];

      //添加多個Block
      for (NSInteger i = 0; i < 5; i++) {
          [operation addExecutionBlock:^{
              NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
          }];
      }

      //2.開始任務
      [operation start];

打印輸出

2015-07-28 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

2015-07-28 17:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

2015-07-28 17:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3, name = (null)}

2015-07-28 17:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}

2015-07-28 17:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

2015-07-28 17:50:16.586 test[17527:4095467] 第4次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

NOTE:addExecutionBlock方法必須在start()方法之前執(zhí)行,否則就會報錯:

NOTE:大家可能發(fā)現(xiàn)了一個問題竞思,為什么我在 Swift 里打印輸出使用NSLog()而不是println()呢表谊?原因是使用print() / println()輸出的話,它會簡單地使用 流(stream) 的概念衙四,學過 C++ 的都知道铃肯。它會把需要輸出的每個字符一個一個的輸出到控制臺。普通使用并沒有問題传蹈,可是當多線程同步輸出的時候問題就來了押逼,由于很多println()同時打印,就會導致控制臺上的字符混亂的堆在一起惦界,而NSLog()就沒有這個問題挑格。到底是什么樣子的呢?你可以把上面NSLog()改為println()沾歪,然后一試便知漂彤。 更多 NSLog() 與 println() 的區(qū)別看這里

  • 自定義Operation

除了上面的兩種 Operation 以外,我們還可以自定義 Operation灾搏。自定義 Operation 需要繼承NSOperation類挫望,并實現(xiàn)其main()方法,因為在調用start()方法的時候狂窑,內部會調用main()方法完成相關邏輯媳板。所以如果以上的兩個類無法滿足你的欲望的時候,你就需要自定義了泉哈。你想要實現(xiàn)什么功能都可以寫在里面蛉幸。除此之外,你還需要實現(xiàn)cancel()在內的各種方法丛晦。所以這個功能提供給高級玩家奕纫,我在這里就不說了,等我需要用到時在研究它烫沙,到時候可能會再做更新匹层。

創(chuàng)建隊列

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锌蓄,隨后出現(xiàn)的幾起案子又固,更是在濱河造成了極大的恐慌仲器,老刑警劉巖煤率,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仰冠,死亡現(xiàn)場離奇詭異,居然都是意外死亡蝶糯,警方通過查閱死者的電腦和手機洋只,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昼捍,“玉大人识虚,你說我怎么就攤上這事《什纾” “怎么了担锤?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乍钻。 經常有香客問我肛循,道長,這世上最難降的妖魔是什么银择? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任多糠,我火速辦了婚禮,結果婚禮上浩考,老公的妹妹穿的比我還像新娘夹孔。我一直安慰自己,他們只是感情好析孽,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布搭伤。 她就那樣靜靜地躺著,像睡著了一般袜瞬。 火紅的嫁衣襯著肌膚如雪怜俐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天吞滞,我揣著相機與錄音佑菩,去河邊找鬼。 笑死裁赠,一個胖子當著我的面吹牛殿漠,可吹牛的內容都是我干的。 我是一名探鬼主播佩捞,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼绞幌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了一忱?” 一聲冷哼從身側響起莲蜘,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤谭确,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后票渠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逐哈,經...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年问顷,在試婚紗的時候發(fā)現(xiàn)自己被綠了昂秃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡杜窄,死狀恐怖肠骆,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情塞耕,我是刑警寧澤蚀腿,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站扫外,受9級特大地震影響莉钙,放射性物質發(fā)生泄漏。R本人自食惡果不足惜畏浆,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一胆胰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刻获,春花似錦蜀涨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沐兵,卻和暖如春别垮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扎谎。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工碳想, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毁靶。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓胧奔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親预吆。 傳聞我的和親對象是個殘疾皇子龙填,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內容