iOS 多線程的四種技術方案

iOS 多線程的四種技術方案javascript

image
image

pthread 實現多線程操做java

代碼實現:ios

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 對象就表明一條線程git

######建立線程的多種方式程序員

  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個線程繼續執行任務
線程間通訊的經常使用方法:小程序圖片下載github

-(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 會自動管理線程的生命週期(建立線程、調度任務、銷燬線程),不須要咱們程序員手動管理內存。bash

  • 任務和隊列微信

  • 任務:在同步函數和異步函數中執行
  • 隊列:用來存聽任務(併發 串行)

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
  • 喜歡就點個讚唄!
相關文章
相關標籤/搜索