iOS底層原理總結(jié) - NSThread

NSThread簡介

NSThread是面向?qū)ο蟮模庋b程度最小最輕量級(jí)的,使用更靈活谨朝,但要手動(dòng)管理線程的生命周期、線程同步和線程加鎖等,開銷較大字币。 NSThread的基本使用比較簡單则披,可以動(dòng)態(tài)創(chuàng)建初始化NSThread對(duì)象,對(duì)其進(jìn)行設(shè)置然后啟動(dòng)洗出;也可以通過NSThread的靜態(tài)方法快速創(chuàng)建并啟動(dòng)新線程士复;此外NSObject基類對(duì)象還提供了隱式快速創(chuàng)建NSThread線程的performSelector系列類別擴(kuò)展工具方法;NSThread還提供了一些靜態(tài)工具接口來控制當(dāng)前線程以及獲取當(dāng)前線程的一些信息共苛。

為什么要用 NSThread 呢判没?

  • 使用NSThread對(duì)象建立一個(gè)線程非常方便
  • 但是!要使用NSThread管理多個(gè)線程非常困難隅茎,不推薦使用
  • 技巧澄峰!使用[NSThread currentThread]獲得任務(wù)所在線程,適用于這三種技術(shù)
  • 使線程休眠3秒:[NSThread sleepForTimeInterval:0.3f];

NSThread創(chuàng)建線程

NSThread有三種創(chuàng)建方式:

  • initWithTarget方式辟犀,先創(chuàng)建線程對(duì)象俏竞,再啟動(dòng) (可以輕松拿到線程)
  • detachNewThreadSelector顯式創(chuàng)建并啟動(dòng)線程(快捷)
  • performSelectorInBackground隱式創(chuàng)建并啟動(dòng)線程(快捷)
/**
 initWithTarget
 */
- (void)onThread1 {
    // 創(chuàng)建并啟動(dòng)
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    // 設(shè)置線程名
    [thread setName:@"thread1"];
    // 設(shè)置優(yōu)先級(jí),優(yōu)先級(jí)從0到1堂竟,1最高
    [thread setThreadPriority:0.9];
    // 啟動(dòng)
    [thread start];
}

/**
 `detachNewThreadSelector`顯式創(chuàng)建并啟動(dòng)線程
 */
- (void)onThread2 {
    // 使用類方法創(chuàng)建線程并自動(dòng)啟動(dòng)線程
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}

/**
 `performSelectorInBackground`隱式創(chuàng)建并啟動(dòng)線程
 */
- (void)onThread3 {
    // 使用NSObject的方法隱式創(chuàng)建并自動(dòng)啟動(dòng)
    [self performSelectorInBackground:@selector(run) withObject:nil];
}

- (void)run {
    NSLog(@"當(dāng)前線程%@", [NSThread currentThread]);
}

打印結(jié)果:


image.png

NSThread方法

//獲取當(dāng)前線程
 +(NSThread *)currentThread; 
//創(chuàng)建線程后自動(dòng)啟動(dòng)線程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
//是否是多線程
+ (BOOL)isMultiThreaded;
//線程字典
- (NSMutableDictionary *)threadDictionary;
//線程休眠到什么時(shí)間
+ (void)sleepUntilDate:(NSDate *)date;
//線程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//取消線程
- (void)cancel;
//啟動(dòng)線程
- (void)start;
//退出線程
+ (void)exit;
//線程優(yōu)先級(jí)
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
- (double)threadPriority NS_AVAILABLE(10_6, 4_0);
- (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
//調(diào)用棧返回地址
+ (NSArray *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0);
+ (NSArray *)callStackSymbols NS_AVAILABLE(10_6, 4_0);
//設(shè)置線程名字
- (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0);
- (NSString *)name NS_AVAILABLE(10_5, 2_0);
//獲取棧的大小
- (NSUInteger)stackSize NS_AVAILABLE(10_5, 2_0);
- (void)setStackSize:(NSUInteger)s NS_AVAILABLE(10_5, 2_0);
// 獲得主線程
+ (NSThread *)mainThread;
//是否是主線程
- (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0);
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
//初始化方法
- (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0);
//是否正在執(zhí)行
- (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
//是否執(zhí)行完成
- (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
//是否取消線程
- (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//線程啟動(dòng)
- (void)start NS_AVAILABLE(10_5, 2_0);
- (void)main NS_AVAILABLE(10_5, 2_0); // thread body method
@end
//多線程通知
FOUNDATION_EXPORT NSString * const NSWillBecomeMultiThreadedNotification;
FOUNDATION_EXPORT NSString * const NSDidBecomeSingleThreadedNotification;
FOUNDATION_EXPORT NSString * const NSThreadWillExitNotification;

@interface NSObject (NSThreadPerformAdditions)
//與主線程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
  // equivalent to the first method with kCFRunLoopCommonModes
//與其他子線程通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
  // equivalent to the first method with kCFRunLoopCommonModes
//隱式創(chuàng)建并啟動(dòng)線程
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5, 2_0);

NSThread線程狀態(tài)

image.png
  • 1魂毁、新建:實(shí)例化對(duì)象
  • 2、就緒:向線程對(duì)象發(fā)送 start 消息出嘹,線程對(duì)象被加入“可調(diào)度線程池”等待 CPU 調(diào)度席楚;detach 方法和 performSelectorInBackground 方法會(huì)直接實(shí)例化一個(gè)線程對(duì)象并加入“可調(diào)度線程池”
  • 3、運(yùn)行:CPU 負(fù)責(zé)調(diào)度“可調(diào)度線程池”中線程的執(zhí)行,線程執(zhí)行完成之前税稼,狀態(tài)可能會(huì)在“就緒”和“運(yùn)行”之間來回切換,“就緒”和“運(yùn)行”之間的狀態(tài)變化由 CPU 負(fù)責(zé)烦秩,程序員不能干預(yù)
  • 4、阻塞:當(dāng)滿足某個(gè)預(yù)定條件時(shí)郎仆,可以使用休眠或鎖阻塞線程執(zhí)行,影響的方法有:sleepForTimeInterval只祠,sleepUntilDate,@synchronized(self)x線程鎖扰肌;
    線程對(duì)象進(jìn)入阻塞狀態(tài)后抛寝,會(huì)被從“可調(diào)度線程池”中移出,CPU 不再調(diào)度
  • 5曙旭、死亡

死亡方式:
5.1 正常死亡:線程執(zhí)行完畢
5.2 非正常死亡:
* 線程內(nèi)死亡--->[NSThread exit]:強(qiáng)行中止后盗舰,后續(xù)代碼都不會(huì)在執(zhí)行
* 線程外死亡:[threadObj cancel]--->通知線程對(duì)象取消,在線程執(zhí)行方法中需要增加 isCancelled 判斷,如果 isCancelled == YES,直接返回
死亡后線程對(duì)象的 isFinished 屬性為 YES桂躏;如果是發(fā)送 calcel 消息钻趋,線程對(duì)象的 isCancelled 屬性為YES;死亡后 stackSize == 0沼头,內(nèi)存空間被釋放爷绘。

啟動(dòng)線程
// 線程啟動(dòng)
- (void)start;
阻塞線程
// 線程休眠到某一時(shí)刻
+ (void)sleepUntilDate:(NSDate *)date;
// 線程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
結(jié)束線程
// 結(jié)束線程
+ (void)exit;

關(guān)于cancel的疑問,當(dāng)使用cancel方法時(shí)进倍,只是改變了線程的狀態(tài)標(biāo)識(shí)土至,并不能結(jié)束線程,所以我們要配合isCancelled方法進(jìn)行使用猾昆。

- (void)onThread {
    // 使用NSObject的方法隱式創(chuàng)建并自動(dòng)啟動(dòng)
    [self performSelectorInBackground:@selector(run) withObject:nil];
}

- (void)run {
    NSLog(@"當(dāng)前線程%@", [NSThread currentThread]);

    for (int i = 0 ; i < 100; i++) {
        if (i == 20) {
            //取消線程
            [[NSThread currentThread] cancel];
            NSLog(@"取消線程%@", [NSThread currentThread]);
        }

        if ([[NSThread currentThread] isCancelled]) {
            NSLog(@"結(jié)束線程%@", [NSThread currentThread]);
            //結(jié)束線程
            [NSThread exit];
            NSLog(@"這行代碼不會(huì)打印的");
        }

    }
}

打印結(jié)果:


image.png

NSThread線程間通信

在開發(fā)中陶因,我們經(jīng)常會(huì)在子線程進(jìn)行耗時(shí)操作,操作結(jié)束后再回到主線程去刷新UI垂蜗。這就涉及到了子線程和主線程之間的通信楷扬。看一下官方關(guān)于NSThread的線程間通信的方法贴见。

// 在主線程上執(zhí)行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;
  // equivalent to the first method with kCFRunLoopCommonModes

// 在指定線程上執(zhí)行操作
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

// 在當(dāng)前線程上執(zhí)行操作烘苹,調(diào)用 NSObject 的 performSelector:相關(guān)方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

下面通過一個(gè)經(jīng)典的下載圖片DEMO來展示線程之間的通信。具體步驟如下: 1片部、開啟一個(gè)子線程镣衡,在子線程中下載圖片。 2档悠、回到主線程刷新UI廊鸥,將圖片展示在UIImageView中。

func onThread() {
    let urlStr = "http://tupian.aladd.net/2015/7/2941.jpg"
    self.performSelector(inBackground: #selector(downloadImg(_:)), with: urlStr)
}

@objc func downloadImg(_ urlStr: String) {
    //打印當(dāng)前線程
    print("下載圖片線程", Thread.current)

    //獲取圖片鏈接
    guard let url = URL.init(string: urlStr) else {return}
    //下載圖片二進(jìn)制數(shù)據(jù)
    guard let data = try? Data.init(contentsOf: url) else {return}
    //設(shè)置圖片
    guard let img = UIImage.init(data: data) else {return}

    //回到主線程刷新UI
    self.performSelector(onMainThread: #selector(downloadFinished(_:)), with: img, waitUntilDone: false)
}

@objc func downloadFinished(_ img: UIImage) {
    //打印當(dāng)前線程
    print("刷新UI線程", Thread.current)

    // 賦值圖片到imageview
    self.imageView.image = image
}
復(fù)制代碼

NSThread線程安全

線程安全辖所,也可以被稱為線程同步惰说,主要是解決多線程爭搶操作資源的問題,就比如火車票缘回,全國各地多個(gè)售票窗口同事去售賣同一列火車票吆视。 怎么保證,多地售票的票池保持一致切诀,就需要用到多線程同步的技術(shù)去實(shí)現(xiàn)了揩环。鎖的問題統(tǒng)一到多線程鎖里詳細(xì)講解。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末幅虑,一起剝皮案震驚了整個(gè)濱河市丰滑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倒庵,老刑警劉巖褒墨,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異擎宝,居然都是意外死亡郁妈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門绍申,熙熙樓的掌柜王于貴愁眉苦臉地迎上來噩咪,“玉大人顾彰,你說我怎么就攤上這事∥改耄” “怎么了涨享?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長仆百。 經(jīng)常有香客問我厕隧,道長,這世上最難降的妖魔是什么俄周? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任吁讨,我火速辦了婚禮,結(jié)果婚禮上峦朗,老公的妹妹穿的比我還像新娘建丧。我一直安慰自己,他們只是感情好波势,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布茶鹃。 她就那樣靜靜地躺著,像睡著了一般艰亮。 火紅的嫁衣襯著肌膚如雪闭翩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天迄埃,我揣著相機(jī)與錄音疗韵,去河邊找鬼。 笑死侄非,一個(gè)胖子當(dāng)著我的面吹牛蕉汪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逞怨,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼者疤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了叠赦?” 一聲冷哼從身側(cè)響起驹马,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎除秀,沒想到半個(gè)月后糯累,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡册踩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年泳姐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暂吉。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胖秒,死狀恐怖缎患,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阎肝,我是刑警寧澤较锡,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站盗痒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏低散。R本人自食惡果不足惜俯邓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望熔号。 院中可真熱鬧稽鞭,春花似錦、人聲如沸引镊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弟头。三九已至吩抓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赴恨,已是汗流浹背疹娶。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伦连,地道東北人雨饺。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像惑淳,于是被迫代替她去往敵國和親额港。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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