iOS多線程之performSelector&NSThread

performSelector開頭的方法有不少,咱們簡單梳理一下ios

NSObject.h

@protocol NSObject
......
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
......
@end
複製代碼

這時的performSelector是同步調用,能夠在主線程也能夠在其餘線程調用,可是和直接調用不一樣的是它會在運行時去找方法的實現,編譯時不校驗方法是否實現,通常和respondsToSelector配合使用,咱們研究線程相關內容不涉及到它們macos

NSRunLoop.h

@interface NSObject (NSDelayedPerforming)
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
@end
複製代碼

這是兩個異步調用函數,即便afterDelay設置爲0仍然是異步調用,其實現邏輯是在RunLoop中開啓一個計時器。這裏有兩個cancle方法,能夠取消任務執行api

[self performSelector:@selector(fun) withObject:nil afterDelay:3];
    [self performSelector:@selector(fun1) withObject:nil afterDelay:2];
    
- (void)fun{
    NSLog(@"我是fun");
}

- (void)fun1{
    [NSRunLoop cancelPreviousPerformRequestsWithTarget:self selector:@selector(fun) object:nil];;
}
複製代碼

延遲三秒以後執行方法fun,可是2秒以後一個賤賤的方法取消了你的執行,因而fun就沒有執行,爲了防止方法泄漏,最好在dealloc方法中主動調用cancne方法,防止內存泄漏markdown

注意由於自線程的runloop默認是不開啓的,因此在子線程執行的時候要手動開啓RunLoop,例如這樣是不執行的數據結構

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self performSelector:@selector(fun) withObject:nil afterDelay:1];
    });
    
    - (void)fun{
    NSLog(@"我是fun");
    }
複製代碼

修改一番多線程

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self performSelector:@selector(fun) withObject:nil afterDelay:1];
        [[NSRunLoop currentRunLoop] run];
    });
    
    - (void)fun{
        NSLog(@"我是fun");
    }
複製代碼

這樣就能夠正常執行fun異步

NSThread.h

@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 API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// equivalent to the first method with kCFRunLoopCommonModes


- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end
複製代碼

這幾個方法咱們須要重點關注一下。async

前兩個方法是在主線程執行,waitUntilDone設置爲YES會阻塞當前線程,主線程執行完selector以後當前線程繼續執行,設置爲NO不會阻塞當前線程。modes設置RunLoop的mode,例如若是咱們設置kCFRunLoopDefaultMode那麼當列表滾動時就不會執行selector,若是咱們設置kCFRunLoopCommonModes就能夠正常執行。沒有modes參數的方法默認設置爲kCFRunLoopCommonModes函數

三四個方法和前兩個相似,只不過是指定線程來執行,若是指定主線程,那麼就和前兩個方法效果同樣。oop

第五個方法開啓子線程在後臺運行,隱式建立並啓動線程

和GCD相比NSThread須要咱們來管理線程的生命週期,因此咱們更多的會選擇使用更簡單的GCD,可是NSThread的一些方法咱們仍是須要常常用到的,好比:

  • [NSThread currentThread]獲取當前線程
  • [NSThread isMainThread]判斷當前線程是不是主線程

咱們看一下NSThread.h還有那些咱們熟悉的方法

  • 用來獲取當前線程的屬性currentThread
@property (class, readonly, strong) NSThread *currentThread;
複製代碼
  • 建立線程並置爲就緒狀態(runable)
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
複製代碼
  • 是否多線程。若是是經過非Cocoa的api開啓的線程,好比POSIX或者Multiprocessing Services APIs,被認爲不是多線程。
+ (BOOL)isMultiThreaded;
複製代碼
  • 這是一個只讀的可變字典,咱們能夠獲取字典以後用來存儲一下咱們感興趣的值
@property (readonly, retain) NSMutableDictionary *threadDictionary;
複製代碼

好比

NSThread *thread = [[NSThread alloc]initWithBlock:^{}];
    NSMutableDictionary *dic = [thread threadDictionary];
    [dic setValue:@"aKey" forKey:@"aValue"];
    NSLog(@"threadDictionary == %@",[thread threadDictionary]);
複製代碼

image.png

  • 讓當前線程掛起的方法
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
複製代碼
  • 取消當前線程
+ (void)exit;
複製代碼
  • 操做線程權限
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;

@property double threadPriority API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)); // To be deprecated; use qualityOfService below
複製代碼
  • 獲取/設置線程名字
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
複製代碼
  • 建立線程是有開銷的(iOS下主要成本包括:內核數據結構(大約1KB)、棧空間(子線程512KB、主線程1MB,也可使用setStackSize:設置,但必須是4K的倍數,並且最小是16K),建立線程大約須要90毫秒的建立時間)
@property NSUInteger stackSize API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
複製代碼
  • 判斷線程是不是主線程(實例方法,判斷當前線程實例是不是主線程)
@property (readonly) BOOL isMainThread API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
複製代碼
  • 判斷當前線程是不是主線程(類方法,判斷當前線程是不是主線程)應用比較多
@property (class, readonly) BOOL isMainThread API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); // reports whether current thread is main
複製代碼
  • 獲取主線程
@property (class, readonly, strong) NSThread *mainThread API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
複製代碼
  • 線程建立方法,建立成功不會自動運行,須要手動調用start方法
- (instancetype)init API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;

- (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));

- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
複製代碼
  • 判斷線程狀態
@property (readonly, getter=isExecuting) BOOL executing API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@property (readonly, getter=isFinished) BOOL finished API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@property (readonly, getter=isCancelled) BOOL cancelled API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
複製代碼
  • 修改線程狀態
- (void)cancel API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)start API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)main API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); // thread body method
複製代碼

NSThread線程間通信

場景:建立一個子線程執行耗時操做,執行結束之後回調主線程刷新頁面

NSThread *thread = [[NSThread alloc]initWithBlock:^{
        sleep(2);
        [self performSelectorOnMainThread:@selector(fun) withObject:nil waitUntilDone:YES];
    }];
    [thread start];
複製代碼

更復雜的操做就須要藉助鎖了;

參考文章

performSelector最全講解

iOS performSelector(參考)

iOS 多線程:『pthread、NSThread』詳盡總結

NSThread詳解

相關文章
相關標籤/搜索