引用计数内存管理
- 所有的对象都存放在堆上,需要手动管理内存
- iOS内存管理通过retainCount进行管理,通过引用计数+/-1来控制内存的的声明周期,通常来说,我们在一个代码块中,我们会对需要用到的对象的引用计数+1,在离开代码块时,对引用计数-1,通过这种机制,我们只需要关心在我们的代码中需要的时候retain,不需要的时候release,而不用关心对象什么时候释放,当引用计数为0,即之后再也没有对该内存的引用,对象内存就会被释放,这个由系统框架来做
autorelease与runloop
runloop在每一个调用周期(消息循环)外部都会添加一个autoreleasepool
,对于autorelease
的对象,并不是在离开作用域就马上释放的,而是在离开autoreleasepool
的时候才被释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @property (nonatomic, weak) NSArray *array1; @property (nonatomic, weak) NSArray *array2;
- (IBAction)btnClick:(id)sender { [self test]; NSLog(@"%@", self.array1); NSLog(@"%@", self.array2); }
- (void)test { self.array1 = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@1, @2, @3, nil]; self.array2 = array; }
|
函数与block
block就是对闭包的实现,block的本质其实就是函数,只是在函数的基础上加上了捕获变量列表
编译之后会变成一个结构体,包含捕获参数,和函数指针,后面我们看看block编译之后的代码
block的编译
1. 不捕获变量
先定义一个test.m文件
1 2 3 4 5 6 7 8 9
| #include <stdio.h>
int main() { void (^blk)(void) = ^{ printf("Block\n"); }; blk(); return 0; }
|
通过clang命令生成预编译后的代码
1
| clang -rewrite-objc test.m
|
生成如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Block\n"); }
int main() { void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0; }
|
可以看到,block被编译成一个静态方法,和一个block描述的结构体
2. 捕获不可变变量
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <stdio.h> #import <Foundation/Foundation.h>
int main() { NSObject *obj = [[NSObject alloc] init]; int a = 10; void (^blk)(void) = ^{ printf("Block:%d\n", a); NSLog(@"%@", obj); }; blk(); return 0; }
|
编译之后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; NSObject *obj; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSObject *_obj, int flags=0) : a(_a), obj(_obj) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; NSObject *obj = __cself->obj;
printf("Block:%d\n", a); NSLog((NSString *)&__NSConstantStringImpl__var_folders_h8_fm423vjj5mlgb636xh5b44n80000gn_T_test_e9e5ff_mi_0, obj); }
static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main() { NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
int a = 10;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); return 0; }
|
这里用到了一个对象obj
,__main_block_desc_0
多了两个方法(__main_block_copy_0
, __main_block_dispose_0
),用于持有和释放block捕获的对象,如果只有int,没有对对象的捕获,则不会生成这两个成员
3. 捕获可变变量
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h>
int main() { __block int a = 10; void (^blk)(void) = ^{ printf("Block:%d\n", a); }; blk(); return 0; }
|
编译后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; };
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_a_0 *a; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
int main() { __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); return 0; }
|
block捕获可变对象其实与捕获普通变量一样,只是对原有变量包装成一个新的对象而已
4. 捕获static变量
这里例子就省了,直接上结论
- 如果是局部static变量,block会当成普通局部变量一样处理,即会捕获该局部变量
- 如果是全局static变量,block会直接使用该全局变量,不进行变量捕获处理
block类型
上面我们看到的block的实现__main_block_impl_0
包含一个_isa
字段,用于标识block的类型,block有三种类型
- _NSConcreteStackBlock:存放在Stack上
- _NSConcreteGlobalBlock:与全局变量一样,存放在全局区
- _NSConcreteMallocBlock:存放在堆上
看下面三种代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| typedef long (^BlkSum)(int, int);
BlkSum blk1 = ^ long (int a, int b) { return a + b; };
NSLog(@"blk1 = %@", blk1); NSLog(@"blk1 = %@", [blk1 copy]);
int base = 100; BlkSum blk2 = ^ long (int a, int b) { return base + a + b; };
NSLog(@"blk2 = %@", blk2); NSLog(@"blk2 = %@", [blk2 copy]);
BlkSum blk3 = [[blk2 copy] autorelease]; NSLog(@"blk3 = %@", blk3); NSLog(@"blk3 = %@", [blk3 copy]);
|
当block存放在全局时,始终只有一份,调用copy/retain/release,返回的是同一个对象
当block存放在栈上时,如果进行copy,block会被拷贝到堆上,调用retain或release无效
当block存放在堆上时,如果进行copy/retain/release,内存管理与对象一样,引用计数+/-1
上面是MRC的行为,而在ARC上即使是block没有进行拷贝,也会被拷贝到堆上,所以在ARC上的block只有堆区和全局区,在ARC上,如果block会自动在需要的时候进行copy,如block作为返回值时,block作为参数传给另一个函数时
前向引用
当我们在block使用可变变量__block
的时候,编译器会生成__Block_byref_a_0
内部有一个__forwarding
字段指向自己
1 2 3 4 5 6 7
| struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; };
|
为什么要用一个变量指向自己,看下面这个例子
1 2 3 4 5 6 7 8 9
| { __block int val = 0; void (^blk)(void) = [^{ ++val; } copy]; ++val; blk(); NSLog(@"%d", val); }
|
根据上面编译的代码,我们可以分析转换成的代码大致如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { __Block_byref_a_0 val;
__main_block_impl_0 blk = __main_block_impl_0(...)
__main_block_impl_0 newBlk = _Block_copy(blk)
++(val->__forwarding->val)
newBlk->FuncP();
NSLog(@"%d", val); }
|
上面有两个block(blk和newBlk)一个存放在栈上,一个存放在堆上
上面也有两个__Block_byref_a_0,当进行拷贝时,栈上的val也会被拷贝一份到堆上
当离开函数作用域时,栈上的内存会被释放,所以当block从栈拷贝到堆上时,会把堆上变量的__forwarding指针,指向堆,故后面及时我们使用局部变量val,实际上内部使用的已经是拷贝到堆上的变量了,这时候和block内部使用的变量是统一变量
循环引用常见问题