iOS 多執行緒的四種技術方案

共田君發表於2019-03-03

iOS 多執行緒的四種技術方案

image
image

pthread 實現多執行緒操作

程式碼實現:

void * run(void *param)
{
    for (NSInteger i = 0; i < 1000; i++) {
        NSLog(@"---buttonclick---%zd---%@", i, [NSThread currentThread]);
    }

    return NULL;
}

@implementation ViewController

- (IBAction)clickButton:(id)sender {
    // 定義一個執行緒
    pthread_t thread;
    // 建立一個執行緒  (參1)pthread_t *restrict:建立執行緒的指標,(參2)const pthread_attr_t *restrict:執行緒屬性  (參3)void *(*)(void *):執行緒執行的函式的指標,(參4)void *restrict:null
    pthread_create(&thread, NULL, run, NULL);
    // 何時回收執行緒不需要你考慮
    pthread_t thread2;
    pthread_create(&thread2, NULL, run, NULL);
}複製程式碼

NSThread實現多執行緒

一個 NSThread 物件就代表一條執行緒

######建立執行緒的多種方式

  1. 第一種方式:先建立再啟動執行緒
    // 建立執行緒
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"jack"];
    // 執行緒啟動了,事情做完了才會死, 一個NSThread物件就代表一條執行緒
    [thread start];
  2. 第二種:直接建立並啟動執行緒
    // 直接建立並啟動執行緒
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"jack"];
  3. 第三種:
    // 直接建立並啟動執行緒
    [self performSelectorInBackground:@selector(run:) withObject:@"jack"];
    // 使執行緒進入阻塞狀態
    [NSThread sleepForTimeInterval:2.0];
#pragma mark - 執行run方法
- (void)run:(NSString *)param
{
    // 當前執行緒是否是主執行緒
    for (NSInteger i = 0; i < 100; i++) {
        NSLog(@"---%@---%zd---%d", [NSThread currentThread], i,  [NSThread isMainThread]);
    }
}複製程式碼

方法2和方法3的優點:快捷
方法1的優點:可以輕鬆拿到執行緒
執行緒間通訊
執行緒間通訊的體現
1個執行緒傳遞資料給另1個執行緒
在1個執行緒中執行完特定任務後,轉到另1個執行緒繼續執行任務
執行緒間通訊的常用方法:小程式圖片下載

-(UIImageView *)imageView
{
    if (!_imageView) {
        _imageView = [UIImageView new];
        _imageView.frame = CGRectMake(0, 0, 300, 300);
        _imageView.center = self.view.center;
        [self.view addSubview:_imageView];
    }
    return _imageView;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    //監聽執行緒結束的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_threadexit:) name:NSThreadWillExitNotification object:nil];

    // Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //第一種方式:先建立再啟動執行緒
    // 建立執行緒
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"booob"];

    // 執行緒啟動了,事情做完了才會死, 一個NSThread物件就代表一條執行緒
    [thread start];

    //第二種:直接建立並啟動執行緒
    // 直接建立並啟動執行緒
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"wang"];


    //第三種:
    // 直接建立並啟動執行緒
    [self performSelectorInBackground:@selector(run:) withObject:@"wang000"];
    // 使執行緒進入阻塞狀態
    [NSThread sleepForTimeInterval:2.0];


    //例子
    // 獲取圖片的url
    NSURL *url = [NSURL URLWithString:@"https://pages.github.com/images/slideshow/yeoman.png"];
    // 另開1條執行緒 object用於資料的傳遞
    NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(downLoadWithURL:) object:url];
    thread3.name = @"downloadimage...";
    // 由於下面下載圖片的耗時太長,應開啟執行緒來完成
    [thread3 start];

}
#pragma mark - 執行run方法
- (void)run:(NSString *)param
{
    // 當前執行緒是否是主執行緒
    for (NSInteger i = 0; i < 10; i++) {
        NSLog(@"---%@---%zd---%d", [NSThread currentThread], i,  [NSThread isMainThread]);
    }

}

//執行緒直接的互動
// 下載圖片
- (void)downLoadWithURL:(NSURL *)url
{
    NSLog(@"%s ,%s %@",__FILE__,__FUNCTION__, [NSThread currentThread]);
    // 下載圖片
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 生成圖片
    UIImage *image = [UIImage imageWithData:data];
    // 返回主執行緒顯示圖片
    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}

//處理執行緒結束事件
-(void)handle_threadexit:(NSNotification *)notify
{
    NSThread  * thread = (NSThread *)notify.object;
    NSLog(@"+++++++++++++++ 執行緒 %@ 結束 ++++++++++++",thread.name);
}複製程式碼

TIPS: 擴充,執行緒結束的通知
以上下載圖片方式使用執行緒已經過時了,開發中我們操作執行緒大多都使用 GCD 和 NSOperation 來實現多執行緒操作。

——————————————————————————————————————————

GCD 是如何實現多執行緒的

  • GCD 實現多執行緒
  • GCD 簡介
  • GCD 全稱是Grand Central Dispatch,可譯為“超級厲害的中樞排程器”,GCD 是蘋果公司為多核的並行運算提出的解決方案, GCD會自動利用更多的 CPU 核心(比如雙核、四核)來開啟執行緒執行任務,GCD 會自動管理執行緒的生命週期(建立執行緒、排程任務、銷燬執行緒),不需要我們程式設計師手動管理記憶體。

  • 任務和佇列

  • 任務:在同步函式和非同步函式中執行
  • 佇列:用來存放任務(併發 序列)

images
images

GCD會自動將佇列中的任務取出,放到對應的執行緒,任務的取出遵循FIFO,即先入先出佇列,First Input First Output 的縮寫。
先進入的任務先完成並結束,再執行後面的任務。
同步函式和非同步函式,併發佇列和序列佇列
用同步的方式執行任務:在當前執行緒中可立即執行任務,不具備開啟執行緒的能力
用非同步的方式執行任務:在當前執行緒結束時執行任務,具備開啟新的執行緒的能力

  • 併發佇列:允許多個任務同時執行

  • 序列佇列:一個任務執行完畢後,再執行下一個任務

建立併發/序列佇列程式碼:

- (void)viewDidLoad {
    [super viewDidLoad];
      dispatch_queue_t queue = dispatch_get_main_queue();
    // 建立序列佇列  serial 序列  concurrent併發
    queueSerial = dispatch_queue_create("searial.whenbar.com", DISPATCH_QUEUE_SERIAL);

    //建立並行佇列
    // 參1:const char *label 佇列名稱
    // 參2:dispatch_queue_attr_t attr 佇列型別
    queueConcurrent = dispatch_queue_create("concurrent.whenbar.com", DISPATCH_QUEUE_CONCURRENT);

}
//1 獲得主佇列
-(void)runqueueMain
{
    // 獲取主佇列  在主佇列中的任務都會在主執行緒中執行。
    dispatch_queue_t queueMain = dispatch_get_main_queue();

}

//2. 建立序列佇列
-(void)runqueueSerial
{

    // GCD同步函式序列佇列(立即執行,當前執行緒)
    // 參1: dispatch_queue_t queue 佇列
    // 參2: 任務
    dispatch_sync(queueSerial, ^{
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
        }
    });


    // 非同步函式序列佇列 (另開執行緒,多個任務按順序執行)
    dispatch_async(queueSerial, ^{
        dispatch_async(queueSerial, ^{
            for (NSInteger i = 0; i < 10; i++) {
                NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
            }
        });
        dispatch_async(queueSerial, ^{
            for (NSInteger i = 0; i < 10; i++) {
                NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
            }
        });
        dispatch_async(queueSerial, ^{
            for (NSInteger i = 0; i < 10; i++) {
                NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
            }
        });
    });

}



//3. 建立併發佇列
-(void)runqueueConcurrent
{
    // 同步函式並行佇列(立即執行,當前執行緒)
    dispatch_sync(queueConcurrent, ^{
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
        }
    });
    // 非同步函式並行佇列 (另開執行緒,多個任務一起執行)
    dispatch_async(queueConcurrent, ^{
        dispatch_async(queueConcurrent, ^{
            for (NSInteger i = 0; i < 5; i++) {
                NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
            }
        });
        dispatch_async(queueConcurrent, ^{
            for (NSInteger i = 0; i < 6; i++) {
                NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
            }
        });
        dispatch_async(queueConcurrent, ^{
            for (NSInteger i = 0; i < 7; i++) {
                NSLog(@"~~~%ld %@",i, [NSThread currentThread]);
            }
        });
    });
}

//4. 建立全域性佇列
-(void)runqueueGlobal
{
    // 獲取全域性佇列 全域性佇列是併發佇列
    // 參1:佇列的優先順序
    // 參2:0(以後可能用到的引數)//#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高\
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 預設(中)\
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低\
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 後臺
    dispatch_queue_t queueGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

}

// 主佇列:(任何一個任務只要在主佇列中,都會加入到主執行緒的佇列中執行)複製程式碼

TIPS: 注意
使用sync函式(同步函式)往當前序列佇列中新增任務,會卡住當前的序列佇列
解釋:使用同步函式新增任務 A 到序列佇列,說明要在當前序列佇列立即執行任務 A ,任務 A 執行完後,才會執行任務 A 後面的程式碼。但當前佇列是序列佇列,也就是說任務 A 必須等到當前序列佇列中正在執行的任務 B 完成之後才能執行,因此又必須先執行任務 A 中立即執行任務,又要必須等到任務 B 執行完以後才能執行下一個任務,所以就會卡死。你等我,我等你,誰也無法執行。

####GCD實現執行緒通訊
小專案:下載圖片

程式碼如下:

// 獲取圖片的url
NSURL *url = [NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"];

// 開啟執行緒下載圖片
dispatch_queue_t queue = dispatch_queue_create("111", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 下載完成後返回主執行緒顯示圖片
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageView.image = image;
    });
});複製程式碼

————————————————————————————————————————

GCD其他常用函式

image
image

//----------------- 佇列組 -----------------------------
//佇列組可以將很多佇列新增到一個組裡,這樣做的好處是,當這個組裡所有的任務都執行完了,佇列組會通過一個方法通知我們。下面是使用方法,這是一個很實用的功能。
-(void)rungroup
{
    //1.建立佇列組
    dispatch_group_t group=dispatch_group_create();
    //2.建立佇列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    //3.多次使用佇列組的方法執行任務, 只有非同步方法
    //3.1.執行3次迴圈
    dispatch_group_async(group,queue,^{
        for (NSInteger i = 0; i< 3; i++){
            NSLog(@"group-01 - %@", [NSThread currentThread]);
        }
    });
    //3.2.主佇列執行8次迴圈
    dispatch_group_async(group, dispatch_get_main_queue(), ^{
        for (NSInteger i=0;i<8;i++) {
            NSLog(@"group-02 - %@", [NSThread currentThread]);
        }
    });
    //3.3.執行5次迴圈
    dispatch_group_async(group, queue, ^{
        for(NSInteger i=0;i<5;i++) {
            NSLog(@"group-03 - %@", [NSThread currentThread]);
        }
    });
    //4.都完成後會自動通知
    dispatch_group_notify(group,dispatch_get_main_queue(),^{
        NSLog(@"完成 - %@", [NSThread currentThread]);
    });
}複製程式碼

dispatch_barrier 柵欄

// 1.barrier : 在barrier前面的先執行,然後再執行barrier,然後再執行barrier後面的 barrier的queue不能是全域性的併發佇列
dispatch_queue_t queue = dispatch_queue_create("11", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    for (int i = 0;  i < 100; i++) {
        NSLog(@"%@--1", [NSThread currentThread]);
    }
});

dispatch_async(queue, ^{
    for (int i = 0;  i < 100; i++) {
        NSLog(@"%@--2", [NSThread currentThread]);
    }
});

dispatch_barrier_async(queue, ^{
    for (int i = 0;  i < 100; i++) {
        NSLog(@"%@--3", [NSThread currentThread]);
    }
});

dispatch_async(queue, ^{
    for (int i = 0;  i < 100; i++) {
        NSLog(@"%@--4", [NSThread currentThread]);
    }
});

//    dispatch_after 延遲執行
    // 延遲執行
    // 方法1
    [self performSelector:@selector(run:) withObject:@"引數" afterDelay:2.0];

    // 方法2
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        for (NSInteger i = 0; i < 100; i++) {
            NSLog(@"%@", [NSThread currentThread]);
        }
    });

    // 方法3
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run:) userInfo:nil repeats:NO];

dispatch_once 整個程式執行中執行一次
// 整個程式中只執行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 一次性程式碼
});複製程式碼

思考題:以下函式輸出的結果是什麼?

image
image

以下的程式碼輸出是什麼呢

image
image

作用:實現某個類的單例物件

單例模式:在整個應用程式中,共享一份資源(這份資源只需要建立初始化1次)

//--------------單例模式--------------------

#if __has_feature(objc_instancetype)
#undef    AS_SINGLETON
#define AS_SINGLETON( ... ) \
- (instancetype)sharedInstance; \
+ (instancetype)sharedInstance;

#undef    DEF_SINGLETON
#define DEF_SINGLETON \
- (instancetype)sharedInstance \
{ \
return [[self class] sharedInstance]; \
} \
+ (instancetype)sharedInstance \
{ \
static dispatch_once_t once; \
static id __singleton__; \
dispatch_once( &once, ^{ __singleton__ = [[self alloc] init]; } ); \
return __singleton__; \
}
#undef    DEF_SINGLETON
#define DEF_SINGLETON( ... ) \
- (instancetype)sharedInstance \
{ \
return [[self class] sharedInstance]; \
} \
+ (instancetype)sharedInstance \
{ \
static dispatch_once_t once; \
static id __singleton__; \
dispatch_once( &once, ^{ __singleton__ = [[self alloc] init]; } ); \
return __singleton__; \
}

#else    // #if __has_feature(objc_instancetype)
#undef    AS_SINGLETON
#define AS_SINGLETON( __class ) \
- (__class *)sharedInstance; \
+ (__class *)sharedInstance;

#undef    DEF_SINGLETON
#define DEF_SINGLETON( __class ) \
- (__class *)sharedInstance \
{ \
return [__class sharedInstance]; \
} \
+ (__class *)sharedInstance \
{ \
static dispatch_once_t once; \
static __class * __singleton__; \
dispatch_once( &once, ^{ __singleton__ = [[[self class] alloc] init]; } ); \
return __singleton__; \
}

#endif    // #if __has_feature(objc_instancetype)

#import "gcdfunViewController.h"

#pragma mark - 單例模式? ?
@interface Person:NSObject
//+ (instancetype)shareInstance;
AS_SINGLETON(Person)
@end

@implementation Person
DEF_SINGLETON(Person)

/*
+ (instancetype)shareInstance
{
    static id _person;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _person = [[super alloc] init];
    });
    return _person;
}
*/
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static id _person;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _person = [super allocWithZone:zone];
    });
    return _person;
}
- (id)copy
{
    return [Person sharedInstance];
}

@end複製程式碼

開發中一般自定義成巨集,比較方便,一行程式碼搞定。

dispatch_apply 快速迭代

//示例小程式:將一個資料夾中的圖片剪下到另一個資料夾

-(void)testdispatch_apply
{
    // 將圖片剪下到另一個資料夾裡
    NSString *from = @"/Users/Ammar/Pictures/桌布";
    NSString *to = @"/Users/Ammar/Pictures/to";
    NSFileManager *manager = [NSFileManager defaultManager];
    NSArray *subPaths = [manager subpathsAtPath:from];

    // 快速迭代
    dispatch_apply(subPaths.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
        NSLog(@"%@ - %zd", [NSThread currentThread], index);
        NSString *subPath = subPaths[index];
        NSString *fromPath = [from stringByAppendingPathComponent:subPath];
        NSString *toPath = [to stringByAppendingPathComponent:subPath];

        // 剪下
        [manager moveItemAtPath:fromPath toPath:toPath error:nil];
        NSLog(@"%@---%zd", [NSThread currentThread], index);
    });

    //作用是把指定次數指定的block新增到queue中, 第一個引數是迭代次數,第二個是所在的佇列,第三個是當前索引,dispatch_apply可以利用多核的優勢,所以輸出的index順序不是一定的
    dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
        NSLog(@"dispatch_apply %zd",index);
    });
    /*輸出結果 無序的
     2016-02-15 10:15:21.229 多執行緒[4346:48391] dispatch_apply 0
     2016-02-15 10:15:21.229 多執行緒[4346:48784] dispatch_apply 1
     2016-02-15 10:15:21.230 多執行緒[4346:48830] dispatch_apply 2
     2016-02-15 10:15:21.230 多執行緒[4346:48391] dispatch_apply 4
     2016-02-15 10:15:21.230 多執行緒[4346:48829] dispatch_apply 3
     2016-02-15 10:15:21.231 多執行緒[4346:48391] dispatch_apply 6
     2016-02-15 10:15:21.231 多執行緒[4346:48391] dispatch_apply 9
     2016-02-15 10:15:21.230 多執行緒[4346:48784] dispatch_apply 5
     2016-02-15 10:15:21.231 多執行緒[4346:48829] dispatch_apply 8
     2016-02-15 10:15:21.231 多執行緒[4346:48830] dispatch_apply 7
     */
}複製程式碼

dispatch_group 佇列組
示例小程式:需求下載圖片1 下載圖片2 將圖片1和圖片2合成新的圖片

// 建立佇列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 建立組
dispatch_group_t group = dispatch_group_create();

// 用組佇列下載圖片1
dispatch_group_async(group, queue, ^{
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]];
    self.image1 = [UIImage imageWithData:data];
    NSLog(@"1%@", [NSThread currentThread]);
});

// 用組佇列下載圖片2
dispatch_group_async(group, queue, ^{
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]];
    self.image2 = [UIImage imageWithData:data];
    NSLog(@"2%@", [NSThread currentThread]);
});

// 將圖片1和圖片2合成一張圖片
dispatch_group_notify(group, queue, ^{
    CGFloat imageW = self.imageView.bounds.size.width;
    CGFloat imageH = self.imageView.bounds.size.height;

    // 開啟點陣圖上下文
    UIGraphicsBeginImageContext(self.imageView.bounds.size);

    // 畫圖
    [self.image1 drawInRect:CGRectMake(0, 0, imageW * 0.5, imageH)];
    [self.image2 drawInRect:CGRectMake(imageW * 0.5, 0, imageW * 0.5, imageH)];

    // 將圖片取出
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    // 關閉圖形上下文
    UIGraphicsEndImageContext();

    // 在主執行緒上顯示圖片
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageView.image = image;
    });
    NSLog(@"3%@", [NSThread currentThread]);
});複製程式碼

####GCD定時器
GCD定時器不受Mode影響因此比NSTimer要準確

#pragma mark - 定時器
//做定時器或倒數計時
-(IBAction)buttonTap:(id)sender
{
    UIButton * button = (UIButton *)sender;
    button.enabled = NO;
    // 1.建立一個定時器源

    // 參1:型別定時器
    // 參2:控制程式碼
    // 參3:mask傳0
    // 參4:佇列  (注意:dispatch_source_t本質是OC物件,表示源)
    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);

    // 嚴謹起見,時間間隔需要用單位int64_t,做乘法以後單位就變了
    // 下面這句程式碼表示回撥函式時間間隔是多少
    int64_t interval = (int64_t)(1.0 * NSEC_PER_SEC);

    // 如何設定開始時間 CGD給我們了一個設定時間的方法
    // 參1:dispatch_time_t when 傳一個時間, delta是增量

    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)); // 從現在起0秒後開始

    // 參1:timer
    // 參2:開始時間
    // 參3:時間間隔
    // 參4:傳0 不需要   DISPATCH_TIME_NOW 表示現在 GCD 時間用 NS 表示
    dispatch_source_set_timer(timer, start, interval, 0);

    __block int count = 60;

    // 3.設定回撥(即每次間隔要做什麼事情)
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"----------------%@", [NSThread currentThread]);
        // 如果希望做5次就停掉
        count -- ;
        dispatch_async(dispatch_get_main_queue(), ^{
            if (count == 0) {
                dispatch_source_cancel(timer);
                [button setTitle:@"點選倒數計時" forState:UIControlStateNormal];
                button.enabled = YES;
            }
            else
            {
                [button setTitle:[NSString stringWithFormat:@"%d",count] forState:UIControlStateNormal];
                [button setTitle:[NSString stringWithFormat:@"%d",count] forState:UIControlStateDisabled];
            }
        });
    });
    // 4.啟動定時器  (恢復)
    dispatch_resume(timer);
}複製程式碼

gcd 還有一些其他的函式 下次再深入!

————————————————————————————————————————————————————————————————————————————————
講完 GCD 就該講講 NSOperation,它是 GCD 的物件導向的封裝,使用起來也更方便,

  • NSOperation實現多執行緒
  • NSOperation是個抽象類,並不具備封裝操作的能力,必須使用它的子類

  • NSInvocationOperation

  • NSBlockOperation
    自定義子類繼承NSOperation,實現內部相應的方法
    使用 NSOperation 實現多執行緒的步驟:

  • 建立任務 NSOperation 物件

  • 建立 NSOperationQueue 佇列
  • 將任務 NSOperation 物件 add 到 NSOperationQueue 佇列中去
  • NSInvocationOperation
    程式碼如下:
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

[op start];複製程式碼

注意:預設情況下,呼叫了start方法後並不會開一條新的執行緒去執行,而是在當前執行緒同步執行操作,只有將 NSOperation 放到一個 NSOperationQueue 中,才會非同步執行操作

  • NSBlockOperation
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
      // 在主執行緒
        NSLog(@"下載1------%@", [NSThread currentThread]);
    }]; 
    // 新增額外的任務(在子執行緒執行),封裝數大於1才會非同步執行
    [op addExecutionBlock:^{
      NSLog(@"下載2------%@", [NSThread currentThread]);
    }];複製程式碼

自定義Operation:需要實現- (void)main方法,需要做的事情放在mian方法中

  • NSOperationQueue
    使用NSOperationQueue建立佇列:主佇列和全域性佇列
// 建立一個其他佇列(包括序列佇列和併發佇列) 放到這個佇列中的NSOperation物件會自動放到子執行緒中執行

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

// 建立一個主佇列,放到這個佇列中的NSOperation物件會自動放到子執行緒中執行
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

// 表示併發數量:即同時執行任務的最大數。
queue.maxConcurrentOperationCount = 1;
佇列的取消、暫停、恢復:
// NSOpertion的 - cancel 方法也可以停止單個操作
- (void)cancelAllOperations; 
// YES代表暫停佇列,NO代表恢復佇列
- (void)setSuspended:(BOOL)b;
新增依賴
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download1 -------------- %@", [NSThread currentThread]);
}];

NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download2 -------------- %@", [NSThread currentThread]);
}];

NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download3 -------------- %@", [NSThread currentThread]);
}];

// 新增依賴: block1 和 block2執行完後 再執行 block3  block3依賴於block1和block2

// 給block3新增依賴 讓block3在block1和block2之後執行
[block3 addDependency:block1];
[block3 addDependency:block2];

[queue addOperation:block1];
[queue addOperation:block2];
[queue addOperation:block3];
注意:不能迴圈依賴,但可以跨佇列依賴,不管NSOperation物件在哪個佇列。只要是兩個NSOperation物件就可以依賴
執行緒間通訊
示例:下載圖片

// 下載圖片 operation實現執行緒間通訊
[[[NSOperationQueue alloc] init] addOperation:[NSBlockOperation blockOperationWithBlock:^{
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://7xjanq.com1.z0.glb.clouddn.com/6478.jpg"]]];

    // 返回主執行緒
    [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{
        self.imageView.image = image;
    }]];

}]];複製程式碼

示例:下載圖片1和圖片2 併合成圖片

-(void)demo_combinenetworkimage
{
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];

    __block UIImage * image1;
    NSBlockOperation * block1 = [NSBlockOperation blockOperationWithBlock:^{
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img1.gtimg.com/15/1513/151394/15139471_980x1200_0.jpg"]];
        image1 = [UIImage imageWithData:data];
        NSLog(@"下載圖片1%@", [NSThread currentThread]);
    }];

    __block UIImage * image2;
    NSBlockOperation * block2 = [NSBlockOperation blockOperationWithBlock:^{
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img1.gtimg.com/15/1513/151311/15131165_980x1200_0.png"]];
        image2 = [UIImage imageWithData:data];
        NSLog(@"下載圖片2%@", [NSThread currentThread]);
    }];


    NSBlockOperation * block3 = [NSBlockOperation blockOperationWithBlock:^{

        CGFloat imageW = self.imageView.bounds.size.width;
        CGFloat imageH = self.imageView.bounds.size.height;

        // 開啟點陣圖上下文
        UIGraphicsBeginImageContext(self.imageView.bounds.size);

        // 畫圖
        [image1 drawInRect:CGRectMake(0, 0, imageW * 0.5, imageH)];
        [image2 drawInRect:CGRectMake(imageW * 0.5, 0, imageW * 0.5, imageH)];

        // 將圖片取出
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

        // 關閉圖形上下文
        UIGraphicsEndImageContext();

        // 在主執行緒上顯示圖片
        [[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"合成圖片 %@", [NSThread currentThread]);
            self.imageView.image = image;
        }]];
    }];

    [block3 addDependency:block1];
    [block3 addDependency:block2];

    [queue addOperation:block1];
    [queue addOperation:block2];
    [queue addOperation:block3];

}複製程式碼
  • 應用:SDWebImage 框架的底層主要功能實現就是基於多執行緒,使用多執行緒,我們可以實現小圖片的多圖片下載。這裡的邏輯其實是比較複雜的
    實現小圖片的多圖片下載思路:

    TIPS: 以上就是一些主要方法, 下面還有一些常用方法需要大家注意:

    NSOperation
    BOOL executing; //判斷任務是否正在執行
    BOOL finished; //判斷任務是否完成
    void (^completionBlock)(void); //用來設定完成後需要執行的操作
    
    - (void)cancel; //取消任務
    - (void)waitUntilFinished; //阻塞當前執行緒直到此任務執行完畢
    NSOperationQueue 
    
    NSUInteger operationCount; //獲取佇列的任務數
    - (void)cancelAllOperations; //取消佇列中所有的任務
    - (void)waitUntilAllOperationsAreFinished; //阻塞當前執行緒直到此佇列中的所有任務執行完畢
    [queue setSuspended:YES]; // 暫停queue
    [queue setSuspended:NO]; // 繼續queue複製程式碼

###執行緒同步

所謂執行緒同步就是為了防止多個執行緒搶奪同一個資源造成的資料安全問題,所採取的一種措施。當然也有很多實現方法,請往下看:

互斥鎖 :給需要同步的程式碼塊加一個互斥鎖,就可以保證每次只有一個執行緒訪問此程式碼塊。
@synchronized(self) {
//需要執行的程式碼塊
}

  • 原創部落格地址 www.jianshu.com/p/b91b42235…
  • 第一次認真寫一篇博文希望大家多多批評指正
  • 如有問題歡迎大家關注我的微信 ExtremeFruit
  • 如果有興趣歡迎下載多執行緒DEMO
  • 喜歡就點個讚唄!

相關文章