Blocks
Blocks是C语言的扩充功能,是带用自动变量的匿名函数(不带有名称的函数)
1.Block语法
//C语言为例
int countAdd(int count)
{
return count + 1;
}
//标准格式
// ^ 返回值类型 参数列表 表达式
^int (int count)
{
return count + 1;
}
//可省略返回值类型
^(int count)
{
return count + 1;
}
//当传入参数为空时,可省略参数列表
^{
printf("Blocksn");
}
2.Block类型变量
在C语言中,可以将函数的地址赋值给函数指针类型变量
int func(int count)
{
return count + 1;
}
//地址赋值
int (*funcopy)(int) = &func;
将Block赋值为Block类型变量
int (^block)(int) = ^(int count){return count + 1;};
由Block类型变量向Block类型变量赋值
int (^block01)(int) = block;
可以使用typedef简化block代码量
typedef int (^block)(int);
block blk = ^(int a){return a + 1;};
3.Block截获自动变量值
截获瞬间值
int value = 10;
void (^blk) (void) = ^{printf("the value is %dn", value);};
blk();
//change the value
value = 7;
printf("the value is %dn", value);
blk();
4.__block
在 block 中给 block 外的其他变量赋值,需要在该自动变量上附加 __block 说明符
__block int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf("val = %dn", val);
截获 OC 对象,可以调用对象的方法
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id object = [[NSObject alloc] init];
[array addObject:object];
}
5.block本质上也是一个OC对象,它内部也有个isa指针
什么是 isa 指针
每个 Objective-C 对象实例都是指向某块内存数据的指针,而自身的数据就存于该内存地址中
//以NSString为例
NSString *str = @"hello world";
//以通用对象类型id为例
id idType = @"hello world";
id 类型定义如下:
typedef struct objc_object
{
//每个对象结构体的首个成员是Class类的变量
//该变量定义了对象所属的类
Class isa;
} *id;
typedef struct objc_class *Class;
//该结构体存放类的元数据
struct objc_class
{
//该isa指向元类
//元类是指类对象所属的类型
Class isa;
//该super_class定义了本类的超类
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
}
super_class指向父类(确立继承关系)
isa指向元类(描述了实例所属的类)
6.Block存储域
Block
语法执行时, Block
底层会转化成Block的结构体类型
的自动变量存储在栈上
__block变量
初始化时, 会转化成__block变量的结构体类型
的自动变量存储在栈上.
| 名称 | 实质 | | :---------- | :-------------------------- | | Block | 栈上Block的结构体实例 | | block变量 | 栈上block变量的结构体实例 |
Block对象有以下3种类型, 存储在应用程序的不同的内存区域:
| 名称 | 设置对象的存储域 | | ---------------------- | ---------------- | | _NSConcreteStackBlock | 栈 | | _NSConcreteGlobalBlock | 数据域 (.data区) | | _NSConcreteMallocBlock | 堆 |
通常情况下,Block对象存储在_NSConcreteStackBlock
区
Block对象存储在_NSConcreteGlobalBlock
区:
Block出现在全局数据区
Block不使用截获的自动变量,如:
typedef int (^blk_t)(int);
for (int rate = 0; rate < 3; rate ++)
{
blk_t blk = ^(int a){return a + 1;};
}
Block对象存储在_NSConcreteMallocBlock
区:
Block或__block变量从栈上复制到堆上时
- 当ARC有效时,大多数情况下编译器会进行判断,自动将Block从栈上复制到堆上
//举个
typedef int (^blk_t)(int);
blk_t func(int rate)
{
return ^(int count){return rate * count;};
}
//ARC有效时,转换如下
blk_t func(int rate)
{
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
//该函数就是_Block_copy函数
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
Block的副本
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return rate * count;};
//可以调用copy方法
blk = [blk copy];
7.__block变量存储域
Block从栈复制到堆时对__block变量产生的影响
1.如果Block
中使用__block
变量,则当该Block从栈复制到堆时,使用的所有__block
变量也被复制到堆上
2.在多个Block
中使用__block
变量时,__block
变量会随任何一个Block
一起从栈复制到堆,如果还有其他的Block
需复制,则增加__block
变量的引用计数
在Block从栈复制到堆时,以及堆上的Block被废弃时会调用这些函数
什么时候栈上的Block会复制到堆?
调用copy 方法
Block作为函数返回值返回时
将Block赋值给赋有__strong修饰符id类型的类或Block类型成员变量时
说到这里 不得不说说ARC
ARC
是由编译器进行内存管理的,无需手动输入retain
,release
先说说手动管理(MRC
)
自己生成的对象,自己持有
alloc
,new
,copy
,mutableCopy
id obj = [[NSObject alloc] init];
非自己生成的对象,自己也能持有
retain
//对象存在,但自己不持有该对象
id obj = [NSMutableArray array];
[obj retain];
释放对象
release
id obj = [NSMutableArray array];
[obj retain];
[obj release];
自动释放
autorelease
可以人为设定变量的作用域,延迟内存的释放,即延长对象的生命周期,并且在合理的时机释放。
//举个
- (NSObject *)initObject() {
NSObject *obj = [[NSObject alloc] init];
return obj;
}
//如何得到obj???
- (NSObject *)initObject() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSObject *obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
return obj;
}
ARC
下面我们简单看看几个常用修饰符:
__strong
//ARC中,默认的为对象添加__strong
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
__strong
为默认修饰符,表示强引用。修饰的变量在被废弃(变量超出作用域/成员变量所属对象被废弃/变量赋值nil)时,会释放被赋予的对象。
//因为obj为强引用,所以自己持有对象
id __strong obj = [NSMutableArray array];
通过__strong
变量赋值了解__strong
id __strong obj0 = [[NSObject alloc] init]; /*对象A*/
id __strong obj1 = [[NSObject alloc] init]; /*对象B*/
id __strong obj2 = nil;
//此时对象A没被持有,所以释放
//对象B被obj0,obj1持有
obj0 = obj1;
//对象B被obj0,obj1,obj2持有
obj2 = obj0;
//依此释放对象B
obj1 = nil;
obj0 = nil;
obj2 = nil;
__weak
为解决循环引用
而生
先来看看互相强引用的实例:
//举个
@interface Test : NSObject
{
id __strong obj01;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (id)init
{
self = [super init]
return self;
}
- (void)setObject:(id __strong)obj
{
obj01 = obj;
}
@end
//循环引用
{
id test0 = [[Test alloc] init];/*对象A*/
id test1 = [[Test alloc] init];/*对象B*/
[test0 setObject:test1];
[test1 setObject:test0];
}
//自动释放对象A,B
//对象A的obj01持有对象B
//对象B的obj01持有对象A
//内存泄漏!!!
内存泄漏
:应当废弃的对象在超出起生存周期后继续存在
__weak
提供弱引用,弱引用不能持有对象实例
在持有某对象的弱引用时,如果该对象被废弃,则此弱引用被赋值为nil
id obj = [[NSObject alloc] init];
如果解决上面的问题 ???
id __weak obj01;
__unsafe_unretained(__weak的不安全版本)
注意一点:附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象,因此不建议使用
__unsafe_unretained
同__weak
一样,不能持有对象实例(既不持有对象的强引用,也不持有弱引用)
__autoreleasing
ARC下,不能使用autorelease方法,也不能使用NSAutoReleasePool类,但通过新的语法,autorelease功能仍起作用。
/*ARC有效*/
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
GCD
1.GCD简介
GCD
是异步执行任务的技术之一。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。
2.GCD任务和队列
任务: 任务就是你在线程中执行的那段代码,在 GCD 中是放在 block 中的。
执行任务有两种方式:
1.同步执行(dispatch_sync)
- 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
- 只能在当前线程中执行任务,不具备开启新线程的能力。
2.异步执行(dispatch_async)
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
- 可以在新的线程中执行任务,具备开启新线程的能力。
队列(FIFO):
串行队列(Serial Dispatch Queue):
- 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
并发队列(Concurrent Dispatch Queue):
- 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
- 并发队列 的并发功能只有在异步(dispatch_async)函数下才有效
3.GCD 的使用步骤
1.创建一个队列
2.将任务追加到任务的等待队列中
队列的创建方法/获取方法:
1.可以使用dispatch_queue_create
来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL
表示串行队列,DISPATCH_QUEUE_CONCURRENT
表示并发队列。
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("com.demo.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
2.对于串行队列,GCD 默认提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)
- 所有放在主队列中的任务,都会放到主线程中执行。
- 可使用
dispatch_get_main_queue()
获得主队列。
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
3.对于并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)
- 可以使用
dispatch_get_global_queue
来获取。需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT
。第二个参数暂时没用,用0即可。
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
任务的创建方法:
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
全局并发队列可以作为普通并发队列来使用。但是主队列有点特殊,所以一共有6种组合方式:
同步执行 + 并发队列
异步执行 + 并发队列
同步执行 + 串行队列
异步执行 + 串行队列
同步执行 + 主队列
异步执行 + 主队列
讲一讲 GCD死锁
什么是GCD死锁?
所谓死锁,通常指有两个线程A和B都卡住了,A在等B ,B在等A,相互等待对方完成某些操作。A不能完成是因为它在等待B完成。但B也不能完成,因为它在等待A完成。于是大家都完不成,就导致了死锁。
int main(int argc, const char * argv[]) {
@autoreleasepool {
//1
dispatch_sync(dispatch_get_main_queue(), ^(void){
//2
NSLog(@"这里死锁了");
});
}
return 0;
}
举几个
1.案例一: 当同步遇到了串行
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
该程序只执行任务1 WHY???
首先执行任务1,之后遇到同步线程,该线程将任务2添加到主队列后面,然后只有当任务2执行之后才会执行任务3。但是任务3又必须等到上一步的同步线程执行完后才能执行。因此,任务2等待任务3的结束,任务3等待任务2的结束。线程锁死。
2.案例二:同步 + 并行
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
该程序输入什么?
该程序输出:
1
2
3
首先执行任务1,此后遇到同步线程,该线程将任务2添加到全局并发队列中,该队列会开辟新的线程去执行任务2,当任务2执行完成之后,再执行任务3。
3.案例三:同步 + 异步
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5
该程序输出什么?
1
2
5
//2,5的输出顺序不定
首先调用队列创建方法创建串行队列,然后执行任务1,进入异步线程,该线程将任务2,同步线程,任务4加入串行队列中。因为该异步线程与任务5一起执行,所以任务2和任务5执行结束的时间不定。但是其中的同步线程将任务3添加到串行队列的后面(任务4的后面),而任务4又必须等到任务3执行结束才能执行,所以线程锁死。
4.案例四:异步遇到同步回主线程
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2"); // 任务2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5
该程序输出什么?
1
2
5
3
4
//2,5的输出顺序不定
首先执行任务1,然后异步线程(其中有任务2,同步线程,任务4)和任务5同时执行,所以任务2和任务5的执行快慢不定,任务2执行完后,同步线程将任务3添加到任务5的后面,待任务5执行完后执行任务3,最后执行任务4.
5.案例五:主线程上出现无限循环
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
while (1) {
}
NSLog(@"5"); // 任务5
该程序输出什么?
1
4
//1,4输出顺序不定
异步线程(任务1,同步线程,任务3)和任务4一起进行,所以任务1和任务4执行快慢未知。任务4执行完之后遇到死循环,所以任务5永远不会执行,而任务2又添加到任务5的后面,任务3又在任务2的后面。所以任务2,3,5都不会执行。