怎么选择网站模板,北京网站开发公司一网天行,建站网站盗用了别人的案例,家庭装修报价明细预算表目录 前言Block底层结构Block捕获变量原理捕获局部变量#xff08;auto、static#xff09;全局变量捕获实例self Block类型Block的copyBlock作为返回值将Block赋值给__strong指针Block作为Cocoa API中方法名含有usingBlock的方法参数Block作为GCD API的方法参数Block属性的写… 目录 前言Block底层结构Block捕获变量原理捕获局部变量auto、static全局变量捕获实例self Block类型Block的copyBlock作为返回值将Block赋值给__strong指针Block作为Cocoa API中方法名含有usingBlock的方法参数Block作为GCD API的方法参数Block属性的写法 Block访问对象类型的auto变量Block在栈上Block被拷贝到堆上Block从堆上移除 修饰符__block__block内存管理__forwarding指针__block修饰对象类型 Block循环引用解决办法强弱共舞 总结 前言
Block是带有局部变量的匿名函数函数实现就是代码块里的内容同样有参数和非返回值本质是一个封装了函数调用以及函数调用环境的OC对象因为它内部有isa指针
Block的基本使用请看这两篇文章
kl
本篇文章着重探究Block这些特性的底层原理
Block底层结构
声明一个最简单的块并调用
void (^block)(void) ^{NSLog(Hello World!);
};
block();使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令将OC代码转换成C代码
// 原本的代码有各种强制转换目前不重要先删去从简// 声明并实现一个block
// void (*block)(void) ((void (*)(int, int))__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA));
block __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA);// 调用执行block
// ((void (*)(__block_impl *))((__block_impl *)block)-FuncPtr)((__block_impl *)block);
block-FuncPtr(block);
// __main_block_impl_0可以直接转换为__block_impl类型是因为两个类型的结构体地址是一样的而且相当于直接把__block_impl里的值都放到__main_block_impl_0里这些穿插了许多下划线的符号实际上是不同的结构体变量Block本质就是struct __main_block_impl_0类型的结构体下图清晰地说明了block的底层结构 __main_block_impl_0可以直接转换为__block_impl类型是因为两个类型的结构体地址是一样的相当于直接把__block_impl里的值都放到__main_block_impl_0里 所以block.impl-FuncPtr(block)就相当于block-FuncPtr(block)
Block捕获变量原理
为了保证block内部能够正常访问外部的变量block有个变量捕获机制
捕获局部变量auto、static
auto自动变量离开作用域就自动销毁只存在于局部变量 static静态局部变量
// 不加关键字默认是auto变量
/*auto*/ int age 10;
static int height 175;void (^block)(void) ^{// age、height的值捕获进来captureNSLog(age is %d, height is %d, age, height);
};// 修改局部变量的值
age 20;
height 180;block();
NSLog(%d %d, age, height);打印结果 可以看到age仍为修改前的值而height确确实实被修改了
将以上代码转换成C代码来看一下
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int age;int *height;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags0) : age(_age), height(_height) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;}
};Block结构体的变量多了两个分别是age、height这说明外部的变量被捕获到了Block的内部构造函数后面的 : age(_age), height(_height)语法会自动将_age、_height赋值给int age、int* height来保存
声明实现Block调用析构函数
int age 10;
static int height 175;block ((void (*)())__test_block_impl_0((void *)__test_block_func_0, __test_block_desc_0_DATA, age, height));age 20;
height 180;而后调用Block实际调用__main_block_func_0
block-FunPtr(block)static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int age __cself-age; // bound by copyint *height __cself-height; // bound by copyNSLog((NSString *)__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
}此时的age是值传递打印的只是Block初始化时传进去的值后面age修改跟这个值无关height是指针传递打印的是height变量地址一直所指向那块内存的值
全局变量
int age_ 10;
static int height_ 175;int main(int argc, const char * argv[]) {autoreleasepool {void (^block)(void) ^{NSLog(age_ is %d, height_ is %d, age_, height_);};age_ 20;height_ 180;block();}return 0;
}全局变量一直在内存中打印的一直是最新的值不用捕获 为什么会有这样的差异呢
auto和static因为作用域的问题自动变量的内存随时可能被销毁所以要捕获就赶紧把它的值拿进来防止调用的时候访问不到静态变量就不一样了它一直在内存中作用域仅限于定义它们的函数、它们不能在函数外访问随时可以通过指针访问到最新的值
全局变量在Block中访问局部变量相当于是跨函数访问要先将变量存储在Block里捕获使用的时候再从Block中取出而全局变量是直接访问
捕获实例self
- (void)testSelf {void (^block)(void) ^{// NSLog(--------%p -- %p -- %p -- %p, self, _name, self-_name, self.name);NSLog(--------%p, self);/*NSLog(--------%p, self-_name);相当于NSLog(--------%p, _name);也会捕获进去*/};block();
}看了它的C实现后发现self也会被捕获进去
实际上OC方法转换成C函数后会发现前两个参数永远是方法调用者self、方法名_cmd
void testSelf(Person* self, SEL _cmd, ) {// ...
}即然self是参数参数也是局部变量它被捕获进Block也就能解释得通了
Block类型
上面提到Block是OC对象因为它有isa指针对象的isa指向它的类型那么Block都有什么类型呢
首先运行以下代码
void (^block)(void) ^{NSLog(Hello!);
};
NSLog(% %, block, [block class]);
NSLog(%, [[block class] superclass]);
NSLog(%, [[[block class] superclass] superclass]);
/*__NSGlobalBlock__NSBlockNSObject*/可以看到Block类型的根类是NSObject也能说明Block是一个OC对象
不同操作对应的Block类型不同
// Global没有访问auto变量跟static变量无关
void (^block1)(void) ^{NSLog(Hello);
};// 函数调用栈要调用一个函数的时候就会指定一块栈区空间给这个函数用
// 一旦函数调用完毕后栈区的这块空间就会回收变成垃圾数据会被其他数据覆盖// Stack访问了auto变量
int age 21;
void (^block2)(void) ^{NSLog(Hello - %d, age);
};
// ARC下打印MallocMRC下确实是StackNSLog(% % %, [block1 class], [block2 class], [^{NSLog(%d, age);
} class]); // 打印结果__NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__// 编译完成后isa指向是_NSConcreteStackBlock、_NSConcreteMallocBlock、_NSConcreteGlobalBlock
// 首先肯定以运行时的结果为准Block确实有三种类型可能会通过Runtime动态修改类型没有访问自动变量的Block类型是__NSGlobalBlock__存储在数据段 其实Global不常用既然不访问变量那么将代码块封装成函数一行直接调用才显得更为简洁 访问了自动变量的Block类型是__NSStackBlock__存储在栈区 以上代码是在MRC下运行的 __NSStackBlock__的Block调用了copy后类型会变为__NSMallocBlock__存储在堆区 若是在ARC下运行即使不用copy修饰编译器也会自动对__NSStackBlock__进行copy操作block2的类型将会是Malloc类型
手动对每种类型的Block调用copy后的结果如下图所示 Block的copy
在ARC环境下编译器会根据情况自动将栈上的block复制到堆上 放到堆上的目的是方便我们来控制他的生命周期可以更有效的进行内存管理
Block作为返回值
typedef void(^BBlock)(void);BBlock myBlock(void) {int age 21;return ^{NSLog(----------%d, age);};
}BBlock bblock myBlock();
bblock();
NSLog(%, [bblock class]); // __NSMallocBlock__
//BBlock myBlock(void) {
// return [^{
// NSLog(----------);
// } copy];
//}由于Block在栈区所以函数调用完毕后Block内存就被销毁了再去调用它就很危险如果在MRC下运行上述代码编译器会提示报错 ARC下不必担心此问题编译器会自动对返回的Block进行copy操作如注释所写返回拷贝到堆上的Block
将Block赋值给__strong指针
int age 21;
/*__strong*/ BBlock bblock ^{NSLog(--------%d, age);
};
NSLog(%, [bblock class]); // ARC__NSMallocBlock__// 没有被强指针指着
NSLog(%, [^{NSLog(--------%d, age);
} class]); // __NSStackBlock__Block作为Cocoa API中方法名含有usingBlock的方法参数
NSArray* array [one, 2, {seven : 7}];
// 遍历数组并调用Block
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {NSLog(% --- %lu, obj, (unsigned long)idx);
}];Block作为GCD API的方法参数
static dispatch_once_t onceToken;
dispatch_once(onceToken, ^{});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});Block属性的写法
因为编译器会自动视情况进行copy操作所以两种写法都没问题只是为了统一规范建议使用copy来修饰属性
property (strong, nonatomic) void (^block)(void);
property (copy, nonatomic) void (^block)(void);Block访问对象类型的auto变量
Block在栈上
只要Block存在栈上无论访问外部变量是用强指针还是弱指针都不会对外部auto变量产生强引用
Block被拷贝到堆上
如果Block被拷贝到堆上会根据auto变量的修饰符__strong、__weak、__unsafe_unretained做出相应的操作
BBlock bblock;
{__strong Person* person [[Person alloc] init];// __weak Person* person [[Person alloc] init];person.age 21;bblock ^{// 在ARC环境下block会自动拷贝到堆区间切换修饰符__strong和__weakperson分别会不释放和释放NSLog(-%d-, person.age);};// MRC环境下block是在栈区间的所以不会对age进行强引用person会随着作用域结束而释放//[bblock release];
}
NSLog(--------------);将上面代码文件转换成C文件
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;Person *__strong person;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags0) : person(_person) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;}
};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};static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)dst-person, (void*)src-person, 3/*BLOCK_FIELD_IS_OBJECT*/);}Block内部的__main_block_desc_0结构体会调用copy函数copy函数内部会调用_Block_object_assign函数而_Block_object_assign函数会根据auto变量的修饰符__strong、__weak、__unsafe_unretained做出相应的操作形成强引用retain或者弱引用
Block从堆上移除
如果Block从堆上移除会调用Block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数_Block_object_dispose函数会自动release引用的auto变量
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src-person, 3/*BLOCK_FIELD_IS_OBJECT*/);}注
只有在引用对象类型的变量时才会生成copy和dispose函数如果引用的是static修饰的对象类型那么捕获的变量在C代码中将会是Person *__strong *person;代码里有__weak转换C文件可能会报错cannot create __weak reference in file using manual reference可以指定支持ARC、指定运行时系统版本xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtimeios-8.0.0 main.m
使用GCD API验证Block对外部变量的强弱引用Github Demo
- (void)touchesBegan:(NSSetUITouch * *)touches withEvent:(UIEvent *)event {Person* person [[Person alloc] init];__weak Person* weakPerson person;// 强引用了Block调用完毕释放了person才会释放
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(---%, person);
// });// 弱引用调用Block之前person已经释放
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(---%, weakPerson);
// });// 编译器已经检查到会有强引用
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(---1%, weakPerson);
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(---2%, person);
// });
// });// 不会等到弱引用就释放了dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(---1%, person);dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(---2%, weakPerson);});});NSLog(Screen Touched);
}修饰符__block
如果在Block内部修改捕获的auto变量值编译器将会报错
int age 21;
BBlock block ^{age 20;NSLog(%d, age);
};
block();从底层可看出在这里修改变量的值实际上是通过改变__main_block_fun_0函数里的局部变量达到改变main函数里的变量这是两个独立的函数显然不可能
1. 使用static修饰变量
用static来修饰age属性底层用指针访问block内部引用的是age的地址值函数间会传递变量的地址可以根据地址去修改age的值修改的就是同一块内存 但不好的是age属性会一直存放在内存中不销毁造成多余的内存占用而且会改变age属性的性质不再是一个auto变量了
2. 使用__block修饰变量
用__block来修饰属性底层会生成__Block_byref_age_0类型的结构体对象里面存储着age的真实值 转换成C文件来查看内部结构经__block修饰后会根据__main_block_impl_0里生成的age对象来修改内部的成员变量age而且在外面打印的age属性的地址值也是__Block_byref_age_0结构体里的成员变量age的地址目的就是不需要知道内部的真实实现所看到的就是打印出来的值
struct __Block_byref_age_0 {void *__isa;
__Block_byref_age_0 *__forwarding; // 指向结构体本身int __flags;int __size;int age;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_age_0 *age; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags0) : age(_age-__forwarding) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;}
};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(int argc, const char * argv[]) {/* autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // 传进去的是age的地址__attribute__((__blocks__(byref))) __Block_byref_age_0 age {(void*)0,(__Block_byref_age_0 *)age, 0, sizeof(__Block_byref_age_0), 10};Block block ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA, p, (__Block_byref_age_0 *)age, 570425344));((void (*)(__block_impl *))((__block_impl *)block)-FuncPtr)((__block_impl *)block);}return 0;
}总结
__block可以用于解决block内部无法修改auto变量值的问题编译器会将__block变量包装成一个对象其实修改的变量是__block生成的对象里面存储的变量的值而不是外面的auto变量但是内部生成的相同的变量的地址和外面的auto变量地址值是一样的所以修改了内部的变量也会修改了外面的auto变量__block不能修饰全局变量、静态变量static
__block内存管理
程序编译时block和__block都是在栈中的这时并不会对__block变量产生强引用
因为__block也会包装成 OC对象所以block底层也会生成copy函数和dispose函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)dst-age, (void*)src-age, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src-age, 8/*BLOCK_FIELD_IS_BYREF*/);}Block复制到堆上
当block被copy到堆时会调用block内部的copy函数copy函数内部会调用_Block_object_assign函数_Block_object_assign函数会对__block变量形成强引用retain 实际上这时__block修饰的变量因为被包装成了OC对象所以也会被拷贝到堆上如果再有block强引用__block由于__block变量已经拷贝到堆上了就不会再拷贝了 Block从堆上移除
当block从堆中移除时会调用block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数_Block_object_dispose函数会自动释放引用的__block变量release
如果有多个block同时持有着__block变量那么只有所有的block都从堆中移除了__block变量才会被释放 __block和OC对象在block中的区别
__block生成的对象就是强引用而NSObject对象会根据修饰符__strong或者__weak来区分是否要进行retain操作
注意__weak不能修饰基本数据类型编译器会报__weak only applies to Objective-C object or block pointer types; type here is int警告
__forwarding指针
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_age_0 *age __cself-age; // bound by ref(age-__forwarding-age) 20;
}在栈中__block中的__forwarding指针指向自己的内存地址复制到堆中之后__forwarding指针指向堆中的__block堆中的__forwarding指向堆中的__block这样的目的都是为了不论访问的__block是在栈上还是在堆上都可以通过__forwarding指针找到存储在堆中的auto变量 保证20被存储在堆中Block所引用的变量
__block修饰对象类型
情况类似于Block捕获对象类型的auto变量__block包装的对象结构体里的对象变量会有__strong或__weak修饰
当__block对象在栈上时不会对指向的对象产生强引用
当__block对象被copy到堆上时也会生成一个新的结构体对象并且只会被block进行强引用会根据不同的修饰符__strong和__weak来对应着该对象类型成员变量是被强引用retain或弱引用
struct __Block_byref_weakPerson_0 {void __isa;__Block_byref_weakPerson_0 __forwarding;int __flags;int __size;void (__Block_byref_id_object_copy)(void, void);void (__Block_byref_id_object_dispose)(void*);Person *__weak weakPerson;
};static void __Block_byref_id_object_copy_131(void *dst, void src) {
_Block_object_assign((char)dst 40, *(void * ) ((char)src 40), 131);
}
static void __Block_byref_id_object_dispose_131(void src) {
_Block_object_dispose((void * ) ((char)src 40), 131);// __Block_byref_weakPerson_0 weakPerson {0, weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
__attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson {(void*)0,(__Block_byref_weakPerson_0 *)weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};注在MRC环境下即使用__block修饰对于结构体对象的成员变量__block内部只会对auto变量进行弱引用无论加不加__weakblock还没有释放__block修饰的变量就已经释放了这点和在ARC环境下不同
Block循环引用
两个对象相互强引用导致谁的引用计数都不会归零谁都不会释放
int main(int argc, const char * argv[]) {autoreleasepool {Person* person [[Person alloc] init];person.age 21;person.block ^{NSLog(%d, person.age);};}NSLog(111111111111);return 0;
}结果就是person对象不会释放因为没有调用dealloc方法
person对象里面的block属性强引用着block对象而block对象内部也会有一个person的成员变量指向这个Person对象这样就会造成循环引用谁也无法释放
implementation Person- (void)test {self.block ^{NSLog(%d, self.age);};
}- (void)dealloc
{NSLog(%s, __func__);
}endint main(int argc, const char * argv[]) {autoreleasepool {Person* person [[Person alloc] init];person.age 21;[person test];}return 0;NSLog(111111111111);
}block引用捕获之前提到self就是函数的第一个参数参数也是局部变量selfself又持有block同样会造成循环引用 解决办法 使用__weak、__unsafe_unretained让Block指向对象的引用变为弱引用 // __unsafe_unretained typeof(self)weakSelf self;
__weak typeof(self)weakSelf self;self.block ^{NSLog(%d, weakSelf.age);
};用__block解决用__block修饰对象会造成三者相互引用造成循环引用需要手动调用block __block Person* person [[Person alloc] init];
person.age 21;
person.block ^{NSLog(%d, person.age);person nil;
};
person.block();block内部也需要手动将person置空这个person是__block内部生成的指向Person对象的变量 block传参将self作为参数传入block中进行指针拷贝并没有对self进行持有 // Person.m
self.block ^(Person * _Nonnull person) {NSLog(%d, person.age);
};
self.block(self);MRC下不支持__weak只能使用__unsafe_unretained MRC下直接使用__block即可解决循环引用上面提到了MRC环境下__block修饰的变量只会被弱引用已达成效果 __block Person *person [[Person alloc] init];
person.age 10;person.block [^{NSLog(age is %d, person.age);
} copy];[person release];强弱共舞
这种情况虽没有引起循环引用但block延迟执行2秒等person释放后就无法获取其age很不合理
__weak typeof(person) weakPerson person;
person.block ^{dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(%d, weakPerson.age);});
};
person.block();改进一下
__weak typeof(person) weakPerson person;
person.block ^{__strong __typeof(weakPerson)strongPerson weakPerson;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(%d, strongPerson.age);});
};
person.block();通过运行结果发现完全解决了以上self中途被释放的问题这是为什么呢分析如下
在完成block中的操作之后才调用了dealloc方法。添加strongWeak之后持有关系为self - block - strongWeak - weakSelf - selfweakSelf被强引用了就不会自动释放因为strongWeak只是一个临时变量它的声明周期只在block内部block执行完毕后strongWeak就会释放而弱引用weakSelf也会自动释放
总结
Block在iOS开发中极为重要非常适合处理异步操作、回调、集合操作等场景重点学习Block的内存管理、变量捕获和循环引用解决方案