想了半天不知道怎么开头,那就直接开始吧。
其实block在工作中用到的地方很多,功能很强大,只是以前老是听人说:“block会有很多坑,不熟悉的话还是用delegate代替吧”,自己也就知难而退。最近决定深究一下block,以后要广泛使用。
目录:
一:block的使用场景
二:block使用中的坑和小技巧
三:搬点理论
一:block的使用场景
“一门技术,如果不为所用,那么学了跟没学一样“。(引自某IT网红)。所以,首要问题就是使用场景。
1:系统API层面,各种UsingBlock和GCD
从iOS4之后,苹果开始“大肆”使用block,这样很方便。把原来分散的代码封装成一个功能模块,确实提高了封装性。比如:
[UIView animateWithDuration:.23 animations:^{
self.view.alpha = 1;
}];
又比如:
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"obj:%@", obj);
}];
又比如:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor cyanColor];
});
2:自定义block,处理View事件回调
比如,把UIButton的响应事件封装成block,这样就比较模块化的处理事件回调。UIButton样式没有详细设置,此处只是为了功能展示。
#import "UIButton+Addition.h"
#import <objc/runtime.h>
static const void *kButtonHandlerKey = &kButtonHandlerKey;
@implementation UIButton (Addition)
+ (UIButton *)buttonWithHandler:(void(^)(id sender)) handler {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:button action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
// 绑定属性
!handler ?: objc_setAssociatedObject(self, kButtonHandlerKey, handler, OBJC_ASSOCIATION_COPY_NONATOMIC);
return button;
}
- (void)buttonAction:(UIButton *)button {
void (^handler)(id obj) = objc_getAssociatedObject([UIButton class], (kButtonHandlerKey));
!handler ?: handler(button);
}
@end
然后,创建UIButton就可以这样:
UIButton *button = [UIButton buttonWithHandler:^(id sender) {
NSLog(@"button has touched!");
}];
3:自定义block,处理业务逻辑或功能代码封装
比如:通过block过滤集合
@implementation NSArray (Addition)
- (NSArray *)array_filter:(BOOL(^)(id obj))handler {
if (!handler) {
return nil;
}
NSMutableArray *mArr = [NSMutableArray new];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
BOOL shouldPreserve = handler(obj);
if (shouldPreserve) {
[mArr addObject:obj];
}
}];
return mArr;
}
@end
然后在外边就可以使用这个category过滤,过滤不是UIView类派生的对象
[array array_filter:^BOOL(id obj) {
return [obj isKindOfClass:[UIView class]];
}];
4:通过block封装KVO,方法太多,此处不做过多展示。想一探究竟的同学,请移步Block Kit。
最后,总结下关于block和delegate的区别:block适用于功能性和View层的逻辑处理或事件封装。delegate这么重量级的回调还是用在Controller之间比较合适。(同样盗用另一个IT网红)
二:block使用中的坑和小技巧
大多数人想到关于block的坑,就是特指retain-cycle,而我也是大多数人之一。
retain-cycle出现的原因,后边再聊。我们经常用到的解决这种问题的办法就是__weak __strong
。
比如:
__weak NSObject *weakSelf = self;
__strong NSObject *strongSelf = weakSelf;
而今天给大家带来的是一款更方便的产品,EXTScope
以后就可以这样撸代码了:
@weakify(self);
[infoItemView byq_tapWithBlock:^{
@strongify(self);
if ([self.delegate respondsToSelector:@selector(getStudentEducationInfoApply)]) {
[self.delegate getStudentEducationInfoApply];
}
}];
三:搬点理论
1,为什么block内部不能修改capture到的局部变量?
形如:
int value = 0;
void (^block) () = ^{
value = 10;
};
这样,编译器会叫的。他会说Variable is not assignable(missing __block type specifier
多么贴心的编译器!那你照做就行了,加上__block
之后就立马不叫了。
再吊个胃口:
int value = 0;
void (^block) () = ^{
NSLog(@"%d", value);
};
value += 10;
block();
为什么我都value += 10
,为什么还是输出0?包括为什么不加__block,就不可修改?这两个问题都可以归结于block捕捉到的局部变量当做const对待。我们做个试验:
int main(int argc, const char * argv[]) {
int value = 0;
void (^WCBlock)(void) = ^{
NSLog(@"%d",value);
};
return 0;
}
这样一段代码,经过clang -rewrite-objc main.m
之后,就变成了以下模样(只摘取重要代码)
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int value;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int value = __cself->value; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_mrhq2p613hxcwc0h1thy9l6c0000gn_T_main_589a5c_mi_0,value);
}
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, const char * argv[]) {
int value = 0;
void (*WCBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, value));
return 0;
}
__main_block_impl_0这个struct就是block的真身,可以看到刚才声明的“value”变量,已经被吞入囊中。
我们加上static:
int main(int argc, const char * argv[]) {
static int value = 0;
void (^WCBlock)(void) = ^{
NSLog(@"%d",value);
};
return 0;
}
同样 clang -rewrite-objc main.m
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *value;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_value, int flags=0) : value(_value) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *value = __cself->value; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_mrhq2p613hxcwc0h1thy9l6c0000gn_T_main_5805b5_mi_0,(*value));
}
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, const char * argv[]) {
static int value = 0;
void (*WCBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &value));
return 0;
}
好了,到此大伙儿肯定已经晕了。我们只对比__main_block_impl_0,发现了什么?
代码中局部变量没有static修饰,在__main_block_impl_0这个struct里呈现的是一个变量int value
;代码中局部变量有static修饰,在__main_block_impl_0这个struct里呈现是确实一个变量指针int *value
。
*那么,其实也可以得出结论了:block访问static修饰的局部变量,该变量通过指针传递,block内可以修改;block访问局部变量(auto),该变量通过值传递,block内不可修改。*
——————————————分割线——————————————
上边聊了static修饰的局部变量在block内部有着不同的表现。下边聊聊另一个神一样存在的修饰符__block
上代码:
int main(int argc, const char * argv[]) {
__block int value;
void (^WCBlock)(void) = ^{
value = 1;
};
return 0;
}
同样 clang -rewrite-objc main.m
struct __Block_byref_value_0 {
void *__isa;
__Block_byref_value_0 *__forwarding;
int __flags;
int __size;
int value;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_value_0 *value; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : value(_value->__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_value_0 *value = __cself->value; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_mrhq2p613hxcwc0h1thy9l6c0000gn_T_main_a6294d_mi_0,(value->__forwarding->value));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->value, (void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->value, 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, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_value_0 value = {(void*)0,(__Block_byref_value_0 *)&value, 0, sizeof(__Block_byref_value_0), 0};
void (*WCBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_value_0 *)&value, 570425344));
return 0;
}
可以看出,多了一个__Block_byref_value_0这样一个struct,太狠了,竟然生成一个新的数据结构。
既然static和__block都可以实现在block内部修改局部变量,那么为什么还要用__block生成一个新的struct呢?我也不能准确得出结论,留个疑问吧,路过的同学,欢迎分享。
先写到这吧,今天不想写了,后续再修改。