一、内存分区
内存区域栈区(stack):由系统自动分配,一般存放函数参数值、局部变量的值等。由编译器自动创建与释放。其操作方式类似于数据结构中的栈,即后进先出、先进后出的原则。
堆区(heap):一般由程序员申请并指明大小,最终也由程序员释放。如果程序员不释放,程序结束时可能会由OS回收。对于堆区的管理是采用链表式管理的,操作系统有一个记录空闲内存地址的链表,当接收到程序分配内存的申请时,操作系统就会遍历该链表,遍历到一个记录的内存地址大于申请内存的链表节点,并将该节点从该链表中删除,然后将该节点记录的内存地址分配给程序。
全局区/静态区:顾名思义,全局变量和静态变量存储在这个区域。只不过初始化的全局变量和静态变量存储在一块,未初始化的全局变量和静态变量存储在一块。程序结束后由系统释放。
文字常量区:这个区域主要存储字符串常量。程序结束后由系统释放。
程序代码区:这个区域主要存放函数体的二进制代码。
二、闭包
闭包(Closure)简单理解就是一个函数,加上这个函数相关的引用环境。block实际是Objc对闭包的实现。
三、Block中变量的复制与修改
- 对于block外的变量引用,block默认是将其复制到其数据结构中来实现访问的
- 通过block进行引用的变量是const的。也就是说不能在block中直接修改这些变量
- 用__block关键字来声明变量,这样就可以在block中修改变量了,对于用__block修饰的外部变量引用,block是复制其引用的地址来实现访问的
四、Block的类型
block主要有三种类型,三种类型也说明了其在内存中存储的方式:
__NSGlobalBlock__:全局block并不是指全局声明的block,而是指不会访问任何外部变量,不会涉及到任何拷贝的block,比如一个不执行任何代码的block
__NSStackBlock__:保存在栈中的block,当函数返回时被销毁
__NSMallocBlock__:保存在堆中的block,当引用计数为0时被销毁。该类型的block都是由__NSStackBlock__类型的block从栈中复制到堆中形成的。
五、Block从栈拷贝到堆上的时机
当我们创建一个block的时候,block是分配在栈上的(__NSGlobalBlock__例外),类型为__NSStackBlock__,但是当下面的一些情形时会把栈上的block拷贝到堆上变成__NSMallocBlock__
- 调用Block的copy实例方法时
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch 的API中传递Block时
六、属性Block用copy还是strong?
对于当前的ARC对于block的处理,当把一个block赋值给一个strong类型的属性时,会自动把block拷贝到堆上,并产生对该block的一个强引用。同样地,copy类型的block会在被赋值的时候进行一次copy操作,如果原来的block是在栈上,那么也会被复制到堆上。所以对于block类型的属性,用strong和copy并没有本质上的区别,推荐使用copy,可以显式地指定拷贝的动作。当然,如果block原本是堆block,那么内部并没有copy,只是进行了一次retain操作;如果block原本是全局的block,那么也没有copy,只是一次简单的赋值引用的操作
- (void)testBlock {
//1.没有引用外部变量,block1为__NSGlobalBlock__
void(^block1)() = ^{
NSLog(@"1");
};
NSLog(@"%@",block1);
//2.引用了外部变量,默认生成在栈上,该block为__NSStackBlock__类型
int num = 0;
NSLog(@"%@",^{
NSLog(@"%d",num);
});
//3.生成在栈上的block赋值给强引用,拷贝到堆上,block2为__NSMallocBlock__
void(^block2)() = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",block2);
//4.全局block可以执行
NSArray *globalBlocks = [self getGlobalBlocks];
for (void(^block)() in globalBlocks) {
NSLog(@"%@",block);
block();
// 对于全局的block进行copy操作,并不会生成新的block,使用的其实是相同的block
void(^copyBlock)() = [block copy];
NSLog(@"%d", copyBlock == block);
}
//5.堆上的block可以在外部执行
NSArray *mallocBlocks = [self getMallocBlocks];
for (void(^block)() in mallocBlocks) {
NSLog(@"%@",block);
block();
// 和全局block一样,堆上的block进行copy操作也不会生成新的block
void(^copyBlock)() = [block copy];
NSLog(@"%d", copyBlock == block);
}
// 6.栈上的block在外部使用(野指针错误)
NSArray *stackBlocks = [self getStackBlocks];
for (void(^block)() in stackBlocks) {
NSLog(@"%@",block);
block();
}
}
- (NSArray *)getGlobalBlocks {
// 没有对外部变量进行应用的block为全局block,类型是__NSGlobalBlock__
// 全局block不需要copy或者retain处理
return [NSArray arrayWithObjects:^{NSLog(@"1");},
^{NSLog(@"2");},
^{NSLog(@"3");},
nil];
}
- (NSArray *)getMallocBlocks {
// 栈上的block赋给一个强引用,会自动从栈上copy到堆上
// 当返回一个block数组的时候注意要先把block拷贝到堆上,然后再加入的数组
int num = 1;
void(^block1)(void) = ^{NSLog(@"%i",num);};
void(^block2)(void) = ^{NSLog(@"%i",num);};
void(^block3)(void) = ^{NSLog(@"%i",num);};
return [NSArray arrayWithObjects:block1,block2,block3,
nil];
}
- (NSArray *)getStackBlocks {
/*
这里有个奇怪的问题,网上没有发现对应的解释。下面数组中的第一个为__NSMallocBlock__,后面的却为__NSStackBlock__,
在方法外执行数组中的block在执行到第二个block的时候会发生崩溃
个人的理解是这样的:
- 首先需要明确,栈block只能在作用域中执行,出了作用域就会被销毁,而堆block的销毁取决于自身的引用计数,可以在作用域外调用
- block默认创建在栈上,但数组对栈上的对象强引用无效,因为栈上的block的销毁是作用域决定的,强引用只能对堆上的对象起作用,所以数组的block在方法返回以后就被销毁了。
- 在数组的内部应该保存了队首的位置,在添加元素的时候,这个队首的引用应该是指向第一个元素,这样就产生了一次赋值操作,所以把第一个
栈block拷贝到了堆上转换成堆block,并进行了一次retain操作
*/
int num = 1;
NSArray *arr = [NSArray arrayWithObjects:^{NSLog(@"%i",num);},
^{NSLog(@"%i",num);},
^{NSLog(@"%i",num);},
nil];
NSLog(@"%@",arr);
/*
注:
// block1为__NSMallocBlock__,后面的block2和block3还是__NSStackBlock__,
// block4,block5为__NSMallocBlock__
void(^block1)() = arr[0];
void(^block2)() = arr[1];
void(^block3)() = arr[2];
// block2赋值给强引用block4,把栈上的block拷贝到堆上
// block2进行copy操作,把栈上的block拷贝到堆上
void(^block4)() = block2;
void(^block5)() = [block2 copy];
NSLog(@"\n%@\n%@\n%@\n%@\n%@",block1,block2,block3,block4,block5);
*/
return arr;
}
网友评论