Block
//在objc中,根据对象的定义,凡是首地址是*isa的结构体指针,都可以认为是对象(id)
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
block本质上也是一个OC对象,它内部也有个isa指针,
block是封装了函数调用以及函数调用环境的OC对象
block有3种类型
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
Block_Type.pngblock的copy:
@property (copy, nonatomic) void (^block)(void); //block属性的建议写法
每一种类型的block调用copy后的结果如下所示
Block_copy.png在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
1.block作为函数返回值时
2.将block赋值给__strong指针时
3.block作为Cocoa API中方法名含有usingBlock的方法参数时
4.block作为GCD API的方法参数时
block对变量的引用:
当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用
如果block被拷贝到堆上
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)
__block修饰符
1.__block可以用于解决block内部无法修改auto变量值的问题
void foo(){
__block int i = 3;
void(^myBlock)(void) = ^{
i *= 2;
};
myBlock();
}
//就因为加了个__block,原本的int值的位置变成了一个struct(struct __Block_byref)。这个struct的首地址为同样为*isa。
struct Block_byref { //Block_private.h中的定义
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
正是如此,这个值才能被block共享、并且不受栈帧生命周期的限制、在block被copy后,能够随着block复制到堆上。
2.block不能修饰全局变量、静态变量(static)
void test()
{
auto int a = 10;
static int b = 10;
block = ^{
NSLog(@"age is %d, height is %d", a, b);
};
}
//编译成底层的C++实现代码
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, a, (*b));
}
解决循环引用问题
// __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
// __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
__weak typeof(self) weakSelf = self;
self.block = ^{
printf("%p",weakSelf);
};
// MRC不支持__ weak的,才有了, __unsafe_unretained
__unsafe_unretained id weakSelf = self;
self.block = ^{
printf("%p",weakSelf);
};
//用__block解决(必须要调用block)
__block id weakSelf =self;
self.block = ^{
weakSelf = nil;
};
self.block();
//__weak与weak基本相同。前者用于修饰变量(variable),后者用于修饰属性(property)。weak主要用于防止block中的循环引用
//如果捕获到的是当前对象的成员变量对象,同样也会造成对self的引用,同样也要避免
- (void)configureBlock {
id tmpIvar = _ivar; //临时变量,避免了self引用
self.block = ^{
[tmpIvar msg];
}
}
为什么weak 一般用来修饰对象,assign一般用来修饰基本数据类型?
原因是assign修饰的对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。
捕获变量不能修改的原因:
当struct第一次被创建时,它是存在于该函数的栈帧上的,其Class是固定的_NSConcreteStackBlock。其捕获的变量是会赋值到结构体的成员上,所以当block初始化完成后,捕获到的变量不能更改。
void foo_(){
int i = 2;
NSNumber *num = @3;
long (^myBlock)(void) = ^long() {
return i * num.intValue;
};
long r = myBlock();
}
//使用Clang转换成C++
void foo(){
int i = 2;
NSNumber *num = @3;
struct __foo_block_impl_0 myBlockT;
struct __foo_block_impl_0 *myBlock = &myBlockT;
myBlock->impl.isa = &_NSConcreteStackBlock;
myBlock->impl.Flags = 570425344;
myBlock->impl.FuncPtr = __foo_block_func_0;
myBlock->Desc = &__foo_block_desc_0_DATA;
myBlock->i = i;
myBlock->num = num;
long r = myBlock->impl.FuncPtr(myBlock);
}
在使用clang转换OC为C++代码时,可能会遇到以下问题,cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如ios-8.0.0
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
网友评论