Block原理
2018-03-04 本文已影响19人
前年的邂逅_Jerry
Block就是一个存储了指向函数体中包含定义block时的代码块的函数指针,以及block外部上下文变量等信息的结构体。
一、Block是一个对象。
#import <objc/runtime.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
int i = 0;
__unused void(^myBlock)() = ^{
NSLog(@"block %d",i);
};
Class tmpClass = object_getClass(myBlock);
while (tmpClass) {
NSLog(@"class %@ superClass %@",tmpClass,class_getSuperclass(tmpClass));
tmpClass = class_getSuperclass(tmpClass);
}
}
@end
结果:
class __NSMallocBlock__ superClass __NSMallocBlock
class __NSMallocBlock superClass NSBlock
class NSBlock superClass NSObject
class NSObject superClass (null)
二、Block实现过程
1、通过命令转换:
//转换成C++代码命令:xcrun -sdk iphonesimulator8.1 clang -rewrite-objc ViewController.m
//包含weak类型数据转换成C++:xcrun -sdk iphonesimulator10.2 clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 ViewController.m
- (void)emptyBlockFunction {
void (^emptyBlock)() = ^{
NSLog(@"abc");
};
emptyBlock();
}
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// @implementation BlockStructureViewController
int globalValue = 10;
static int staticValue = 5;
struct __BlockStructureViewController__emptyBlockFunction_block_impl_0 {
struct __block_impl impl;
struct __BlockStructureViewController__emptyBlockFunction_block_desc_0* Desc;
__BlockStructureViewController__emptyBlockFunction_block_impl_0(void *fp, struct __BlockStructureViewController__emptyBlockFunction_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __BlockStructureViewController__emptyBlockFunction_block_func_0(struct __BlockStructureViewController__emptyBlockFunction_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fj_03n4zg_x4tv33bsws1sfxlkw0000gn_T_BlockStructureViewController_8a7742_mi_0);
}
static struct __BlockStructureViewController__emptyBlockFunction_block_desc_0 {
size_t reserved;
size_t Block_size;
} __BlockStructureViewController__emptyBlockFunction_block_desc_0_DATA = { 0, sizeof(struct __BlockStructureViewController__emptyBlockFunction_block_impl_0)};
static void _I_BlockStructureViewController_emptyBlockFunction(BlockStructureViewController * self, SEL _cmd) {
void (*emptyBlock)() = ((void (*)())&__BlockStructureViewController__emptyBlockFunction_block_impl_0((void *)__BlockStructureViewController__emptyBlockFunction_block_func_0, &__BlockStructureViewController__emptyBlockFunction_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)emptyBlock)->FuncPtr)((__block_impl *)emptyBlock);
//这里调用结构体的构造函数(函数指针,desc的结构体变量)
*emptyBlock = &__BlockStructureViewController__emptyBlockFunction_block_impl_0((void *)__BlockStructureViewController__emptyBlockFunction_block_func_0, &__BlockStructureViewController__emptyBlockFunction_block_desc_0_DATA))
emptyBlock->FuncPtr(emptyBlock);
}
2、自己实现block
struct block {
int Flags;
void *FuncPtr;
};
struct block_impl {
struct block impl;
block_impl(void *fp, int flags = 0){
impl.Flags = flags;
impl.FuncPtr = fp;
}
};
static void block_impl_func_0(struct block_impl *__cself) {
printf("哈哈");
}
void test() {
struct block_impl blockImpl = block_impl((void *)block_impl_func_0, 20);
block *tmpBlockPointer = (block *)&blockImpl;
//struct本身是不占内存
void (*Func)(struct block *) = (void (*)(struct block *))tmpBlockPointer->FuncPtr;
Func(tmpBlockPointer);
}
三、__block
- (void)blockDataBlockFunction {
__block int a = 100;
//代码会生成如下
/*
struct __Block_byref_a_0 {
void *__isa;
struct __Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
*/
//放到栈里,是系统来控制内存的,离开作用域后,就释放内存了
//OC里的对象都是在堆区的,堆区的内存需要你自己创建、自己释放
void (^blockDataBlock)() = ^{
//堆栈区
NSLog(@"block_int_a %p", &a);
NSLog(@"a = %d", a);
};
//a->forwarding-a;
//你在栈里的a->forwarding = block里的a的地址
//你刚刚说外面的a是栈里面, block里面的a是在堆里面,那为什么我在外面改变a,能改变里面的值?
//栈区修改a的值
a = 10; //此处能在block里面修改a的值 实际上代码里面执行的是a->forwarding.a的值
blockDataBlock();
}
int a = 10;
void(^block)() = ^{
NSLog(@"%d",a);
};
a = 0;//此处不能修改a的值
block();
四、block存储的问题
typedef void(^testBlock)();
typedef void(^testBlock1)(int a);
@property(nonatomic, strong)testBlock strongBlock;
@property(nonatomic, copy)testBlock copyBlock;
@property(nonatomic, weak)testBlock weakBlock;
@property(nonatomic, strong)testBlock1 strongBlock1;
@property(nonatomic, copy)testBlock1 copyBlock1;
@property(nonatomic, weak)testBlock1 weakBlock1;
1、当你没有使用外部变量的时候,通通都是global的,无论你怎么修饰、无论是否成员变量;存储在代码区
_strongBlock = ^{
NSLog(@"_strongBlock");
};
_copyBlock = ^{
NSLog(@"_copyBlock");
};
_weakBlock = ^{
NSLog(@"_weakBlock");
};
NSLog(@"strongBLock %@ copyBLock %@ weakBLock %@", object_getClass(_strongBlock), object_getClass(_copyBlock), object_getClass(_weakBlock));
结果:
strongBLock __NSGlobalBlock__ copyBLock __NSGlobalBlock__ weakBLock __NSGlobalBlock__
2、当在block里添加了外部变量,那么block的内存就会变成不一样;
int k = 0;
_strongBlock = ^{
NSLog(@"_strongBlock %d", k);
};
_copyBlock = ^{
NSLog(@"_copyBlock %d", k);
};
_weakBlock = ^{
NSLog(@"_weakBlock %d", k);
};
NSLog(@"strongBLock %@ copyBLock %@ weakBLock %@", object_getClass(_strongBlock), object_getClass(_copyBlock), object_getClass(_weakBlock));
结果:
strongBLock __NSMallocBlock__ copyBLock __NSMallocBlock__ weakBLock __NSStackBlock__
3、当block赋值给strong变量、(非weak的)成员变量,block为mallocBlock;
int k = 0;
_strongBlock1 = ^(int i){
NSLog(@"_strongBlock1 : i=%d", k);
};
_copyBlock1 = ^(int i){
NSLog(@"_copyBlock1 : i=%d", k);
};
_weakBlock1 = ^(int i){
NSLog(@"_weakBlock1 : i=%d", k);
};
NSLog(@"strongBLock1 %@ copyBLock1 %@ weakBLock1 %@", object_getClass(_strongBlock1), object_getClass(_copyBlock1), object_getClass(_weakBlock1));
结果:
strongBLock1 __NSMallocBlock__ copyBLock1 __NSMallocBlock__ weakBLock1 __NSStackBlock__
4、当block为函数参数,block为stackBlock;
5、全局变量、静态变量、静态局部变量在block中的存储,block都为globalBlock
6、static变量无法加__block符的
7、对globalBlock 进行copy操作 依然是它本身地址 对mallocBlock进行copy,引用计数+1 对stackBlock进行copy 地址改变了
8、需要手动对block进行copy的情况:作为函数参数,这里涉及到block数组,原因是栈里的对象,你无法来讲引用计数
- 结论:
当你的block里有外部变量的时候
当 block 调用 copy 方法时,如果 block 在栈上,会被拷贝到堆上;
当 block 作为函数返回值返回时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法;
当 block 被赋值给 __strong id 类型的对象或 (非weak)成员变量时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法;
当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 进行拷贝;
五、循环引用
typedef void(^cycleBlock)();
@interface ViewController (){
cycleBlock blk;
NSObject *object;
}
1、例一
NSObject *otherObject = [[NSObject alloc] init];
NSLog(@"object %p &object %p", otherObject, &otherObject);
//otherObject本来是在堆区,然后把block赋值给成员对象,block会自动copy,到堆区里面,otherObject本来在堆区,(没有weak的情况下,就会对它有引用计数+1)
__weak typeof(otherObject)weakObject = otherObject;
blk = ^{
NSLog(@"object %p &object %p", weakObject, &weakObject);
};
blk();
//block 对otherObject不进行引用计数 此操作otherObject被释放掉
otherObject = nil;
blk();
/*
结果:
object 0x60400000a630 &object 0x7fff5f32fc98
object 0x60400000a630 &object 0x600000441160
object 0x0 &object 0x600000441160
*/
NSObject *otherObject = [[NSObject alloc] init];
NSLog(@"object %p &object %p", otherObject, &otherObject);
blk = ^{
NSLog(@"object %p &object %p", otherObject, &otherObject);
};
blk();
//block 对otherObject进行引用计数 此操作otherObject不会被释放
otherObject = nil;
blk();
/*
结果:
object 0x6080000149f0 &object 0x7fff57690c98
object 0x6080000149f0 &object 0x60800024bb40
object 0x6080000149f0 &object 0x60800024bb40
*/
2、例二
__strong讲解: https://www.jianshu.com/p/404e0ea5f6d7
NSMutableDictionary *otherObject = [[NSMutableDictionary alloc] init];
NSLog(@"object %p &object %p", otherObject, &otherObject);
__weak typeof(otherObject)weakObject = otherObject;
blk = ^{
__strong typeof(weakObject)strongObject = weakObject;
NSLog(@"object %p &object %p", strongObject, &strongObject);
//如果[otherObject setValue:@"sda" forKey:@"12"]; 则产生循环引用
[strongObject setValue:@"sda" forKey:@"12"];
};
blk();
otherObject = nil;
blk();
/*
结果:
object 0x60c000222480 &object 0x7fff51adcca8
object 0x60c000222480 &object 0x7fff51adcc08
object 0x0 &object 0x7fff51adcc08
*/
3、例三,
NSObject *otherObject = [[NSObject alloc] init];
NSLog(@"object %p &object %p", otherObject, &otherObject);
__weak typeof(otherObject)weakObject = otherObject;
//只要 block 部分执行了,即使我们中途释放了 obj,block 内部依然会继续强引用它
blk = ^{
// __strong会保证otherObject到运行结束前都不会被释放
__strong typeof(weakObject)strongObject = weakObject;
NSLog(@"object %p &object %p", strongObject, &strongObject);
sleep(3);
NSLog(@"object %p &object %p", strongObject, &strongObject);
};
blk();
otherObject = nil;
sleep(5);
blk();
/*
运行结果:
object 0x608000010780 &object 0x7fff59c14ca8
object 0x608000010780 &object 0x7fff59c14c08
object 0x608000010780 &object 0x7fff59c14c08
object 0x0 &object 0x7fff59c14c08
object 0x0 &object 0x7fff59c14c08
*/
4、例四,对成员变量的循环引用
- (void)strongCycleBlockFunction {
object = [[NSObject alloc] init];
NSLog(@"object %p &object %p", object, &object);
//对于成员变量,block是把self引用计数+1,不是对成员变量object本身来增加引用计数的
blk = ^{
NSLog(@"object %p &object %p", object, &object);
};
blk();
//这里的object和block中的block是同一个内存地址
object = nil;
blk();
/*
结果:因为block不是对objec进行引用的,而是对self进行引用
object 0x600000010180 &object 0x7fd7426061e8
object 0x600000010180 &object 0x7fd7426061e8
object 0x0 &object 0x7fd7426061e8
*/
}
5、例五、block里面定义静态变量或者使用外部的静态变量
不需要对静态变量进行任何修饰,不存在循环引用。
static int i = 0;
@interface SecondController ()
@end
@implementation SecondController
- (void)dealloc{
NSLog(@"%s",__func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
void (^test)() = ^{
static int j = 0;
NSLog(@"i = %d, j = %d", i,j);
};
i = 2;
test();
}
6、例六
@interface SecondController ()
@property (nonatomic, copy) void(^blk)(NSString * str);
@end
@implementation SecondController{
NSString * str;
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
str = @"sjdk";
self.view.backgroundColor = [UIColor whiteColor];
self.blk = ^(NSString * str2){
//self 持有 block ,block 持有str ,str被self持有,这里会产生循环引用。
str = str2;
NSLog(@"%@",str);
};
self.blk(@"21");
}
@end
如下写法是不存在循环引用的
@interface SecondController ()
@end
@implementation SecondController{
NSString * str;
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
str = @"sjdk";
self.view.backgroundColor = [UIColor whiteColor];
void(^blk)(NSString *) = ^(NSString * str2){
str = str2;
NSLog(@"%@",str);
};
blk(@"21");
}
@end