看了簡書里伯恩的遺產大大的文章關于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í)行画畅,它不會阻塞當前線程轴踱。隊列:用于存放任務寇僧。一共有兩種隊列沸版, 串行隊列 和 并行隊列视粮。
串行隊列:放到串行隊列的任務,GCD 會 FIFO
(先進先出) 地取出來一個岛啸,執(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_SERIAL
或NULL
表示創(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í)行結果:
以上信號量部分來源于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的封裝胳挎,完全面向對象饼疙,所以使用起來更好理解。 大家可以看到NSOperation
和NSOperationQueue
分別對應GCD的任務
和隊列
串远。操作步驟也很好理解:
- 將要執(zhí)行的任務封裝到一個
NSOperation
對象中宏多。 - 將此任務添加到一個
NSOperationQueue
對象中。
然后系統(tǒng)就會自動在執(zhí)行任務澡罚。至于同步還是異步伸但、串行還是并行請繼續(xù)往下看:
添加任務
值得說明的是,NSOperation
只是一個抽象類留搔,所以不能封裝任務更胖。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperation
和NSBlockOperation
隔显。創(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()
在內的各種方法丛晦。所以這個功能提供給高級玩家奕纫,我在這里就不說了,等我需要用到時在研究它烫沙,到時候可能會再做更新匹层。