1. Block
1.1 block的基础语法
1.2 block的内存管理
- NSStackBlock:位于栈内存,函数返回后Block将无效;
1.2.1. 不引用任何外部变量
1.2.2 引用外部变量的block
1.3 定义和赋值block属性
1.4 block的循环引用
2. GCD
2.1 将任务添加到队列
2.2 线程间通讯
2.3 实现异步下载网络图片
3. GCD任务和队列
3.1 Serial Dispatch Queue
3.1.1 创建队列
3.1.2 串行队列+同步任务
3.1.3 串行队列+异步任务
3.2 Concurrent Dispath Queue
3.2.1 创建队列
3.2.2 并发队列+同步任务
3.3.2 并发队列+异步任务
3.3 主队列
3.3.1 创建主队列
3.3.2 主队列+同步任务
3.3.3 主队列+异步任务
3.4 全局队列
3.4.1 全局同步
3.4.2 全局异步
3.5 总结
4. GCD阻塞(Barrier)
- 当self.view = nil的时候,会调用loadView方法。
1. Block
- block最常用来逆向传值,又叫做回调
- block是一段代码块
- 是匿名函数,所以是C的
- block代码块里面的代码只有被调用的时候才会被执行。
- block可以当作参数传递
- block可以作为属性
需要注意的是由于Objective-C在iOS中不支持GC机制,使用Block必须自己管理内存,而内存管理正是使用Block坑最多的地方,错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放导致crash。 Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境。
- Block其实包含两个部分内容:
- Block执行的代码。这是在编译的时候已经生成好的;
- 一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的变量的值建立拷贝一个副本到栈上。
1.1 block的基础语法
- block的类型是
void(^)()
,和int
一样。 - block的名字要放在小尖尖里面。
void(^demo)()
这就是一个叫做demo的无参数的block。 - block的定义
// 无参无返回
void(^block)();
// 无参有返回
int(^block1)();
// 有参有返回
int(^block1)(int number);
block的定义可以借助
inlineBlock
系统提供的代码块进行。
1.2 block的内存管理
- 无论是ARC还是MRC,只要block没有访问外部变量,block始终在全局区
- MRC情况下
- block如果访问外部变量,block在栈里
- 不能对block使用retain,否则不能保存在堆里
- 只有使用copy,才能放到堆里
-
ARC情况下
- block如果访问外部变量,block在堆里
- block可以使用copy和strong,并且block是一个对象
-
NSMallocBlock
:表示block在堆区 NSGlobalBlock
:类似函数,位于text段;
- NSStackBlock
:位于栈内存,函数返回后Block将无效;
为什么block需要使用copy?
ARC环境下栈区上面的对象,随时有可能被销毁。所以block在ARC下放在栈区很危险,很有可能在没有调用之前就被销毁了。
使用了copy,访问了局部变量之后,block就从栈区到了堆区。
在ARC环境下,使用strong和copy的效果是一毛一样!
但是把block作为属性的时候,老程序员还是习惯用copy。
单独把某个文件变成MRC环境。
1.2.1. 不引用任何外部变量
- (void)blockDemo {
void(^myBlock)() = ^ {
NSLog(@"hello world");
};
NSLog(@"%@", myBlock);
}
- 不引用任何外部变量的 block 保存在全局区 NSGlobalBlock
- 如果Block没有引用外部变量,那么这个Block的函数体内部包装的代码都不会发生变化,而且执行效率高,保存在全局区;(类似不变的字符串)
- 但是实际开发中,不引用外部变量的 block 几乎是不存在的
1.2.2 引用外部变量的block
- (void)blockDemo {
int i = 10;
void(^myBlock)() = ^ {
NSLog(@"hello world %zd", i);
};
NSLog(@"%@", myBlock);
}
引用外部变量的 block 保存在 :
- ARC : 堆区 NSMallocBlock
- MRC : 栈区 NSStackBlock
因此 : 在定义 block 属性时应该使用 copy 关键字,将 block 从栈区复制到堆区
1.3 定义和赋值block属性
// 定义 block 属性
property (nonatomic, copy) void (^demoBlock)();
- (void)blockDemo2 {
int i = 10;
void(^myBlock)() = ^ {
NSLog(@"hello world %zd", i);
};
NSLog(@"%@", myBlock);
// 错误的写法,不会调用 setter 方法,MRC下,无法拷贝到堆区
// _demoBlock = myBlock;
// 正确的写法,调用 setter 方法,并且对 block 进行 copy
self.demoBlock = myBlock;
NSLog(@"%@", self.demoBlock);
}
1.4 block的循环引用
- block循环引用
//定义属性
@property (nonatomic, copy) void (^demoBlock)();
//在 viewDidLoad 中记录 block
- (void)viewDidLoad {
[super viewDidLoad];
void(^block)() = ^ {
NSLog(@"%@", self.view);
};
// 记录 block
self.demoBlock = block;
}
上述代码发生循环引用的原因:
- viewController引用了block属性。也就是说所有声明的属性,都被控制器做了强引用。
- block内部代码又引用了self (viewcontroller)
-
解决循环引用的办法:
-
天坑!!!
- 在block里面一定不要直接使用成员变量,因为所有的成员变量都已经被controller强引用了。
- 如果需要引用成员变量,请使用weakself
__weak typeof(self) weakself = self
2. GCD
- 全称是Grand Central Dispatch
-
纯C语言的,提供了非常多强大的函数.
-
GCD的优势:
- 是苹果为多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(例如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
-
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码.
-
GCD的核心:将任务添加到队列
- 任务:执行的操作。只有两种方式:同步、异步。
-
队列:用来存放任务
-
GCD使用的两个步骤:
- 创建任务:任务都是使用block封装的
-
将任务添加到队列中
- GCD会自动将队列中的任务取出来,放到对应线程中执行
- 任务的去处遵循first in first out原则
-
GCD中所有的函数,都是以
dispatch
开头的。 -
异步具备开启新线程的能力,也具备跳过当前代码继续往下执行的能力。
- 同步不具备开启新线程的能力,也不具备跳过当前代码继续往下执行的能力。
名称 | 开启新线程的能力 | 跳过当前代码继续往下执行的能力 |
---|---|---|
异步 | 有 | 有 |
同步 | NULL | NULL |
2.1 将任务添加到队列
// 队列+任务
- (void)gcdDemo1
{
// 全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 任务
void (^task)() = ^ {
NSLog(@"%@",[NSThread currentThread]);
};
// 同步任务
dispatch_sync(queue, task);
// 异步任务 : 每次执行任务的线程不一定是一样的
dispatch_async(queue, task);
/*也可以简写
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@",[NSThread currentThread]);
});
*/
NSLog(@"end");
}
2.2 线程间通讯
/// 线程间通信
- (void)gcdDemo3
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"下载中... %@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"下载完成 %@",[NSThread currentThread]);
});
});
}
2.3 实现异步下载网络图片
- (void)downloadImage
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 获取网络图片地址
NSURL *url = [NSURL URLWithString:@"http://photocdn.sohu.com/20151209/mp47379110_1449633737507_2_th.png"];
// 获取网络图片二进制数据
NSData *data = [NSData dataWithContentsOfURL:url];
// 获取图片对象
UIImage *image = [UIImage imageWithData:data];
// 图片下载完成之后,回到主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 设置图片视图
self.imageView.image = image;
// 图片视图自适应图片的大小
[self.imageView sizeToFit];
// 设置滚动视图
[self.scrollView setContentSize:image.size];
});
});
}
3. GCD任务和队列
- GCD的自定义队列分为两种:串行队列(Serial Dispatch Queue)、并发队列(Concurrent Dispatch Queue)
- GCD 公开有 5 个不同的队列:运行在主线程中的 main queue,3 个不同优先级的后台队列,以及一个优先级更低的后台队列(用于 I/O)。
另外,开发者可以创建自定义队列:串行或者并行队列。自定义队列非常强大,在自定义队列中被调度的所有 block 最终都将被放入到系统的全局队列中和线程池中。
-
串行队列(Serial Dispatch Queue):
- 让任务一个接着一个有序的执行:不管队列里面放的是什么任务.一个任务执行完毕后,再执行下一个任务.
- 同时只能调度一个任务执行.
-
并发队列(Concurrent Dispatch Queue)
- 可以让多个任务并发/同时执行.自动开启多个线程同时执行多个任务.
- 同时可以调度多个任务执行
- 并发队列的并发功能只有内部的任务是异步任务时,才有效.
3.1 Serial Dispatch Queue
- 以先进先出的方式,顺序调度队列中的任务执行
- 无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务
3.1.1 创建队列
// 参数1 : 队列的标示符
// 参数2 : 队列的属性.决定了队列是串行的还是并行的.
dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);
3.1.2 串行队列+同步任务
#pragma mark - 串行队列+同步任务
/*
1. 没有开新线程
2. 循环是顺序打印
3. @"end"最后执行
*/
- (void)gcdDemo1
{
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
NSLog(@"end");
}
3.1.3 串行队列+异步任务
#pragma mark - 串行队列+异步任务
/*
1. 开一条新线程 : 因为队列是顺序调度任务,前一个任务执行完成以后才能调度后面的任务,开一条新线程就够了
2. 循环是顺序打印
3. @"end"不是在最后执行
*/
- (void)gcdDemo2
{
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
NSLog(@"end");
}
3.2 Concurrent Dispath Queue
- 并行同步:不会开启新的线程
3.2.1 创建队列
// 参数1 : 队列的标示符
// 参数2 : 队列的属性.决定了队列是串行的还是并行的.
dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);
3.2.2 并发队列+同步任务
#pragma mark - 并发队列+同步任务
/*
1. 没有开新线程
2. 循环顺序打印
3. @"end"最后执行
*/
- (void)gcdDemo1
{
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
NSLog(@"end");
}
3.3.2 并发队列+异步任务
#pragma mark - 并发队列+同步任务
/*
1. 开启多条新线程
2. 循环无序打印
3. @"end"不是最后执行
*/
- (void)gcdDemo2
{
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
}
NSLog(@"end");
}
3.3 主队列
- 主队列是串行队列的特殊的一种。
- 主队列中的任务,只能在主线程中进行。
- 主队列中的任务,只有当主线程空闲的时候才能执行。
3.3.1 创建主队列
- 主队列是负责在主线程调度任务的,会随着程序启动一起创建,只需要获取不用创建.
dispatch_queue_t queue = dispatch_get_main_queue();
3.3.2 主队列+同步任务
- 会死锁
- Xcode8.1 之后运行的时候会直接崩溃
- 造成死锁的原因是因为主队列是本质上是串行的,要等到依照顺序执行。而同步任务不会产生新的线程,也是按照顺序执行的。这样双方都在互相等待。
//下面代码是会被死锁的代码
- (void)gcdDemo2
{
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"start");
dispatch_sync(queue, ^{
NSLog(@"执行中...%@",[NSThread currentThread]);
});
NSLog(@"end");
}
#pragma mark - 死锁解决办法
// 主队列中的同步任务放进子线程中,不使其阻塞主线程
- (void)gcdDemo3
{
dispatch_async(dispatch_queue_create("ZJ", DISPATCH_QUEUE_CONCURRENT), ^{
NSLog(@"start");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"执行中...%@",[NSThread currentThread]);
});
NSLog(@"end");
});
}
3.3.3 主队列+异步任务
#pragma mark - 主队列+异步任务
/*
1. 执行顺序 : @"start"->@"end"->执行中...
2. 没有开启新线程
*/
- (void)gcdDemo1
{
// 主队列 : 程序一启动就创建好的,不需要创建
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"start");
dispatch_async(queue, ^{
NSLog(@"执行中...%@",[NSThread currentThread]);
});
NSLog(@"end");
}
3.4 全局队列
- 全局队列是并行队列的特殊一种。
// 全局队列,跟主队列一样不需要创建
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
- 开发中最常用的队列之一。另外一个是主队列。
- 是系统为了方便程序员开发提供的。直接
get
就可以得到一个并发队列。
3.4.1 全局同步
- 在当前线程执行,任务依次执行
- (void)gcdDemo
{
// 全局队列,跟主队列一样不需要创建
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
/*
dispatch_sync(queue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
*/
}
NSLog(@"end");
}
3.4.2 全局异步
- 开启多条线程,任务不按顺序执行
#pragma mark - 全局队列
// 执行效果跟并发队列一样的
- (void)gcdDemo
{
// 全局队列,跟主队列一样不需要创建
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
/*
dispatch_sync(queue, ^{
NSLog(@"%d %@",i,[NSThread currentThread]);
});
*/
}
NSLog(@"end");
}
3.5 总结
任务\队列 | 串行队列Serial | 并行队列concurrent | 主队列main | 全局队列global |
---|---|---|---|---|
同步任务sync | 无新线程、串行执行 | 无新线程、串行执行 | 没事会锁死 | 无新线程、串行执行 |
异步任务async | 新线程、串行执行 | 新线程、并行执行 | 无新线程、串行执行 | 新线程、并行执行 |
4. GCD阻塞(Barrier)
注意:
- barrier使用的时候规定要使用自定义的并行队列
- 大部分情况下都是正确的,但是不能绝对信任。