block
这个东西--基本就和我们生活息息相关了,😆,毕竟用的太多了。今天兄弟们一起探究下block
到底是个啥?🐯
1. block
分类
- 真男人从不拐弯抹角。上——代码
1.1. 全局block
void (^block)(void) = ^{
NSLog(@"xiaogu");
};
NSLog(@"%@",block);
- 输出:
当没有外部变量时是 全局
block
1.2. 堆区block
int a = 10;
void (^block)(void) = ^{
NSLog(@"xiaogu -- %d",a);
};
NSLog(@"%@",block);
- 输出:
有外部变量引入之后会变成 堆
block
1.3. 栈block
int a = 10;
void (__weak ^block)(void) = ^{
NSLog(@"xiaogu -- %d",a);
};
NSLog(@"%@",block);
- 输出:
用
__weak
修饰之后,变成了栈block
2. block
循环引用
我赌我全部财产:所有使用block的兄弟们,都经历过循环引用
-
循环引用
(我喜欢说大白话)就是:我持有你,你持有我。结果放不开了~
-
- 举个例子:
typedef void(^XGBlock)(void);
@interface XGTestVC ()
@property(nonatomic, copy)XGBlock block;
@property(nonatomic,copy)NSString * name;
@end
//实现
self.name = @"xiaogu";
self.block = ^(void){
NSLog(@"-%@-",self.name);
};
self.block();
这就造成了
循环引用
self
—持有-->block
--持有—>self
-
- 测试的话,可以写个
dealloc
方法,看退出界面是否释放
- 测试的话,可以写个
- (void)dealloc
{
NSLog(@"dealloc");
}
2.1. 解决方案
- 当我们遇到问题时,都会有各种各样的解决方法。今天和兄弟们简单说几种
2.1.1. 强弱共舞(strong - weak - dance
)
兄弟们肯定都听说这个词。今天我来献一波丑~
-
- 刚开始做
iOS
的兄弟们想到的办法就是解决依赖,然后来一波weakSelf
- 刚开始做
__weak typeof(self) weakSelf = self;
self.block = ^(void){
NSLog(@"-%@-",weakSelf.name);
};
然后发现真的解决了
循环引用
,就不管了~ 其实这样是有问题的
因为weakSelf
这样加在了弱引用表,你不知道什么时候释放。如果block
中有耗时操作,那么这个可能就是nil
了
-
- 强弱共舞(
strong - weak - dance
),来吧~ 展示~
- 强弱共舞(
__weak typeof(self) weakSelf = self;
self.block = ^(void){
__strong __typeof(weakSelf)strongSelf = weakSelf;
//耗时操作啥的
NSLog(@"-%@-",strongSelf.name);
};
这就是
强弱共舞
,strongSelf
的生命周期会在出去block代码块
释放,这是一种完美的解决方案(我感觉)~因为我喜欢,😆
2.1.2. 中间者模式 -- 手动释放
当然,也有兄弟们不喜欢强弱共舞,那也可以手动释放
__block
可以修改外部变量。那么我这么做
__block XGTestVC *tvc = self;
self.block = ^(void){
//耗时操作啥的
NSLog(@"-%@-",tvc.name);
//用完了就置nil
tvc = nil;
};
这也可以解决循环引用。毕竟
tvc
是有作用域的,出了作用域就释放了
2.1.3. 传值模式
-
- 这中就没啥好说的了,就是传过来个值,他没有持有,就不会造成循环引用了
-
- 如果如果循环引用,我更倾向 强弱共舞 的解决方便,这让人很舒服毕竟,😆
3. block
结构探索
上面那些解决方法很简单的说了下,接下来才是重头戏~
3.1. 全局block
-
- 我们先写一个比较简单的
block
- 我们先写一个比较简单的
-
- 看过我原先博客的兄弟们,都知道我要怎么看了。
xcrun -sdk iphonesimulator clang -rewrite-objc main.m
- 看过我原先博客的兄弟们,都知道我要怎么看了。
-
- 我把得出的
.cpp
对比了下
- 我把得出的
其实
block
就是一个结构体,所以可以@“%@”输出,然后他有自己的构造方法。
~ xgblock->FuncPtr(xgblock);~(xgblock)会议隐藏参数的形式传入
3.2. 堆block
-
- 我们可以看看,如果引入外部参数会怎么样
int a = 180;
void (^xgblock)(void) = ^{
NSLog(@"xiaogu-height-%d",a);
};
xgblock();
😆,我们
xcrun-clang
看下
栈的话就自己看吧,哈哈哈🙄
3.2.1. __block
修饰
兄弟们也比较清楚:在
block
块中改变外部的值需要用到__block
修饰,那我们既然探索了,就看下,__block
做了什么
-
- 测试代码
__block int a = 180;
void (^xgblock)(void) = ^{
a++;
NSLog(@"xiaogu-height-%d",a);
};
xgblock();
-
- 我们
xcrun-clang
看下
- 我们
-
- 我们看到通过
__block
修饰之后,多了这么一条。我把完整的给大家看下
- 我们看到通过
-
- 结论:通过
__block
修饰变量后,会生成一个结构体。然后这个结构体会把变量存起来,更改的时候会改结构体里面的值。(其实就是把原先的变量封装成相应的对象)
- 结论:通过
4. block
源码分析
4.1. 定位源码
我们研究之前,首先要解决一个问题:就是目前我们还不知道
block
的源码在哪?观察谁
-
- 首先我们要定位源码。(以下是我的定位过程~)
- 首先我们要定位源码。(以下是我的定位过程~)
-
- 我们找到源码之后。就舒服了。(这个源码是可以编译的。所以调试起来十分舒适~)(我用目前最新的源码跟兄弟们一起走一波)
4.2. block
栈—>堆
-
- 我看了好多网上
block
的博客,都会说:block
从栈区,然后copy
成了堆区block
- 我看了好多网上
-
- 兄弟们,我们要探究底层,当然要感受下
_Block_copy
做了什么啊。怎么从栈区到堆区的
- 兄弟们,我们要探究底层,当然要感受下
-
- 我们先写个堆区的
block
- 我们先写个堆区的
__block int a = 10;
void (^mallocBlock)(void) = ^void {
a++;
};
NSLog(@"MallocBlock is %@", mallocBlock);
-
- 然后通过定位源码过程中,我们知道它会执行
_Block_copy
(这其实就是关键方法),而且我们可以调试😆,那我们断点下,😆
- 然后通过定位源码过程中,我们知道它会执行
-
- 然后我们看下他刚进 时的值
现在他还是个栈
block
。
-
- 嘿嘿。😝,我们看看他怎么变成堆
block
的。我先把代码贴上,也可以帮助下没有源码的兄弟们~
- 嘿嘿。😝,我们看看他怎么变成堆
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy. — 如果是栈block,进行copy
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
-
- 我的断点调试
- 我的断点调试
通过
malloc
申请内存空间用于接收block
通过
memmove
将block
拷贝至新申请的内存中
设置
block
对象的类型为堆区block
,即result->isa = _NSConcreteMallocBlock
4.3. block
三层 copy
据说这个是,面试不咋问,但是和兄弟们喝酒吹牛逼容易说的技术点😆
-
- 当用
__block
修饰的变量是对象时
- 当用
__block NSString *name = [NSString stringWithFormat: @"XG"];
void(^blockCopy)(void) = ^{
name = @"xiaogu";
NSLog(@"%@",name);
};
blockCopy();
-
- 然后我们用
xcrun
得到.cpp
文件
- 然后我们用
// name最开始的赋值 __Block_byref_name_0 修饰。(结构体的赋值,跟__Block_byref_name_0一一对应)
__attribute__((__blocks__(byref))) __Block_byref_name_0 name = {
(void*)0,
(__Block_byref_name_0 *)&name,
33554432,
sizeof(__Block_byref_name_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_6j_hfrbcdq96lsbr8vr1qjw1fkm0000gn_T_main_b45ce1_mi_0)
};
//__Block_byref_name_0的结构体
struct __Block_byref_name_0 {
void *__isa;
__Block_byref_name_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *name;
};
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 块
//这里面就不说了~毕竟兄弟们已经有条件探索了,~上面也提过了,会发生内存拷贝。
void(*blockCopy)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_name_0 *)&name, 570425344));
((void (*)(__block_impl *))((__block_impl *)blockCopy)->FuncPtr)((__block_impl *)blockCopy);
我是用源调试的,所以比较好理解点
-
-
简单的总结下:通过
__block
修饰,且变量是对象会发生 三层copy
-
3.1. 第一层
copy
就是_Block_copy
--> 自身的copy
--> 从栈—>堆 -
3.2. 第二层
copy
:_Block_byref_copy
-->把修饰的对象,copy
成Block_byref
结构体类型 -
3.3.第三层
copy
:_Block_object_assign
,修饰的是对象的时候__Block_byref_id_object_copy_131
,对__block
修饰的当前对象copy
-
OK了,,哎,年底了,好忙,我又要去加班了。。o(╥﹏╥)o