iOS開發(fā)高級進(jìn)階(13-16)-多線程

這周就學(xué)了多線程,現(xiàn)在還在云里霧里....

線程的概念

  • 執(zhí)行調(diào)度單位
  • 并發(fā)資源訪問控制
    • 原子操作 Atomic(任務(wù)不可分隔,保證數(shù)據(jù)一致性)
    • 用鎖同步,鎖的類型
      • Semaphore 信號量:系統(tǒng)多個資源
      • Mutex 互斥量:單個資源
      • Critical Section 臨界區(qū):反復(fù)訪問的情況
      • Read-Write Lock 讀寫鎖 只有1個寫,其它是讀,不受鎖的影響
      • Condition Variable 條件變量 符合條件后激活

iOS中多任務(wù)API

iOS有三種多線程編程的技術(shù):

  1. NSThread
  2. Cocoa NSOperation
  3. GCD 全稱:Grand Central Dispatch
    抽象度層次是從低到高的,抽象度越高的使用越簡單

三種方式的優(yōu)缺點(diǎn)介紹:

NSThread:

優(yōu)點(diǎn):NSThread 比其他兩個輕量級
缺點(diǎn):需要自己管理線程的生命周期,線程同步。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷

NSThread實(shí)現(xiàn)的技術(shù)有下面三種:

  • Technology
  • Description
  • Cocoa threads
    一般使用cocoa thread 技術(shù)。

Cocoa operation

優(yōu)點(diǎn):不需要關(guān)心線程管理,數(shù)據(jù)同步的事情,可以把精力放在自己需要執(zhí)行的操作上。

Cocoa operation 相關(guān)的類是 NSOperation ,NSOperationQueue。

NSOperation是個抽象類,使用它必須用它的子類,可以實(shí)現(xiàn)它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation。

創(chuàng)建NSOperation子類的對象,把對象添加到NSOperationQueue隊(duì)列里執(zhí)行。

GCD

Grand Central Dispatch (GCD)是Apple開發(fā)的一個多核編程的解決方法。在iOS4.0開始之后才能使用。GCD是一個替代諸如NSThread, NSOperationQueue, NSInvocationOperation等技術(shù)的很高效和強(qiáng)大的技術(shù)。

參考:http://blog.csdn.net/totogo2010/article/details/8010231


Main Thread (主線程)

主線程管理界面,也叫 UI 線程
長時間操作線程的應(yīng)該另外開一個線程進(jìn)行

NSThread

//新開線程執(zhí)行 
//import
@import Foundation;

//方法一:直接創(chuàng)建線程并且開始運(yùn)行線程
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];

//方法二:
NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                    selector:@selector(doSomething:)
                                    object:nil];
[myThread start];

參數(shù)說明:

  • selector :線程執(zhí)行的方法,這個selector只能有一個參數(shù),而且不能有返回值。

  • target :selector消息發(fā)送的對象

  • argument:傳輸給target的唯一參數(shù),也可以是nil

    //通知主線程更新界面
      NSObject *Obj = [[NSObject alloc]init];
      [Obj performSelectorOnMainThread:@selector(doSomething:) withObject:self waitUntilDone:NO];
    
    //查詢是否是主線程
      NSLog(@"%@",[NSThread isMainThread]?@"YES":@"NO");
    

Demo

  1. 為了訪問 HTTP 協(xié)議,在Info.plist中添加

     <key>NSAppTransportSecurity</key>
     <dict>
         <key>NSAllowsArbitraryLoads</key>
         <true/>
     </dict>
    
  2. 在 View 中添加imageView

  3. .m文件
    #import "ViewController.h"
    #define kURL @"http://upload.jianshu.io/daily_images/images/myb4DiWJtEcFVgHwgdhh.jpg"
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    @end

    @implementation ViewController
    
    -(void)loadImage:(NSString *) url{
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
        UIImage *image = [UIImage imageWithData:data scale:2.0];
        [self performSelectorOnMainThread:@selector(dispalyImage:) withObject:image waitUntilDone:NO];
    }
    
    -(void)dispalyImage:(UIImage*) image{
        self.imageView.image = image;
    }
    
    - (void)viewDidLoad{
        [super viewDidLoad];
    
        NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:kURL];
       [thread start];
    }
    
    - (void)didReceiveMemoryWarning{
        [super didReceiveMemoryWarning];
    }
    
    @end
    

創(chuàng)建一個線程的開銷

  1. 內(nèi)核中占據(jù)1KB 內(nèi)存保存線程資料
  2. stack space堆中占據(jù) 512KB 內(nèi)存( osx 主線程8MB\ios 主線程1MB)
  3. 需要耗費(fèi)90毫秒,費(fèi)電
    因此線程越少越好

線程屬性設(shè)定

thread.name = @"image";
thread.stackSize = 1024 * 4 * 5 ;//20K
thread.threadPriority=1.0;//0.0-1.0 ,1.0為高優(yōu)先級
//?thread.threadDictionary;//沒弄清楚怎么用

線程的控制

   [thread start];  
   [thread cancel];  
   NSLog(@"1--%@",[NSThread currentThread]);//當(dāng)前線程
   [NSThread sleepForTimeInterval:2];
   NSDate * sleepTime =[[NSDate alloc]initWithTimeIntervalSinceNow:60];
   [NSThread sleepUntilDate:sleepTime];
   [NSThread exit];//直接取消,慎用

Runloop (循環(huán)執(zhí)行)

線程和 RunLoop 之間是一一對應(yīng)的,其關(guān)系是保存在一個全局的 Dictionary 里

線程剛創(chuàng)建時并沒有 RunLoop

RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時,RunLoop 的銷毀是發(fā)生在線程結(jié)束時。

只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)

一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer

Timer :CFRunLoopTimerRef/NSTimer

Observer:CFRunLoopObserverRef:觀察 runloop 狀態(tài)變化

每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。

如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。

兩種 source :

  • source0:

    • 只包含一個回調(diào)
    • 不能主動觸發(fā)事件
    • 需要手工標(biāo)記和喚醒
      CFRunLoopSourceSignal(<#CFRunLoopSourceRef source#>)
      CFRunLoopWakeUp(<#CFRunLoopRef rl#>)
  • source1:

    • 一個 mach_port 和一個回調(diào)
    • mach消息到達(dá)時,主動喚醒 Runloop 線程

實(shí)際上 RunLoop 內(nèi)部是一個 do-while 循環(huán)。

當(dāng)調(diào)用 CFRunLoopRun() 時,線程就會一直停留在這個循環(huán)里;直到超時或被手動停止,該函數(shù)才會返回。

Source/Timer/Observer 被統(tǒng)稱為 mode item,一個 item 可以被同時加入多個 mode。但一個 item 被重復(fù)加入同一個 mode 時是不會有效果的。如果一個 mode 中一個 item 都沒有,則 RunLoop 會直接退出,不進(jìn)入循環(huán)。

主線程的 RunLoop 里有兩個預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode

這兩個 Mode 都已經(jīng)被標(biāo)記為"Common"屬性

DefaultMode 是 App 平時所處的狀態(tài),TrackingRunLoopMode 是追蹤 ScrollView 滑動時的狀態(tài)

當(dāng)創(chuàng)建一個 Timer 并加到 DefaultMode 時,Timer 會得到重復(fù)回調(diào),但此時滑動一個TableView時,RunLoop 會將 mode 切換為 TrackingRunLoopMode,這時 Timer 就不會被回調(diào),并且也不會影響到滑動操作。

有時需要一個 Timer,在兩個 Mode 中都能得到回調(diào),一種辦法就是將這個 Timer 分別加入這兩個 Mode。還有一種方式,就是將 Timer 加入到頂層的 RunLoop 的 "commonModeItems" 中。

"commonModeItems" 被 RunLoop 自動更新到所有具有"Common"屬性的 Mode 里去。

NSRunloop 生命周期

待補(bǔ)充


NSOperation

NSThread中,依賴\同步\調(diào)度需要寫的東西比較多
參考:http://blog.csdn.net/totogo2010/article/details/8013316

  • NSOperation:封裝任務(wù)部分
    • 用 selector(選擇器)編寫任務(wù): NSInvocationOperation
    • 用 block 編寫任務(wù):NSBlockOperation
    • 任務(wù)可以用依賴關(guān)系串起來
  • NSOperationQueue: 封裝線程管理部分

demo

  1. 為了訪問 HTTP 協(xié)議,在Info.plist中添加

     <key>NSAppTransportSecurity</key>
     <dict>
         <key>NSAllowsArbitraryLoads</key>
         <true/>
     </dict>
    
  2. 在 View 中添加imageView

  3. .m文件

@interface NSOperationTemplateVC ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation NSOperationTemplateVC


-(void)loadImage:(NSString *) url{
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
    self.image = [UIImage imageWithData:data scale:2.0];

}

-(void)dispalyImage{
    self.imageView.image = self.image;
}


- (void)viewDidLoad
{
    [super viewDidLoad];


    NSInvocationOperation *opLoadImage = [[NSInvocationOperation alloc]initWithTarget:self
                                                                       selector:@selector(loadImage:)
                                                                         object:kURL];
    NSInvocationOperation *opDispalyImage = [[NSInvocationOperation alloc]initWithTarget:self
                                                                       selector:@selector(dispalyImage)
                                                                         object:nil];
    [opDispalyImage addDependency:opLoadImage];

    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    [queue addOperation:opLoadImage];
    [[NSOperationQueue mainQueue] addOperation:opDispalyImage];

    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
    }

    @end

NSOperation 的直接使用

是個抽象基類,一般不直接創(chuàng)造實(shí)例

NSOperation *op = [[NSOperation alloc]init];
[op start];
[op waitUntilFinished];
[op cancel];

[op main];
//默認(rèn)不做
//在 autoreleasepool 里運(yùn)行
//定義子類時覆蓋
//可以通過繼承重寫NSOperation 的main 使用

NSInvocationOperation

創(chuàng)建

//方法一
     NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self
                             selector:@selector(loadImage:)
                             object:kURL];
//方法二
    NSInvocation * invocation =[[NSInvocation alloc]init];
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithInvocation:invocation];

NSBlockOperation

//創(chuàng)建
[NSBlockOperation blockOperationWithBlock:<#^(void)block#>);

//追加
 NSBlockOperation *blockOperation = [[NSBlockOperation alloc]init];
 [blockOperation addExecutionBlock:<#^(void)block#>];

NSOperation 依賴關(guān)系

[op addDependency:<#(nonnull NSOperation *)#>];

依賴完成后本Operation才會執(zhí)行

移除(必須手工移除):

[op removeDependency:<#(nonnull NSOperation *)#>];

NSOperation 狀態(tài)

op.name = @"image";

NSLog(@"%@",op.ready?@"YES":@"NO");
NSLog(@"%@",op.finished?@"YES":@"NO");
NSLog(@"%@",op.cancelled?@"YES":@"NO");

NSLog(@"%@",op.asynchronous?@"YES":@"NO");

[op setCompletionBlock:<#(void (^ _Nullable)(void)()completionBlock#>];

NSOperationQueue

//add 操作
    [queue addOperation:operation];
    [queue addOperations:<#(nonnull NSArray<NSOperation *> *)#> waitUntilFinished:NO];
    [queue addOperationWithBlock:<#^(void)block#>];


    [queue setMaxConcurrentOperationCount:5];//可跨隊(duì)列,但必須保證沒有阻塞和掛起
    [queue cancelAllOperations];
    [queue setSuspended:YES];//暫停

    [queue waitUntilAllOperationsAreFinished];

mainQueue

  NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];

GCD

(作業(yè)相關(guān)內(nèi)容,必須好好學(xué))

用函數(shù)的方法調(diào)用進(jìn)程,是一個 C語言 API

Grand Central Dispatch 簡稱(GCD)是蘋果公司開發(fā)的技術(shù),以優(yōu)化的應(yīng)用程序支持多核心處理器和其他的對稱多處理系統(tǒng)的系統(tǒng)

參考:http://blog.csdn.net/totogo2010/article/details/8016129

dispatch queue分為下面三種:

  • Serial
    又稱為private dispatch queues,同時只執(zhí)行一個任務(wù)。Serial queue通常用于同步訪問特定的資源或數(shù)據(jù)。當(dāng)你創(chuàng)建多個Serial queue時,雖然它們各自是同步執(zhí)行的,但Serial queue與Serial queue之間是并發(fā)執(zhí)行的。

  • Concurrent
    又稱為global dispatch queue,可以并發(fā)地執(zhí)行多個任務(wù),但是執(zhí)行完成的順序是隨機(jī)的。

  • Main dispatch queue
    它是全局可用的serial queue,它是在應(yīng)用程序主線程上執(zhí)行任務(wù)的。
    我們看看dispatch queue如何使用

用法:

  1. 拿到一個 queue
  2. 把 block 放到 queue 中去

用dispatch_async處理后臺任務(wù)

dispatch_sync:等執(zhí)行結(jié)束再返回
dispatch_async:不等結(jié)束就返回
如果要獲知任務(wù)結(jié)束信息,只要自己寫就好了
現(xiàn)在在執(zhí)行的 queue
dispatch_get_current_queue()

全局隊(duì)列
      dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{...}

  //1
  DISPATCH_QUEUE_PRIORITY_HIGH
  DISPATCH_QUEUE_PRIORITY_DEFAULT
  DISPATCH_QUEUE_PRIORITY_LOW
  DISPATCH_QUEUE_PRIORITY_BACKGROUND

  //2
   QOS_CLASS_USER_INTERACTIVE//響應(yīng)用戶         
   QOS_CLASS_USER_INITIATED//用戶發(fā)起               
   QOS_CLASS_DEFAULT//
   QOS_CLASS_UTILITY//程序內(nèi)部維護(hù)
   QOS_CLASS_BACKGROUND//后臺
   QOS_CLASS_UNSPECIFIED//
主隊(duì)列(串行)
dispatch_get_main_queue(), ^{...}
//dispatch_main(); 在 OSX 服務(wù)程序中使用,iOS 不使用

Demo

 - (void)viewDidLoad
{
    [super viewDidLoad];

//新建線程,然后通知主線程更新界面
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSURL * url = [NSURL URLWithString:@"http://upload.jianshu.io/daily_images/images/myb4DiWJtEcFVgHwgdhh.jpg"];
    NSData * data = [[NSData alloc]initWithContentsOfURL:url];
    UIImage *image = [[UIImage alloc]initWithData:data];
    if (data != nil) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });
    }
});

}

dispatch_queue_create(串行)

dispatch_queue_create("com.my.q",NULL);//name=com.my.q

操作
dispatch_suspend()/dispatch_resume()
dispatch_set_finalizer_f(func)//clean

傳遞參數(shù)

dispatch_set_context(q,voidPtr)
dispatch_get_context(q,voidPtr)

dispatch_once讓單例線程安全

dispatch_once(<#dispatch_once_t *predicate#>, <#^(void)block#>)

 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); // 1

用 dispatch_after 延后工作

 dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // 2
});

dispatch_apply(5, globalQ, ^(size_t index) {
// 執(zhí)行5次
});

dispatch_group_async

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:1];
    NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:3];
    dispatch_group_wait(group,10);
    NSLog(@"group3");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"updateUi");
});

用 dispatch_barrie\dispatch_semaphore 處理讀者與寫者的沖突(臨界區(qū))

  • dispatch_barrier_async
  • dispatch_barrier_sync
dispatch_barrier

用 Dispatch Sources 來監(jiān)視事件(進(jìn)度條)

用dispatch_semaphore(信號量)來處理多個資源

在GCD中有三個函數(shù)是semaphore的操作,分別是:
  dispatch_semaphore_create   創(chuàng)建一個semaphore
  dispatch_semaphore_signal   發(fā)送一個信號
  dispatch_semaphore_wait    等待信號
第一個函數(shù)有一個整形的參數(shù),可以理解為信號的總量
dispatch_semaphore_signal是發(fā)送一個信號,自然會讓信號總量加1
dispatch_semaphore_wait等待信號,當(dāng)信號總量少于0的時候就會一直等待,否則就可以正常的執(zhí)行,并讓信號總量-1

int data = 3;
__block int mainData = 0;
__block dispatch_semaphore_t sem = dispatch_semaphore_create(0);


dispatch_queue_t queue = dispatch_queue_create("StudyBlocks", NULL);

dispatch_async(queue, ^(void) {
    int sum = 0;
    for(int i = 0; i < 5; i++)
    {
        sum += data;
        
        NSLog(@" >> Sum: %d", sum);
    }
    
    dispatch_semaphore_signal(sem);
});
//dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//可以看看取消注釋前后的區(qū)別

for(int j=0;j<5;j++)
{
    mainData++;
    NSLog(@">> Main Data: %d",mainData);
}

用 Dispatch Sources 來監(jiān)視事件(進(jìn)度條)

//1
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0,     dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
    [progressIndicator incrementBy:dispatch_source_get_data(source)];
});
dispatch_resume(source);

dispatch_apply([array count], globalQueue, ^(size_t index) {
// do some work on data at index
dispatch_source_merge_data(source, 1);
});


//2
dispatch_queue_t globalQueue =     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t stdinSource =    dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                                   STDIN_FILENO,
                                                   0,
                                                   globalQueue);
dispatch_source_set_event_handler(stdinSource, ^{
    char buf[1024];
    int len = read(STDIN_FILENO, buf, sizeof(buf));
    if(len > 0)
        NSLog(@"Got data from stdin: %.*s", len, buf);
});
dispatch_resume(stdinSource);


//創(chuàng)建

dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                                   STDIN_FILENO,
                                                   0,
                                                   globalQueue);

DISPATCH_SOURCE_TYPE_TIMER 定時器
DISPATCH_SOURCE_TYPE_MACH_SEND/RECV
DISPATCH_SOURCE_TYPE_PROC 監(jiān)控進(jìn)程
DISPATCH_SOURCE_TYPE_VNODE 監(jiān)控文件系統(tǒng)對象
DISPATCH_SOURCE_TYPE_SIGNAL
DISPATCH_SOURCE_TYPE_READ/WRITE 從描述符中讀取數(shù)據(jù) 向描述符中寫入字符

//配置
dispatch_source_set_event_handler(stdinSource, ^{...}]
dispatch_source_set_event_get_data/handle/mask(stdinSource)
dispatch_source_set_cancel_handler(stdinSource, ^{...})
//啟動
dispatch_resume(stdinSource, ^{...})

//撤銷
dispatch_source_cancel(stdinSource)

計(jì)時器Demo

__block int timeout=300; //倒計(jì)時時間
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒執(zhí)行
dispatch_source_set_event_handler(_timer, ^{
    if(timeout<=0){ //倒計(jì)時結(jié)束,關(guān)閉
        dispatch_source_cancel(_timer);

        dispatch_async(dispatch_get_main_queue(), ^{
            
          
        });
    }else{
        int minutes = timeout / 60;
        int seconds = timeout % 60;
        NSString *strTime = [NSString stringWithFormat:@"%d分%.2d秒后重新獲取驗(yàn)證碼",minutes, seconds];
        dispatch_async(dispatch_get_main_queue(), ^{
           
            self.lbTimer.text = strTime;
         
        });
        timeout--;
        
    }  
});  
dispatch_resume(_timer);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容