-
Notifications
You must be signed in to change notification settings - Fork 0
Objc Block
block是带有自动变量的匿名函数。
匿名函数: 没有函数名的函数,一对{}包裹的内容是匿名函数的作用域。
自动变量: 栈上声明的一个变量不是静态变量和全局变量,是不可以在这个栈内声明的匿名函数中使用的,但在Block中却可以。虽然使用block不用声明类,但是block提供了类似Objective-C的类一样可以通过成员变量来保存作用域外变量值的方法,那些在block的一对{}里使用到但却是在{}作用域以外声明的变量,就是block捕获的自动变量。
根据作用域不同,block分为如下三类:
- _NSConcreteGlobalBlock - 全局的静态block,不会访问任何外部变量(ARC&MRC)
- _NSConcreteStackBlock - 保存在栈中的block,当函数返回时会被销毁(MRC)
- _NSConcreteMallocBlock - 保存在堆中的block, 当引用计数为0时会被销毁(ARC&MRC)
Q: 为何要用copy
修饰block呢?(严格来说这个问题在ARC环境下是有问题的或者说过时的。)
A1: 其实这个表述方式指的是MRC环境下,block中使用了外部变量,默认是_NSConcreteStackBlock,存放在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃; 这个时候使用copy修饰,这可以让block从栈中复制到堆中,变为_NSConcreteMallocBlock
,程序员自己管理,就可以避免block对象随时被销毁了。
A2: 实际上在ARC下,通常只会看到_NSConcreteGlobalBlock与_NSConcreteMallocBlock, 因为系统会默认对使用外部变量的block进行copy操作,无论是用copy修饰还是strong修饰,最终都是先看是否引用了外部变量,之后不论是copy还是strong都执行了copy操作,新的类型是根据是否引用外部变量来决定的。
!!!当然使用copy修饰,更加符合代码统一风格。
block表达式语法
^ 返回值类型 (参数列表) {表达式}
block变量语法
返回值类型 (^变量名)(参数列表) = Block表达式
自定义了一个继承至NSObject的block研究类BlockResearch, 在里面编写了block的各种情况
typedef void(^Success)(NSData *response);
typedef void(^Failure)(NSString *message, int code);
@interface BlockResearch : NSObject
@property (nonatomic, copy) void(^show)(); /// 无返回值,无参(参数没有写void),会有警告信息`This block declaration is not a prototype`
@property (nonatomic, copy) void(^animation)(void); /// 无返回值,无参(参数有写void),不会有警告信息
@property (nonatomic, copy) void(^completion)(BOOL finished); /// 无返回值,有一个参数
@property (nonatomic, copy) Success success; /// 使用typedef别名定义的block,无返回值, 有一个参数;
@property (nonatomic, copy) Failure failure; /// 使用typedef别名定义的block,无返回值, 有两个参数;
@property (nonatomic, copy) BOOL(^addition)(int a, int b); /// 有返回值,有两个参数
@end
在比如ViewController.m中#import BlockResearch.h之后,开始编写具体使用的代码
- (void)testBlock {
BlockResearch *br = [[BlockResearch alloc]init];
// MARK: - 1. 无返回值,无参(参数没有写void),会有警告信息
/// 调用
br.show = ^{
NSLog(@"show");
};
/// 回调执行
br.show();
// MARK: - 2. 无返回值,无参(参数没有写void),会有警告信息
/// 调用
br.animation = ^{
NSLog(@"hide");
};
/// 回调执行
br.animation();
// MARK: - 3. 无返回值,有一个参数
/// 调用
br.completion = ^(BOOL finished) {
NSString *result = finished == YES ? @"is finished" : @"not finished";
NSLog(@"animation %@", result);
};
/// 回调执行
br.completion(YES);
// MARK: - 4. 使用typedef别名定义的block,无返回值, 有一个参数;
/// 调用
br.success = ^(NSData *response) {
NSLog(@"the response data is: %@",response);
};
/// 回调执行
br.success([NSData data]);
// MARK: - 5. 使用typedef别名定义的block,无返回值, 有两个参数;
/// 调用
br.failure = ^(NSString *message, int code) {
NSString *result = code == 200 ? @"success" : @"failure";
NSLog(@"the %@ request code is: %@", result, message);
};
/// 回调执行
br.failure(@"bad gateway", 400);
// MARK: - 6. 有返回值,有两个参数
/// 对于block有返回值这种,有后面会介绍一种方法,模仿swift高级函数如map, filter等, 优点会体现的很明显,当然日常也可以使用
/// 调用
br.addition = ^BOOL(int a, int b) {
NSLog(@"addition result is %d", a+b);
return YES;
};
/// 回调执行
br.addition(1, 2);
}
// MARK: - block做为参数
- (void)requestWith:(NSString *)url
success:(void(^)(id response))success
failure:(void(^)(NSString *message, int code))failure {
///NSURL *ur = [[NSURL alloc]initWithString:url];
/// 其他逻辑
/// 判断是否成功
/// 以下仅为了代码可以执行,随意写的,不做实际用途
BOOL isSuccess = [url isEqualToString:@""] ? YES : NO;
if (isSuccess) { /// 如果成功
id response = (id)@"{data:{}}";
success(response);
} else { /// 如果失败
failure(@"bad gateway",400);
}
}
实际调用
[self requestWith:@"https://www.baidu.com"
success:^(id response) {
NSLog(@"success %@",response);
} failure:^(NSString *message, int code) {
NSLog(@"failure %@-%d",message, code);
}];
block表达式可以捕获外部自动变量的值,这个是值是瞬间值,所以那怕声明block之后,在block外修改自动变量的值,也不会对block内部捕获的自动变量值产生影响。
- (void)testCaptureVariable {
int a = 10;
void(^block)(void) = ^{
NSLog(@"within block, a = %d", a);
};
a = 20;
block();
NSLog(@"a = %d", a);
}
输出结果如下:
within block, a = 10
a = 20
若是我们变量声明前面加上_ _block修饰,
- (void)testCaptureVariable {
__block int a = 10;
void(^block)(void) = ^{
NSLog(@"within block, a = %d", a);
};
a = 20;
block();
NSLog(@"a = %d", a);
}
输出结果如下:
within block, a = 20
a = 20
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的,如下图所示
对于用 _ _block 修饰的外部变量引用,block 是复制其引用地址来实现访问的,如下图所示
以上的结论会在block探索章节得到验证
如下样例,定义了一个cycleRetainBlock, 然后执行表达式
@property (nonatomic, copy) void(^cycleRetainBlock)(void);
self.cycleRetainBlock = ^{
NSLog(@"self %@",self);
};
会显示警告信息:Capturing 'self' strongly in this block is likely to lead to a retain cycle
, 这就是block的循环引用。
self拥有成员变量cycleRetainBlock--->就持有cycleRetainBlock, 在cycleRetainBlock代码块中打印self的信息,就会使cycleRetainBlock---->持有self, 这就导致了两个相互持有,不能在作用域结束后正常释放。
解决原理:打破两个的相互持有,self对cycleRetainBlock的持有不好打破,因为得使用这个block, 那么选择在block内打破持有。
解决方法:
-
- 使用__weak typeof(self)
- (void)testCycleRetainOfBlock {
__weak typeof(self) weakSelf = self;
self.cycleRetainBlock = ^{
NSLog(@"self %@",weakSelf);
};
self.cycleRetainBlock();
}
-
- Reactive Cocoa中的
@weakify
和@strongify
- Reactive Cocoa中的
- (void)testCycleRetainOfBlock {
@weakify(self);
self.cycleRetainBlock = ^{
@strongify(self)
NSLog(@"self %@",self);
};
self.cycleRetainBlock();
}
有以下样例代码:
@property (nonatomic, copy) void(^copyBlock)(void);
@property (nonatomic, strong) void(^strongBlock)(void);
- (void)testBlockTypes {
///1. 没有引用外部变量
void(^block01)(void) = ^{
NSLog(@"within block01");
};
NSLog(@"block01-%@",block01);
///2. 引用外部变量
int a = 1;
void(^block02)(void) = ^{
NSLog(@"within block02-%d",a);
};
NSLog(@"block02-%@",block02);
///3. 直接打印输出引用外部变量的block copy
NSLog(@"block01 copy-%@",[block01 copy]);
///4. 定义了一个新的变量来接受,block的copy
void(^block03)(void) = [block02 copy];
NSLog(@"block03-%@",block03);
///5. 没有引用外部变量,但是是copy修饰的成员变量
self.copyBlock = ^{
NSLog(@"within self.copyBlock");
};
NSLog(@"copyBlock-%@",self.copyBlock);
///6. 没有引用外部变量,但是是strong修饰的成员变量
self.strongBlock = ^{
NSLog(@"within self.strongBlock");
};
NSLog(@"strongBlock-%@",self.strongBlock);
}
执行结果:
1. block01-<__NSGlobalBlock__: 0x100e74b88>
2. block02-<__NSMallocBlock__: 0x600001b701b0>
3. block01 copy-<__NSGlobalBlock__: 0x100e74b88>
4. block03-<__NSMallocBlock__: 0x600001b701b0>
5. copyBlock-<__NSGlobalBlock__: 0x100e74bc8>
6. strongBlock-<__NSGlobalBlock__: 0x100e74be8>
通过上面例子,我们可以看出: 在默认ARC环境下,对于没有使用外部变量的block,都是__NSGlobalBlock__,保存在全局的静态block; 此时无论是copy操作,还是用copy, strong操作,最后都是__NSGlobalBlock__类型,这个通过上面3,5,6都可以看出来; 对于使用外部变量,以及对其进行copy复制的,都会复制到堆区,变成__NSMallocBlock__.
以上就可以验证开头的QA问答了!!!
对于要验证MRC的,可以: 在工程-->Build Phases-->Compile Sources中,将需要切换为MRC的类文件后加上-fno-objc-arc
即可
把上面的样例代码再执行一遍,得到结果如下:
1. block01-<__NSGlobalBlock__: 0x10bcbbb98>
2. block02-<__NSStackBlock__: 0x7ffee4203980>
3. block01 copy-<__NSGlobalBlock__: 0x10bcbbb98>
4. block03-<__NSMallocBlock__: 0x6000017215c0>
5. copyBlock-<__NSGlobalBlock__: 0x10bcbbbd8>
6. strongBlock-<__NSGlobalBlock__: 0x10bcbbbf8>
可以看到2输出的结果就是__NSStackBlock__了,对其复制操作的4,得到结果就是__NSMallocBlock__。
以上就是对开头QA的验证!
创建一个demo来了解block的内部结构, oc或者c的皆可。这里以oc创建的demo为例;
若是使用c的demo,使用LLVM编译器的clang命令可将含有block的Objective-C代码转换成C++的源代码, 命令
clang -rewrite-objc main.c -o main.cpp
或者clang -rewrite-objc 源码文件名
1.1 在main.m文件的main函数中,编写一个block如下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
void(^testBlock)(void) = ^() {
NSLog(@"test");
};
testBlock();
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
1.2. 在命令行cd到main.m所在的目录
1.3. 使用clang工具,在命令行中执行其命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
1.4. 使用命令ls
,在当前目录中可以查看到多了一个文件:main.cpp
1.5. 执行命令open main.cpp
,就可以打开该文件
1.6. 该文件中内容很多,可以从上往下过一遍,也可以通过关键字"StackBlock"搜索,快速定位到__main_block_impl_0
就是对应block的结构体代码了,如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc; // 描述block大小、版本等信息
//构造函数函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; /// isa指向栈
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们可以看到__main_block_impl_0结构体,包含了另外两个结构体,__block_impl
与__main_block_desc_0
__block_impl结构体如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__main_block_desc_0结构体如下:
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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_jksy2fhn6kv3_yf8_wfwjhqh0000gq_T_main_9d24b6_mi_0);
}
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
其中,涉及我们写的block被转成了如下
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
接下来,我们在上一节block基础上做一些调整,增加一个外部变量a
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
int a = 1;
void(^testBlock)(void) = ^() {
NSLog(@"test a: %d",a);
};
testBlock();
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
再重复操作一遍上面的步骤, 最后得到的main.cpp
文件,同样的查看方式,发现__main_block_impl_0
有了变化
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在__main_block_impl_0
中多了int a;
这证明了前面的结论:对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的
, 同时也可以证明捕获自动变量如何实现的,因为瞬间将变量捕获,所以不受外部修改的影响, 当然捕获了变量,复制到block内,block的size也会变化。
其他源码中,也可以看到该变量a
的存在
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_jksy2fhn6kv3_yf8_wfwjhqh0000gq_T_main_5879a6_mi_0,a);
}
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)};
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 1;
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
这一步,我们以在block中修改外部变量的值为例,来说明
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
__block int a = 1;
void(^testBlock)(void) = ^() {
a = 10;
NSLog(@"test a: %d",a);
};
testBlock();
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
输入结果test a: 10
, 说明外部变量已经被修改;
再重复执行前面的步骤,获得新的main.cpp
, 我们可以看到这次的内容与之前有很大区别
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; // by ref
__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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 10;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_jksy2fhn6kv3_yf8_wfwjhqh0000gq_T_main_464b53_mi_0,(a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
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, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
void(*testBlock)(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 *)testBlock)->FuncPtr)((__block_impl *)testBlock);
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
从代码中我们可以看到:
- 源码中增加一个名为 __Block_byref_i_0 的结构体,用来保存我们要 capture 并且修改的变量 i。
- __main_block_impl_0 中引用的是 __Block_byref_i_0 的结构体指针,这样就可以达到修改外部变量的作用。
- __Block_byref_i_0 结构体中带有 isa,说明它也是一个对象。
- 我们需要负责 __Block_byref_i_0 结构体相关的内存管理,所以 __main_block_desc_0 中增加了 copy 和 dispose 函数指针,对于在调用前后修改相应变量的引用计数。
以上也验证了前面的结论: 对于用 _ _block 修饰的外部变量引用,block 是复制其引用地址来实现访问的
iOS开发-由浅至深学习block - 涉及模仿swift高级函数, 可以看一下这篇文章,思路很好!
谈Objective-C block的实现
iOS Block用法和实现原理
iOS开发-由浅至深学习block
Block为什么使用copy修饰
iOS探索 全方位解读Block
一道Block面试题的深入挖掘