iOS多線程 之 NSThread詳解

NSThread是對內(nèi)核mach kernel中的mach thread的封裝驾锰,所以,每一個NSThread的對象其實就是一個線程,我們創(chuàng)建一個NSThread對象也就意味著我們創(chuàng)建了一個新的線程担汤。
初始化創(chuàng)建NSThread的方法有如下幾種:

/*
使用target對象的selector作為線程的任務(wù)執(zhí)行體,該selector方法最多可以接收一個參數(shù)帅刀,該參數(shù)即為argument
需要手動調(diào)用start方法來啟動線程執(zhí)行任務(wù)
*/
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

/*
使用block作為線程的任務(wù)執(zhí)行體
需要手動調(diào)用start方法來啟動線程執(zhí)行任務(wù)
*/
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

/*
類方法端圈,返回值為void
使用一個block作為線程的執(zhí)行體蜓斧,并直接啟動線程
*/
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

/*
類方法,返回值為void
使用target對象的selector作為線程的任務(wù)執(zhí)行體侥袜,該selector方法最多接收一個參數(shù)蝌诡,該參數(shù)即為argument
*/
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

NSThread的使用:

  • -(instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument 的使用

    /*
    說明: 本文的栗子都是在單視圖的工程中執(zhí)行,防止主線程退出后枫吧,其他線程被退出浦旱,不方便實驗。
    */
    
    //線程的任務(wù)執(zhí)行體并接收一個參數(shù)arg
    - (void)firstThread:(id)arg
    {
        for (int i = 0; i < 10; I++)
        {
            NSLog(@"Task %@ %@", [NSThread currentThread], arg);
        }
        NSLog(@"Thread Task Complete");
    }
    
    - (void)viewWillAppear:(BOOL)animated
    {    
        [super viewWillAppear: YES];
      
        /*
        創(chuàng)建一個線程九杂,線程任務(wù)執(zhí)行體為firstThread:方法
        該方法可以接收參數(shù)@"Hello, World"
        */
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(firstThread:) object:@"Hello, World"];
        //設(shè)置線程的名字颁湖,方便查看
        [thread setName:@"firstThread"];
        //啟動線程
        [thread start];    
    }
    
    - (void)viewDidAppear:(BOOL)animated
    {
        [super viewDidAppear: YES];
        NSLog("ViewDidAppear");
    }
    

    打印結(jié)果:該結(jié)果循環(huán)輸出10次

    Task <NSThread: 0x1c446f780>{number = 4, name = firstThread} Hello, World
    

    上面輸出了線程的名稱,還輸出了我們傳入的參數(shù)例隆,通過很簡單的代碼就可以創(chuàng)建一個新的線程來執(zhí)行任務(wù)甥捺,在開發(fā)中盡量將耗時的操作放在其他線程中執(zhí)行,只將更新UI的操作放在主線程中執(zhí)行镀层。

    一般情況下镰禾,通過上述方法創(chuàng)建的線程在執(zhí)行完任務(wù)執(zhí)行體后就會退出并銷毀,可以在firstThread:方法的第二個NSLog方法viewDidAppear:方法的輸出上打斷點唱逢,然后運行程序查看線程信息吴侦,在第一個斷點時即firstThread:方法的斷點中,程序中線程信息如下圖:

    image.png

    從上圖可以看到坞古,現(xiàn)在程序中有一個線程名為firstThread备韧,該線程即為我們創(chuàng)建的NSThread對象,而com.apple.main-thread(serial)即為主線程的名稱痪枫,其中serial是指明主線程是串行的织堂,這個內(nèi)容會在GCD中進(jìn)行講解,我們可以通過類方法[NSThread mainThread]來獲取主線程听怕。接下來繼續(xù)執(zhí)行到第二個斷點捧挺,程序中線程信息如下圖:
    image.png

    從上圖可以看到,firstThread線程不見了尿瞭,因為在執(zhí)行完任務(wù)執(zhí)行體后該線程就退出并被銷毀了。通過上述翅睛,我們無法復(fù)用NSThread声搁,盡管線程的創(chuàng)建相比進(jìn)程更加輕量級黑竞,但創(chuàng)建一個線程遠(yuǎn)比創(chuàng)建一個普通對象要消耗資源,而主線程和接收事件處理的線程仍然存在疏旨,這正是因為RunLoop的作用很魂,請參考RunLoop詳解

  • 其他三個方法使用:

    //栗子2:
    /*
    通過傳入block的方式創(chuàng)建一個線程檐涝,線程執(zhí)行體即為block的內(nèi)容
    但該方式創(chuàng)建線程無法傳入?yún)?shù)
    */
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        for (int i = 0; i < 100; i++)
        {
            NSLog(@"Task %@", [NSThread currentThread]);
        }
    }];
    //設(shè)置線程名稱
    [thread setName:@"firstThread"];
    //啟動線程
    [thread start];
    
    //栗子3:
    /*
    通過類方法創(chuàng)建并自動啟動一個線程
    該線程的執(zhí)行體即為傳入的block
    */
    [NSThread detachNewThreadWithBlock:^{
        for (int i = 0; i < 100; i++)
        {
            NSLog(@"Task %@", [NSThread currentThread]);
        }
    }];
    
    //栗子4:
    /*
    通過類方法創(chuàng)建并自動啟動一個線程
    該線程的執(zhí)行體為self的firstThread:方法遏匆,并傳入相關(guān)參數(shù)
    */
    [NSThread detachNewThreadSelector:@selector(firstThread:) toTarget:self withObject:@"Hello, World!"];
    

    上述把所有NSThread的創(chuàng)建方法都講解了一遍,實例方法和類方法的區(qū)別就在于谁榜,實例方法會返回NSThread對象幅聘,當(dāng)需要啟動線程時需要手動觸發(fā)start方法,而類方法沒有返回值窃植,創(chuàng)建線程后立即啟動該線程帝蒿。這里說的啟動線程start方法,僅僅是將線程的狀態(tài)從新建轉(zhuǎn)為就緒巷怜,何時執(zhí)行該線程的任務(wù)需要系統(tǒng)自行調(diào)度葛超。

NSThread常用的屬性和方法:

/*
類屬性,用于獲取當(dāng)前線程
如果是在主線程調(diào)用則返回主線程對象
如果在其他線程調(diào)用則返回其他的當(dāng)前線程
什么線程調(diào)用延塑,就返回什么線程
*/
@property (class, readonly, strong) NSThread *currentThread;

//類屬性绣张,用于返回主線程,不論在什么線程調(diào)用都返回主線程
@property (class, readonly, strong) NSThread *mainThread;

/*
設(shè)置線程的優(yōu)先級关带,范圍為0-1的doule類型胖替,數(shù)字越大優(yōu)先級越高
我們知道,系統(tǒng)在進(jìn)行線程調(diào)度時豫缨,優(yōu)先級越高被選中到執(zhí)行狀態(tài)的可能性越大
但是我們不能僅僅依靠優(yōu)先級來判斷多線程的執(zhí)行順序独令,多線程的執(zhí)行順序無法預(yù)測
*/
@property double threadPriority;

//線程的名稱,前面的栗子已經(jīng)介紹過了
@property (nullable, copy) NSString *name

//判斷線程是否正在執(zhí)行
@property (readonly, getter=isExecuting) BOOL executing;

//判斷線程是否結(jié)束
@property (readonly, getter=isFinished) BOOL finished;

//判斷線程是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled;

/*
讓線程睡眠好芭,立即讓出當(dāng)前時間片燃箭,讓出CPU資源,進(jìn)入阻塞狀態(tài)
類方法舍败,什么線程執(zhí)行該方法招狸,什么線程就會睡眠
*/
+ (void)sleepUntilDate:(NSDate *)date;

//同上,這里傳入時間
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

//退出當(dāng)前線程邻薯,什么線程執(zhí)行裙戏,什么線程就退出
+ (void)exit;

/*
實例方法,取消線程
調(diào)用該方法會設(shè)置cancelled屬性為YES厕诡,但并不退出線程
*/
- (void)cancel;

使用:

//按鈕點擊事件處理器
- (void)btnClicked
{
    //取消線程
    [self.thread cancel];
}

- (void)viewWillAppear:(BOOL)animated
{    
     self.thread = [[NSThread alloc] initWithBlock:^{
        for (int i = 0; i < 100; i++)
        {
            //獲取當(dāng)前正在執(zhí)行的線程累榜,即self.thread
            NSThread *currentThread = [NSThread currentThread];
            //判斷線程是否被取消
            if ([currentThread isCancelled])
            {
                //如果被取消就退出當(dāng)前正在執(zhí)行的線程,即self.thread
                [NSThread exit];
            }
            NSLog(@"Task %@", currentThread);
            //循環(huán)內(nèi),每次循環(huán)睡1s
            [NSThread sleepForTimeInterval:1];
        }
    }];
    [self.thread setName:@"firstThread"];
    //啟動線程
    [self.thread start];    
}

上面的栗子也比較簡單壹罚,在視圖中加入了一個按鈕葛作,點擊按鈕就會讓我們創(chuàng)建的線程執(zhí)行退出方法,在viewWillAppear:方法中創(chuàng)建并啟動了一個線程猖凛,這個線程每次循環(huán)都會判斷當(dāng)前線程是否被取消赂蠢,如果取消就退出當(dāng)前線程,接下來線程就會被銷毀辨泳,每次循環(huán)執(zhí)行完后都會讓當(dāng)前線程睡眠一秒虱岂,這里可能很多人都會有誤區(qū),讓線程睡眠會使得線程進(jìn)入阻塞狀態(tài)菠红,當(dāng)睡眠時間到后就會從阻塞狀態(tài)進(jìn)入就緒狀態(tài)第岖,被系統(tǒng)線程調(diào)度為執(zhí)行狀態(tài)后才能繼續(xù)執(zhí)行,所以這里睡1s并不是說精準(zhǔn)的1s后再繼續(xù)執(zhí)行途乃,只是1s后從阻塞態(tài)進(jìn)入就緒態(tài)绍傲,之后何時執(zhí)行由系統(tǒng)調(diào)度決定。還需要說明的是cancel方法并不會讓線程退出耍共,僅僅是將cancelled屬性置為YES烫饼,退出需要我們手動觸發(fā)exit方法。

所以執(zhí)行上述代碼后试读,每一秒多會輸出一次杠纵,當(dāng)我們點擊按鈕后該線程就會將cancelled屬性置為YES,在線程下次執(zhí)行時就會執(zhí)行exit方法退出線程钩骇,退出線程會立即終止當(dāng)前執(zhí)行的任務(wù)比藻,也就是說exit方法后的代碼不會再執(zhí)行了。

退出線程有如下三種情況:

  • 任務(wù)執(zhí)行體執(zhí)行完成后正常退出
  • 任務(wù)執(zhí)行體執(zhí)行過程中發(fā)生異常也會導(dǎo)致當(dāng)前線程退出
  • 執(zhí)行NSThread類的exit方法退出當(dāng)前線程

下載圖片的例子:

- (void)viewWillAppear:(BOOL)animated
{
    //創(chuàng)建一個線程用來下載圖片    
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1508398116220&di=ba2b7c9bf32d0ecef49de4fb19741edb&imgtype=0&src=http%3A%2F%2Fwscont2.apps.microsoft.com%2Fwinstore%2F1x%2Fea9a3c59-bb26-4086-b823-4a4869ffd9f2%2FScreenshot.398115.100000.jpg"]]];
        //圖片下載完成之后使用主線程來執(zhí)行更新UI的操作
        [self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:NO];
    }];
    //啟動線程
    [thread start];    
}

//主線程執(zhí)行當(dāng)前更新UI的方法
- (void)updateImage:(UIImage*)image
{
    self.imageView.image = image;
}

NSThread線程鎖倘屹,同步鎖

當(dāng)多條線程同時訪問同一塊資源時银亲,是需要用到線程鎖同步鎖纽匙。

舉例:銀行取錢

//定義一個Account類
@interface Account: NSObject
//賬號
@property (nonatomic, strong) NSString *accountNumber;
//余額
@property (nonatomic, assign) double balance;
//取錢操作
- (void)draw:(id)money;

@end

@implementation Account

@synthesize accountNumber = _accountNumber;
@synthesize balance = _balance;

- (void)draw:(id)money
{
    double drawMoney = [money doubleValue];
    //判斷余額是否足夠
    if (self.balance >= drawMoney)
    {
        //當(dāng)前線程睡1毫秒
        //[NSThread sleepForTimeInterval:0.001];
        self.balance -= drawMoney;
        NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance);
    }
    else
    {
        //余額不足务蝠,提示
        NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]);
    }
}

@end

//ViewController.m
- (void)viewWillAppear:(BOOL)animated
{    
    Account *account = [[Account alloc] init];
    account.accountNumber = @"1603121434";
    account.balance = 1500.0;
    
    NSThread *thread1 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)];
    [thread1 setName:@"Thread1"];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)];
    [thread2 setName:@"Thread2"];
    
    [thread1 start];
    [thread2 start];    
}

上面這個栗子很簡單,定義了一個Account類表示銀行賬戶烛缔,然后定義了取錢的操作馏段,在draw:方法里,注釋了[NSThread sleepForTimeInterval:0.001];代碼践瓷,然后在視圖中創(chuàng)建了兩個線程院喜,都去取錢,運行上述程序我們發(fā)現(xiàn)線程1取到錢了晕翠,線程2提示余額不足喷舀,但這個結(jié)果不一定正確,我們提到過,多線程的執(zhí)行順序是無法預(yù)測的元咙,哪怕線程2的優(yōu)先級比線程1低梯影,也有可能線程2先執(zhí)行巫员,所以我們把注釋的一行去掉注釋庶香,來模擬第一個線程進(jìn)入到取錢的判斷條件體以后被系統(tǒng)線程調(diào)度切換,此時的輸出結(jié)果為:

Thread1 draw money 1000.000000 balance left 500.000000
Thread2 draw money 1000.000000 balance left -500.000000

這就是競爭條件简识,這里不再贅述什么是競爭條件赶掖,線程1進(jìn)入判斷體后還沒有進(jìn)行取錢的操作就被切換到就緒態(tài),系統(tǒng)切換線程2執(zhí)行七扰,由于線程1還沒有進(jìn)行取錢操作奢赂,所以余額是滿足要求的,線程2也進(jìn)入了判斷體颈走,這樣兩個線程都可以取到錢膳灶。

解決競爭條件的方法很多,比如鎖機(jī)制同步代碼塊立由,接下來分別舉兩個栗子:

//栗子2:
- (void)draw:(id)money
{
    @synchronized (self) {
        double drawMoney = [money doubleValue];
        
        if (self.balance >= drawMoney)
        {
            [NSThread sleepForTimeInterval:0.001];
            self.balance -= drawMoney;
            NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance);
        }
        else
        {
            NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]);
        }
    }
}

//栗子3:
- (void)draw:(id)money
{
    /*
    self.lock在ViewController的初始化函數(shù)中進(jìn)行初始化操作
    self.lock = [[NSLock alloc] init];
    */
    [self.lock lock];
    double drawMoney = [money doubleValue];
    
    if (self.balance >= drawMoney)
    {
        [NSThread sleepForTimeInterval:0.001];
        self.balance -= drawMoney;
        NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance);
    }
    else
    {
        NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]);
    }
    [self.lock unlock];
}

在栗子2中轧钓,我們對draw:方法添加了一個同步代碼塊,使用@synchronized包圍的代碼即為同步代碼塊锐膜,同步代碼塊需要一個監(jiān)聽器毕箍,我們使用account對象本身作為監(jiān)聽器,因為是account對象產(chǎn)生的競爭條件道盏,當(dāng)執(zhí)行同步代碼塊時需要先獲取監(jiān)聽器而柑,如果獲取不到則線程會被阻塞,當(dāng)同步代碼塊執(zhí)行完成則釋放監(jiān)聽器荷逞,與java的synchronized同步代碼塊一樣媒咳。

栗子3,我們使用鎖機(jī)制种远,創(chuàng)建了一個NSLock類的鎖對象涩澡,lock方法用于獲取鎖,如果鎖被其他對象占用則線程被阻塞院促,unlock方法用于釋放鎖筏养,以便其他線程加鎖。

線程的調(diào)度對于開發(fā)者來說是透明的常拓,我們不能也無法預(yù)測線程執(zhí)行的順序渐溶,但有時我們需要線程按照一定條件來執(zhí)行,這時就需要線程間進(jìn)行通信弄抬,NSCondition就提供了線程間通信的方法茎辐,查看一下NSCondition的聲明文件:

NS_CLASS_AVAILABLE(10_5, 2_0)
@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

/*
調(diào)用NSCondition對象wait方法的線程會阻塞,直到其他線程調(diào)用該對象的signal方法或broadcast方法來喚醒
喚醒后該線程從阻塞態(tài)改為就緒態(tài),交由系統(tǒng)進(jìn)行線程調(diào)度
執(zhí)行wait方法時內(nèi)部會自動執(zhí)行unlock方法釋放鎖拖陆,并阻塞線程
*/
- (void)wait;

//同上弛槐,只是該方法是在limit到達(dá)時喚醒線程
- (BOOL)waitUntilDate:(NSDate *)limit;

/*
喚醒在當(dāng)前NSCondition對象上阻塞的一個線程
如果在該對象上wait的有多個線程則隨機(jī)挑選一個,被挑選的線程則從阻塞態(tài)進(jìn)入就緒態(tài)
*/
- (void)signal;

/*
同上依啰,該方法會喚醒在當(dāng)前NSCondition對象上阻塞的所有線程
*/
- (void)broadcast;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

NS_ASSUME_NONNULL_END

NSCondition實現(xiàn)了NSLocking協(xié)議乎串,所以NSCondition同樣具有鎖的功能,與NSLock一樣可以獲取鎖與釋放鎖的操作速警。了解了NSCondition基本方法叹誉,就可以實現(xiàn)生產(chǎn)者消費者問題了:

@interface Account: NSObject

@property (nonatomic, strong) NSString *accountNumber;
@property (nonatomic, assign) double balance;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, assign) BOOL haveMoney;

- (void)deposite:(id)money;
- (void)draw:(id)money;

@end

@implementation Account

@synthesize accountNumber = _accountNumber;
@synthesize balance = _balance;
@synthesize condition = _condition;
@synthesize haveMoney = _haveMoney;

//NSCondition的getter,用于創(chuàng)建NSCondition對象
- (NSCondition*)condition
{
    if (_condition == nil)
    {
        _condition = [[NSCondition alloc] init];
    }
    return _condition;
}

- (void)draw:(id)money
{
    //設(shè)置消費者取錢20次
    int count = 0;
    while (count < 20)
    {
        //首先使用condition上鎖闷旧,如果其他線程已經(jīng)上鎖則阻塞
        [self.condition lock];
        //判斷是否有錢
        if (self.haveMoney)
        {
            //有錢則進(jìn)行取錢的操作长豁,并設(shè)置haveMoney為NO
            self.balance -= [money doubleValue];
            self.haveMoney = NO;
            count += 1;
            NSLog(@"%@ draw money %lf %lf", [[NSThread currentThread] name], [money doubleValue], self.balance);
            //取錢操作完成后喚醒其他在次condition上等待的線程
            [self.condition broadcast];
        }
        else
        {
            //如果沒有錢則在次condition上等待,并阻塞
            [self.condition wait];
            //如果阻塞的線程被喚醒后會繼續(xù)執(zhí)行代碼
            NSLog(@"%@ wake up", [[NSThread currentThread] name]);
        }
        //釋放鎖
        [self.condition unlock];
    }
}

- (void)deposite:(id)money
{
    //創(chuàng)建了三個取錢線程忙灼,每個取錢20次匠襟,則存錢60次
    int count = 0;
    while (count < 60)
    {   
        //上鎖,如果其他線程上鎖了則阻塞
        [self.condition lock];
        //判斷如果沒有錢則進(jìn)行存錢操作
        if (!self.haveMoney)
        {
            //進(jìn)行存錢操作该园,并設(shè)置haveMoney為YES
            self.balance += [money doubleValue];
            self.haveMoney = YES;
            count += 1;
            NSLog(@"Deposite money %lf %lf", [money doubleValue], self.balance);
            //喚醒其他所有在condition上等待的線程
            [self.condition broadcast];
        }
        else
        {
            //如果有錢則等待
            [self.condition wait];
            NSLog(@"Deposite Thread wake up");
        }
        //釋放鎖
        [self.condition unlock];
    }
}

@end

- (void)viewWillAppear:(BOOL)animate
{
    
    [super viewWillAppear:YES];

    Account *account = [[Account alloc] init];
    account.accountNumber = @"1603121434";
    account.balance = 0;
    //消費者線程1酸舍,每次取1000元
    NSThread *thread = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)];
    [thread setName:@"consumer1"];
    
    //消費者線程2,每次取1000元
    NSThread *thread2 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)];
    [thread2 setName:@"consumer2"];
    
    //消費者線程3爬范,每次取1000元
    NSThread *thread3 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)];
    [thread3 setName:@"consumer3"];
    
    //生產(chǎn)者線程父腕,每次存1000元
    NSThread *thread4 = [[NSThread alloc] initWithTarget:account selector:@selector(deposite:) object:@(1000)];
    [thread4 setName:@"productor"];

    [thread start];
    [thread2 start];
    [thread3 start];
    [thread4 start];
}

上面這個栗子也比較簡單,關(guān)于NSCondition需要注意的就是它的wait方法青瀑,在執(zhí)行wait方法前按照邏輯當(dāng)然是要先獲取鎖璧亮,避免競爭條件,執(zhí)行wait方法后會阻塞當(dāng)前線程斥难,直到其他線程調(diào)用這個condition來喚醒被阻塞的線程枝嘶,被阻塞的線程喚醒后進(jìn)入就緒態(tài),當(dāng)被調(diào)度執(zhí)行后會重新獲取鎖并在wait方法下一行代碼繼續(xù)執(zhí)行哑诊。還有一個要注意的地方就是是否有錢的haveMoney這個flag群扶,這個flag存在的意義就是,當(dāng)線程被喚醒后進(jìn)入就緒態(tài)镀裤,接下來系統(tǒng)線程調(diào)度具體調(diào)度哪個線程來執(zhí)行開發(fā)者是不知道的竞阐,也就是說我們無法預(yù)知接下來執(zhí)行的是生產(chǎn)者還是消費者,為了避免錯誤暑劝,加一個flag用于判斷骆莹。

上面代碼的寫法是按照蘋果官方文檔的順序?qū)懙模嚓P(guān)于NSCondition可查閱官方文檔:Apple NSCondition

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載担猛,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者幕垦。
  • 序言:七十年代末丢氢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子先改,更是在濱河造成了極大的恐慌疚察,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仇奶,死亡現(xiàn)場離奇詭異貌嫡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猜嘱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門衅枫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫁艇,“玉大人朗伶,你說我怎么就攤上這事〔竭洌” “怎么了论皆?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長猾漫。 經(jīng)常有香客問我点晴,道長,這世上最難降的妖魔是什么悯周? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任粒督,我火速辦了婚禮,結(jié)果婚禮上禽翼,老公的妹妹穿的比我還像新娘屠橄。我一直安慰自己,他們只是感情好闰挡,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布锐墙。 她就那樣靜靜地躺著,像睡著了一般长酗。 火紅的嫁衣襯著肌膚如雪溪北。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天夺脾,我揣著相機(jī)與錄音之拨,去河邊找鬼。 笑死咧叭,一個胖子當(dāng)著我的面吹牛蚀乔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播佳簸,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乙墙,長吁一口氣:“原來是場噩夢啊……” “哼颖变!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起听想,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤腥刹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后汉买,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衔峰,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年蛙粘,在試婚紗的時候發(fā)現(xiàn)自己被綠了垫卤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡出牧,死狀恐怖穴肘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舔痕,我是刑警寧澤评抚,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站伯复,受9級特大地震影響慨代,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜啸如,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一侍匙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧叮雳,春花似錦想暗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厌均,卻和暖如春唬滑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棺弊。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工晶密, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人模她。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓稻艰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親侈净。 傳聞我的和親對象是個殘疾皇子尊勿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

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

  • 你要知道的iOS多線程NSThread僧凤、GCD、NSOperation元扔、RunLoop都在這里 轉(zhuǎn)載請注明出處 h...
    WWWWDotPNG閱讀 2,734評論 7 13
  • 1躯保、簡介:1.1 iOS有三種多線程編程的技術(shù),分別是:1.澎语、NSThread2途事、Cocoa NSOperatio...
    LuckTime閱讀 1,345評論 0 1
  • NSThread簡介 一、屬性 二擅羞、實例方法 三尸变、類方法 四减俏、賣火車票&線程加鎖 然后:將售票的方法加鎖 END.
    明似水閱讀 99評論 0 0
  • 進(jìn)程與線程 進(jìn)程:計算機(jī)操作系統(tǒng)分配資源的單位垄懂,是指系統(tǒng)中正在運行的應(yīng)用程序草慧,進(jìn)程之間相互獨立,運行在受保護(hù)的內(nèi)存...
    三十六_閱讀 379評論 1 1
  • 會議流程: 一、主持人開場及大臣自我介紹(5分鐘) 主持人:歐陽樂晨 三個標(biāo)簽:小學(xué)二(6)班 7歲 喜歡...
    fef923f073e3閱讀 212評論 0 0