Grand Central Dispatch (1)

Grand Central Dispatch

[TOC]

GCD是什么

Grand Central Dispatch 是蘋果公司發(fā)布的一套多核多線程任務(wù)分發(fā)的解決方案,簡稱GCD搅荞,或者你叫他滾床單也沒有人反對(duì)辈挂,嘿嘿步鉴。

GCD發(fā)布

蘋果公司首次發(fā)布GCD是伴隨Mac OS X 10.6 和 iOS 4系統(tǒng)一起發(fā)布的,也正是伴隨著block塊語法的支持颜懊,GCD技術(shù)將多線程執(zhí)行代碼年堆,通過block封裝成代碼塊,大大提高了多線程開發(fā)的效率呐籽,減少了開發(fā)難度锋勺,也極大增強(qiáng)了代碼的可讀性蚀瘸。

GCD之前的黑暗時(shí)代

如果我將GCD技術(shù)比喻成普羅米修斯帶給人類的火種有一些夸張的話,至少可以將其比作火柴庶橱。而在沒有生火器的石器時(shí)代贮勃,人類只能依靠何鉆木取火。

POSIX線程

POSIX線程(pthread)是一套C語言編寫的線程管理API苏章,面向過程寂嘉,我只在老東家一套C源碼庫中見別人用過,自己從來沒有用過布近,也不會(huì)用垫释,就像我也不會(huì)鉆木取火一樣。

NSThread

Cocoa框架中撑瞧,用OC將pthread對(duì)象化封裝棵譬,就誕生了NSThread操作類,但很可惜至今NSThread.h頭文件中一行注釋都木有预伺,只能看出這個(gè)類早在1994年就已經(jīng)存在了订咸。

這里就不列舉具體事例了,因?yàn)槿缃襁@個(gè)類的使用頻率已經(jīng)非常低了酬诀,唯一一種你可能會(huì)遇到的使用情境是判斷當(dāng)前執(zhí)行線程是否為主線程脏嚷,具體代碼如下

  if([NSThread isMainThread]){
        
  }

但你在GCD和NSOperation出現(xiàn)之前,會(huì)在各種需要多線程處理的情況下瞒御,使用NSThread的隱式調(diào)用方法父叙,也就是NSThread頭文件中給NSObject類作為屬性方法擴(kuò)展的一系列接口:

@interface NSObject (NSThreadPerformAdditions)

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    // equivalent to the first method with kCFRunLoopCommonModes

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
    // equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);

@end

總計(jì)五個(gè)API,簡易實(shí)現(xiàn)了一般開發(fā)需要使用的基本線程操作肴裙,避免用戶自己動(dòng)手寫NSThread調(diào)度趾唱,引發(fā)的一些列莫名的死鎖問題,在某種程度上減少了當(dāng)時(shí)的多線程開發(fā)難度蜻懦。

但這些API有一些很直觀的問題甜癞,例如由于OC語言限制,這些API的參數(shù)傳遞宛乃、返回值獲取都不易實(shí)現(xiàn)悠咱,并且實(shí)際寫出來的代碼也會(huì)因?yàn)檫壿嬏D(zhuǎn)分布在文件的各個(gè)位置,影響閱讀和糾錯(cuò)征炼,你不相信請(qǐng)看我從教科書上抄下來的例子:

- (void)launchThreadByNSObject_performSelectorInBackground_withObject
{
    [self performSelectorInBackground:@selector(doWork) withObject:nil];
}

- (void) doWork
{
    /*
     *
     * 長時(shí)間處理
     *
     * 例如  圖像處理
     *      網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求
     *      大型數(shù)據(jù)庫操作
     *      磁盤操作
     */
    
    //操作結(jié)束后調(diào)用主線程修改UI
    
    [self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
}

- (void) doneWork
{
    //主線程修改UI
}

這個(gè)例子是一個(gè)解決關(guān)于主線程刷新UI問題的例子析既,我們同學(xué)都知道所有有關(guān)UI刷新的方法,務(wù)必要在主線程調(diào)用谆奥,這是個(gè)硬性要求渡贾,是因?yàn)閁I渲染就是在主線程循環(huán)中完成的,如果在支線程中調(diào)用雄右,會(huì)出現(xiàn)莫名其妙的錯(cuò)誤空骚、UI卡死或者程序崩潰。

所以多線程在我們的日常開發(fā)中擂仍,用得最多的地方囤屹,就是網(wǎng)絡(luò)數(shù)據(jù)的異步請(qǐng)求,然后主線程刷新UI逢渔。將有延遲和計(jì)算量大的操作放在支線程完成肋坚,待完成后使用主線程刷新UI,才能有效地防止主線程UI刷新阻塞肃廓。

iOS 4與block

iOS 4帶來的編譯器對(duì)block塊語法的支持智厌,有點(diǎn)像人類發(fā)現(xiàn)了磷這種易燃物質(zhì)一樣,帶來的是火柴(GCD 和 NSOperation)這個(gè)更簡易的生火工具盲赊。

GCD和NSOperation 可以看作是 pthread(面向過程)和NSThread(面向?qū)ο螅┑腷lock升級(jí)版本铣鹏,帶來的多線程編程體驗(yàn)則是質(zhì)的飛躍。

GCD像是火柴哀蘑,輕便易用诚卸,隨用隨取。NSOperation則像打火機(jī)绘迁,一次開發(fā)合溺,重復(fù)使用。

GCD實(shí)戰(zhàn)

好了缀台,已經(jīng)說了十幾分鐘廢話了棠赛,終要進(jìn)入主題進(jìn)行GCD多線程開發(fā)實(shí)戰(zhàn)。在開始之前膛腐,希望大家要提前學(xué)習(xí)block塊語法的相關(guān)知識(shí)睛约,不要求熟練使用,只要求看得懂依疼。

實(shí)戰(zhàn)一 異步加載

還記得我們?cè)谏厦嬲故镜膹慕炭茣铣聛淼睦用刺等@個(gè)例子如果改成GCD的版本,會(huì)是什么樣子的呢律罢?

//異步請(qǐng)求Dispatch
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //長時(shí)間處理
    dispatch_async(dispatch_get_main_queue(), ^{
        //主線程更新UI
    });
});

這是什么鬼膀值?我來解釋一下。GCD使用的是C語言風(fēng)格的調(diào)用接口误辑,栗子中調(diào)用了兩次dispatch_async方法沧踏,第一次將長時(shí)間處理操作分撥到支線程處理,在其完成后巾钉,跳轉(zhuǎn)回主線程更新UI翘狱,操作都在方法的block參數(shù)中傳入,簡單明了砰苍,層級(jí)分明潦匈,沒有傳參障礙阱高,沒有閱讀障礙,一氣呵成茬缩,簡直美極了赤惊。

dispatch_async方法傳入的第二個(gè)參數(shù)是執(zhí)行block,沒啥好說的凰锡,第一個(gè)參數(shù)則是線程未舟。dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)方法獲取的Global線程,是非主線程中的一個(gè)掂为,具體是哪個(gè)不用開發(fā)者操心裕膀,反正是系統(tǒng)認(rèn)為這時(shí)候不是很忙的那一個(gè)。而兩個(gè)傳入?yún)?shù)中的第一個(gè)是線程的優(yōu)先級(jí)(共四個(gè)優(yōu)先級(jí))勇哗,第二個(gè)參數(shù)則約定為0昼扛。dispatch_get_main_queue()這個(gè)沒有任何參數(shù)的方法,返回的則是主線程智绸。

注意這里返回的參數(shù)類型是dispatch_queue_t野揪,是一個(gè)普通變量,估計(jì)是線程的索引瞧栗。真是兩三句話就能講明白的方法調(diào)用斯稳,什么你說聽不懂、看不懂迹恐。無所謂呀~ 我們將這段代碼加入代碼片段挣惰,需要使用的時(shí)候拿出來用就行啦。

比如:

圖片異步加載

首先放開權(quán)限NSAppTransportSecurity,NSAllowsArbitraryLoads

NSURLConnection版本

不加多線程異步操作

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

    [cell.imageView setImage:[UIImage new]];
    
    NSURL* url = [NSURL URLWithString:self.static_data[indexPath.row]];
    NSData* data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:nil error:nil];
    UIImage* image = [UIImage imageWithData:data];
    [cell.imageView setImage:image];
    [cell setNeedsLayout];

    return cell;
}

使用GCD以后

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

    [cell.imageView setImage:[UIImage new]];
    
    NSURL* url = [NSURL URLWithString:self.static_data[indexPath.row]];

    dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSData* data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:nil error:nil];
        UIImage* image = [UIImage imageWithData:data];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            [cell.imageView setImage:image];
            [cell setNeedsLayout];
        });
    });

    return cell;
}
NSURLSession版本
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    
//    cell.backgroundColor = [UIColor lightGrayColor];
    // Configure the cell...
    [cell.imageView setImage:[UIImage new]];
    
    NSURL* url = [NSURL URLWithString:self.static_data[indexPath.row]];
    NSURLSessionConfiguration* c = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession* session = [NSURLSession sessionWithConfiguration:c];
    NSURLSessionDataTask* task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        dispatch_async(dispatch_get_main_queue(), ^{
            UIImage* image = [UIImage imageWithData:data];
            //                NSLog(@"%@",image);
            //                NSLog(@"%@",cell.imageView);
            [cell.imageView setImage:image];
            [cell setNeedsLayout];
        });
        
    }];
    [task resume];
    return cell;
}

這次試出來UI刷新的阻塞感受了么殴边?霸髅?你說沒有锤岸,那你用真機(jī)調(diào)試一下竖幔,就會(huì)有更明顯的感受了。

UI阻塞在實(shí)際開發(fā)中是偷,偶爾會(huì)遇到拳氢。并且會(huì)引起一些莫名其妙的bug,希望大家再遇到時(shí)候能及時(shí)往這方面思考蛋铆。比如馋评,我們?nèi)绻?code>UIViewController的初始化等一系列加載函數(shù)中加入能引起阻塞的代碼,整個(gè)VC的加載會(huì)產(chǎn)生卡頓刺啦,還很有可能直接崩潰留特。

所以將阻塞操作放在支線程處理,是十分必要的。我們只要將下面代碼存為代碼片段蜕青,隨用隨取苟蹈。

//支線程調(diào)用
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            <#code#>
});

//主線程調(diào)用
dispatch_async(dispatch_get_main_queue(), ^{
        <#code#>
});

實(shí)戰(zhàn)二 同步操作等待

多線程操作的第二個(gè)常用情景就是并行操作等待。

dispatch_group_t group = dispatch_group_create();
    // 合并匯總結(jié)果
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
            //并行阻塞操作1
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"1");
    });
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
        //并行阻塞操作2
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"2");
    });
    dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
        //并行阻塞操作3
        NSLog(@"3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //3項(xiàng)操作都完成后調(diào)用主線程更新UI
        NSLog(@"4");
    });

在這段演示代碼里面右核,即使你看不懂GCD相關(guān)調(diào)用汉操,也能猜出最后的輸出結(jié)果對(duì)吧,我解釋一下[NSThread sleepForTimeInterval:1.0];這句調(diào)用是讓線程睡眠1秒中蒙兰,模擬1秒鐘阻塞。

好的告訴我你的答案芒篷。

3
2
1
4

這也是一段可以收藏為代碼片段的實(shí)用工具搜变,可以起名為并行代碼等待。就像異步等待一樣针炉,我們現(xiàn)在來舉一個(gè)簡單的實(shí)際案例挠他。

并行操作案例

還是舉一個(gè)不是很簡單的例子,也可能不是很實(shí)用篡帕,但絕對(duì)能體現(xiàn)這套邏輯的精髓殖侵。在講栗子之前,我們先來學(xué)習(xí)一下SDWebImage的另外一段代碼(對(duì)镰烧,又是SDWebImage)拢军。

//SDImageCache.m 608行

- (NSUInteger)getSize {
    __block NSUInteger size = 0;
    dispatch_sync(self.ioQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        for (NSString *fileName in fileEnumerator) {
            NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
            NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
            size += [attrs fileSize];
        }
    });
    return size;
}

這段代碼,具體功能是進(jìn)行文件夾文件大小的統(tǒng)計(jì)怔鳖。對(duì)你沒有聽錯(cuò)茉唉,文件夾是無法直接接獲取其大小的,需要遍歷其中每個(gè)文件然后相加統(tǒng)計(jì)结执。

這段代碼實(shí)用GCD度陆,但使用的方法我們前面并沒有講過,我放在后面再說献幔。目前我們的任務(wù)是把這個(gè)方法改造一下懂傀,讓他可以統(tǒng)計(jì)任意的文件夾大小。

- (NSUInteger)getSize:(NSString*)dicPath {
    __block NSUInteger size = 0;
    NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:dicPath];
    for (NSString *fileName in fileEnumerator) {
        NSString *filePath = [dicPath stringByAppendingPathComponent:fileName];
        NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
        size += [attrs fileSize];
    }
    return size;
}

接下來我們統(tǒng)計(jì)一下cache目錄和tmp目錄的容量

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDir = [paths objectAtIndex:0];
NSString *tmpDir = NSTemporaryDirectory();
    
NSUInteger cacheSize = [self getSize:cachesDir];
NSUInteger tmpSize = [self getSize:tmpDir];
    
NSLog(@"total size : %@ (%@+%@)",@(cacheSize + tmpSize),@(cacheSize),@(tmpSize));

total size : 657060 (657060+0)

tmp文件夾是空的蜡感,我們換成libiary目錄蹬蚁,不過因?yàn)閏ache目錄在libiary目下,所以是有重復(fù)的铸敏,不過無所謂缚忧。

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDir = [paths objectAtIndex:0];

NSArray * paths2 = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString * libraryPath = paths2[0];
    
NSUInteger cacheSize = [self getSize:cachesDir];
NSUInteger librarySize = [self getSize:libraryPath];
    
NSLog(@"total size : %@ (%@+%@)",@(cacheSize + librarySize),@(cacheSize),@(librarySize));

我們?cè)趫?zhí)行這段代碼的時(shí)候,一般會(huì)很順暢就執(zhí)行完了杈笔,沒有任何阻塞闪水。原因是統(tǒng)計(jì)的目標(biāo)目錄,文件非常少。如果遇到文件稍多的情況球榆,上面這段代碼就出出現(xiàn)阻塞朽肥,又因?yàn)檎麄€(gè)是在主線程操作的,所以必然會(huì)影響到UI的刷新持钉,界面會(huì)卡頓衡招。好,那讓我們運(yùn)用前面的GCD模版來將這段代碼改造成異步執(zhí)行每强。

NSLog(@"1");
    
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   
   NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
   NSString *cachesDir = [paths objectAtIndex:0];
   
   NSArray * paths2 = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
   NSString * libraryPath = paths2[0];
   
   NSUInteger cacheSize = [self getSize:cachesDir];
   NSUInteger librarySize = [self getSize:libraryPath];
   dispatch_async(dispatch_get_main_queue(), ^{
       
           NSLog(@"total size : %@ (%@+%@)",@(cacheSize + librarySize),@(cacheSize),@(librarySize));
       
   });
});
    
NSLog(@"2");

上面是我修改的結(jié)果始腾,大家來分析一下輸出順序,應(yīng)該是

1
2
total size : 1314324 (657060+657264)

前面坐了這么多鋪墊空执,接下來我們進(jìn)入正題浪箭,講解一下串行和并行。串行很好理解辨绊,我們一般寫的代碼都是一步一步一串一串執(zhí)行的奶栖。并行則是多項(xiàng)任務(wù)同時(shí)進(jìn)行,也不難理解门坷,類似于中學(xué)物理學(xué)的電路的并聯(lián)合串聯(lián)宣鄙。

上面這段代碼,我們前后調(diào)用兩次getSize方法默蚌,按順序分別統(tǒng)計(jì)了兩個(gè)目錄的大小冻晤,我們統(tǒng)計(jì)一下耗時(shí):

dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   
   clock_t begin, duration;
   
   NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
   NSString *cachesDir = [paths objectAtIndex:0];
   
   NSArray * paths2 = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
   NSString * libraryPath = paths2[0];
   
   begin = clock();
   
   NSUInteger cacheSize = [self getSize:cachesDir];
   NSUInteger librarySize = [self getSize:libraryPath];
   
   duration = clock() - begin;
   
   NSLog(@"%@",@((double)duration/CLOCKS_PER_SEC));
   
   dispatch_async(dispatch_get_main_queue(), ^{
       
           NSLog(@"total size : %@ (%@+%@)",@(cacheSize + librarySize),@(cacheSize),@(librarySize));
       
   });
});

0.002481

這里單位是秒,其實(shí)已經(jīng)很快敏簿。 好明也,我們把前面的并行模版套進(jìn)來。

dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   
   NSString *path = [[NSBundle mainBundle] bundlePath];
   
   NSArray * paths2 = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
   NSString * libraryPath = paths2[0];
   
   __block clock_t begin, duration;
   __block NSUInteger cacheSize,librarySize;
   
   dispatch_group_t group = dispatch_group_create();
   // 合并匯總結(jié)果
   
   begin = clock();
   dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
      
        cacheSize = [self getSize:path];
   });
   dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
   
        librarySize = [self getSize:libraryPath];
   });
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
   
        duration = clock() - begin;
       NSLog(@"%@",@((double)duration/CLOCKS_PER_SEC));
       NSLog(@"total size : %@ (%@+%@)",@(cacheSize + librarySize),@(cacheSize),@(librarySize));
   });
});

0.039834

提問

結(jié)果很讓我欣慰惯裕,整整大了一個(gè)數(shù)量級(jí)温数,請(qǐng)你們分析一下原因。

原因也很簡單蜻势,就是因?yàn)榻y(tǒng)計(jì)這種小目錄是在耗時(shí)太短撑刺,短到比創(chuàng)建GCD Group的CPU占用都要少,所以耗時(shí)不降反增握玛,呵呵够傍。但一旦這個(gè)耗時(shí)任務(wù)CPU占用大于GCD消耗的時(shí)候,并行操作帶來的耗時(shí)收益就是

串行總耗時(shí) - 并行最大耗時(shí)

小結(jié)

這節(jié)課由于篇幅有限挠铲,我們講的內(nèi)容并不多冕屯,但實(shí)用性很高疗我。大家注意到?jīng)]有谋减,從頭到尾我們等于講任何與GCD有關(guān)的接口調(diào)用、類型相關(guān)的內(nèi)容麦向,卻教會(huì)了你進(jìn)行異步請(qǐng)求和同步等待操作的方法,模版拿過來基本不用修改就能嵌套使用浴韭,這就叫知其然丘喻。下一章節(jié)我們?cè)購腁PI方向講解GCD的類型和方法調(diào)用,這叫知其所以然念颈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泉粉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子榴芳,更是在濱河造成了極大的恐慌嗡靡,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窟感,死亡現(xiàn)場離奇詭異叽躯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)肌括,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酣难,“玉大人谍夭,你說我怎么就攤上這事『┠迹” “怎么了紧索?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長菜谣。 經(jīng)常有香客問我珠漂,道長,這世上最難降的妖魔是什么尾膊? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任媳危,我火速辦了婚禮,結(jié)果婚禮上冈敛,老公的妹妹穿的比我還像新娘待笑。我一直安慰自己,他們只是感情好抓谴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布暮蹂。 她就那樣靜靜地躺著,像睡著了一般癌压。 火紅的嫁衣襯著肌膚如雪仰泻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天滩届,我揣著相機(jī)與錄音集侯,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛浅悉,可吹牛的內(nèi)容都是我干的趟据。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼术健,長吁一口氣:“原來是場噩夢啊……” “哼汹碱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荞估,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤咳促,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勘伺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跪腹,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年飞醉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冲茸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缅帘,死狀恐怖轴术,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钦无,我是刑警寧澤逗栽,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站失暂,受9級(jí)特大地震影響彼宠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弟塞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一凭峡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧决记,春花似錦想罕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至笙瑟,卻和暖如春楼镐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背往枷。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工框产, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凄杯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓秉宿,卻偏偏與公主長得像戒突,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子描睦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Object C中創(chuàng)建線程的方法是什么膊存?如果在主線程中執(zhí)行代碼,方法是什么忱叭?如果想延時(shí)執(zhí)行代碼隔崎、方法又是什么? 1...
    AlanGe閱讀 1,716評(píng)論 0 17
  • 一韵丑、前言 上一篇文章iOS多線程淺匯-原理篇中整理了一些有關(guān)多線程的基本概念爵卒。本篇博文介紹的是iOS中常用的幾個(gè)多...
    nuclear閱讀 2,046評(píng)論 6 18
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案撵彻,以及其使用方法和注意事項(xiàng)钓株。當(dāng)然也會(huì)給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 601評(píng)論 0 0
  • NSThread 第一種:通過NSThread的對(duì)象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 789評(píng)論 0 3
  • 深秋的一個(gè)周末,我受文友邀約專程探訪北隍城島陌僵,它是長島三十二島之一享幽,位于最北端,這里沒有被開發(fā)灘涂破壞拾弃,天然原生態(tài)...
    暗香浮動(dòng)搖煙柳閱讀 2,591評(píng)論 0 0