深究block

想了半天不知道怎么开头,那就直接开始吧。

其实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呢?我也不能准确得出结论,留个疑问吧,路过的同学,欢迎分享。

先写到这吧,今天不想写了,后续再修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值