关于objective-c:ObjectiveC-之-block

37次阅读

共计 10563 个字符,预计需要花费 27 分钟才能阅读完成。

前言

作为 iOS 开发,咱们素日里会高频应用 block,block 十分重要,在学习 Swift 闭包时,我忽然感觉能够将 Objective-C block 和 Swift 闭包 一起比照学习。

如果你针对上面的问题曾经有了比拟深的了解,那么能够略过本篇文章:

  1. block 的数据结构
  2. block 的内存机制
  3. block 和 weakify/strongify 的关联
  4. Swift 闭包和 Objective-C block 的区别
  5. dispatch_block_t 的利用场景

一、block 的数据结构

(一)block 语法解析

作为硬核派,理解 block 数据结构咱们必定不能 Google 他人的论断,咱们有本人的 clang 工具,应用 clang 工具,能够将 OC 代码转成 C++ 代码。

首先,咱们筹备 main.m 这个类,类内容为:

// main.m 

int main() {return 1;}

咱们切到 main.m 类所在文件夹,应用指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m,发现语法解析生成的 cpp 代码如下main.cpp

// main.cpp

#import <UIKit/UIKit.h>

int main() {void (^block)(void) = ^ {NSLog(@"Hello World!");
    };
    block();
    return 1;
}

接着咱们在 main.m 中增加 block 代码:

// main.m 

int main() {void (^block)(void) = ^ { };
    block();
    return 1;
}

持续应用 clang 解析main.m,发现生成的 cpp 代码如下:

// main.cpp

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_9b_w0ymsg0n3yqdlb90w49xqmz40000gn_T_main_2428cf_mi_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)};
int main() {void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 1;
}
static struct IMAGE_INFO {unsigned version; unsigned flag;} _OBJC_IMAGE_INFO = {0, 2};

去除强制转换后咱们能够看出申明 block 的时候,block 底层调用了 __main_block_iml_0 构造体,传入的参数别离是 __main_block_func_0(办法函数)&__main_block_desc_0_DATA(构造体地址)

(二)block Cpp 数据结构解析

1.__main_block_func_0 的办法代码段

// 须要传入的参数是构造体:__main_block_impl_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_9b_w0ymsg0n3yqdlb90w49xqmz40000gn_T_main_2428cf_mi_0);
    }
}

能够看到,这个函数体中传入了 __cself 和 block 中调用的办法 NSLog

2. __main_block_desc_0_DATA 的构造体代码段

static struct __main_block_desc_0 {
  size_t reserved;       // 作用不大,不须要理睬
  size_t Block_size;     // 整个 block 的在内存中占的字节大小
} __main_block_desc_0_DATA = {0, sizeof(struct __main_block_impl_0)};// 计算 blcok 主构造体__main_block_impl_0 的大小

总的来言,此构造体就是为了保留 block 构造体的大小

3. __main_block_impl_0

__main_block_impl_0 是承载 block 最重要的构造,钻研 __main_block_impl_0 能够从两个方面。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

能够发现,__main_block_impl_0 构造中次要蕴含了两个构造体:

  • struct __block_impl impl:函数指针
  • struct __main_block_desc_0* Desc:block 大小等内存信息

__block_impl__main_block_desc_0 均由构建 block的办法传入,比方下面咱们构建 block 传入的两个参数:__main_block_func_0__main_block_desc_0,就是用来构建 implDesc 的。

二、block 束缚问题

通过剖析一般 block 函数的 Cpp 构造,咱们明确了 block 函数的根本数据结构,上面咱们进一步剖析:为什么应用 block 时,须要有以下的关键词润饰:

  • 捕捉的变量须要应用 __block 能力批改
  • 应用 self 须要用关键词 weakify、strongify 润饰

(一)捕捉的变量须要应用 __block 能力批改

#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad {[super viewDidLoad];
    
    __block NSInteger num = 0;
    void (^blockTest)(void) = ^ {num = 1;};
    blockTest();}

@end

代码示例如上,咱们能够发现,如果咱们定义的 NSInteger num 如果不必 __block 润饰,编译器会报错:Variable is not assignable (missing __block type specifier),那么咱们会产生一个疑难:block到底是如何捕捉变量的呢?为什么我要批改的变量必须要用 __block 关键词 进行润饰能力在 block 中对其进行批改?

应用指令将上述代码生成对应的 cpp 代码,内容如下:

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 0};
    void (*blockTest)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blockTest)->FuncPtr)((__block_impl *)blockTest);
}

咱们发现和第一局部咱们在 main.m 定义简略的 block 不同,这里的生成的 block 并不是struct __main_block_impl_0,而是struct __ViewController__viewDidLoad_block_impl_0

所以咱们首先能够明确的是,不同的 block 有其本人的命名标准,但后缀根本都是_block_impl_0

接着咱们查看 __ViewController__viewDidLoad_block_impl_0 的定义:

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

与第一局部 __main_block_impl_0 相比,__ViewController__viewDidLoad_block_impl_0减少了 __Block_byref_num_0 *num 这个字段,也就是说咱们在 block 中援用的字段,都会呈现在 block 构造体中。

咱们看到 __Block_byref_num_0 *num 后标注了 // by ref,也就意味着 block 中对num 理论是援用(不是 copy),所以咱们须要对 num 应用关键词 __block 将其转成 __Block_byref_num_0 援用类型。

(二)应用 self 须要用关键词 weakify/strongify 润饰

1. 不应用 weakify/strongify 润饰,会产生什么?

初学 block 时,咱们大概率都会遇到一个问题:block中应用的 self 没有进行 weakify/strongify解决,咱们也晓得这样做的问题:

block 中应用了 self(没有进行weakify/strongify 申明),当执行 block 时,self如果曾经被开释,那么在 block 中执行 self 办法利用就会 crash,因为 self 曾经被开释。

如果不对 block 中应用的 self 申明weakify/strongify,生成的 cpp 代码会是什么状况:

#import "ViewController.h"

@implementation ViewController
- (void)viewDidLoad {[super viewDidLoad];
    
    void (^blockTest)(void) = ^ {self.view.backgroundColor = [UIColor orangeColor];
    };
    blockTest();}

@end

生成的 cpp 代码:

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  ViewController *self;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, ViewController *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  ViewController *self = __cself->self; // bound by copy

        ((void (*)(id, SEL, UIColor * _Nullable))(void *)objc_msgSend)((id)((UIView *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("view")), sel_registerName("setBackgroundColor:"), ((UIColor * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("UIColor"), sel_registerName("orangeColor")));
    }
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = {0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    void (*blockTest)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blockTest)->FuncPtr)((__block_impl *)blockTest);
}

要害语句是:

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  ViewController *self = __cself->self; // bound by copy

        ((void (*)(id, SEL, UIColor * _Nullable))(void *)objc_msgSend)((id)((UIView *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("view")), sel_registerName("setBackgroundColor:"), ((UIColor * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("UIColor"), sel_registerName("orangeColor")));
    }

bound by copy,应用 copy 的形式进行绑定,咱们晓得 copy 意味着浅拷贝,被援用的对象援用计数会 +1,那么这样就会出问题:

  1. self 中定义了 block,相当于 self 持有了block
  2. 同时 block 中又持有了 self
  3. 导致循环援用,该开释的对象无奈被开释,内存泄露

为了验证下面所说的 循环援用导致无奈回收 的状况,咱们来模仿一个场景:

@implementation BNDestroyDemoView

- (instancetype)initWithFrame:(CGRect)frame {if (self = [super initWithFrame:frame]) {dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"BNDestroyDemoView initWithFrame:%@",self);
        });
    }
    return self;
}

@end

咱们新建了一个类BNDestroyDemoView.h,这个类被创立后会被立即置 nil:

@interface ViewController ()

@property (nonatomic, strong) BNDestroyDemoView *demoView;

@end

@implementation ViewController

- (void)viewDidLoad {[super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.demoView = [[BNDestroyDemoView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
    self.demoView = nil;
}


@end

这个类会提早 3 秒执行 dispatch_after 中的 block 内容,即便咱们曾经将 self.demoView置 nil,3 秒后咱们仍旧能够看到如下的日志打印,表明 self 并没有被零碎回收:

2. weakify/strongify 润饰,是进行深拷贝吗?

通过下面的剖析,咱们明确一个情理:应用 block 时要防止产生 循环援用,既然浅拷贝会导致援用计数 +1。

既然如上的浅拷贝逻辑会导致循环援用,咱们有什么方法解决循环援用呢?

  • 深拷贝
  • 弱援用,强应用

深拷贝的办法是将 self 的内存间接拷贝一份,不对原 self 的援用计数新增,这种办法首先从开销上会比拟大,而且有时 self 如果被重置为 nil,咱们的指标就是不执行 self 的办法,而不是执行深拷贝后的 self 办法。

所以那就只有应用 弱援用,强应用 的办法了,这种办法在 iOS 开发中是一种通用的解决方案,在 Runloop 循环援用 TimerYYAsyncLabel等技术计划中都有应用,上面进行具体的论述。

弱援用 的意思是:我传入 block 中的 self 通过 weak进行润饰,不减少 self 的援用计数

强应用 的意思是:我在执行 block 办法体期间,须要将 弱援用 self改为 强援用 ,防止在执行block 期间 self 被回收。

对应的代码实现如下:

- (void)viewDidLoad {[super viewDidLoad];
    
    __weak __typeof(self) weakSelf  = self;
    void (^block)(void) = ^ {__strong __typeof(self) strongSelf = weakSelf;
        NSLog(@"Hello World!");
    };
    block();}

于此咱们便解决了应用 block 会导致循环援用的问题,但继而又产生了一个问题:

强援用 self有可能为 nil 吗?

答案是:可能。如果在执行 block 之前,self就曾经被回收,因为 block 在执行前对 self 是弱援用,所以 self 是有可能变为 nil 的。

@implementation BNDestroyDemoView

- (instancetype)initWithFrame:(CGRect)frame {if (self = [super initWithFrame:frame]) {__weak __typeof(self) weakSelf  = self;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{__strong __typeof(self) strongSelf = weakSelf;
            NSLog(@"BNDestroyDemoView initWithFrame:%@",@[strongSelf]);
        });
    }
    return self;
}

@end

下面这段代码是十分不强壮的,如果 BNDestroyDemoView 在执行 block 之前被零碎回收,就会导致 crash:

三、dispatch_block_t 的利用场景

通常咱们写一个不带参数的块回调函数是这样写的:

在 . h 头文件中
typedef void (^leftBlockAction)(); // 定义类型
-(void)leftButtonAction:(leftBlockAction)leftBlock; // 在定义一个回调函数:在.m 文件中:-(void)leftButtonAction:(leftBlockAction)leftBlock{leftBlock();
}

应用dispatch_block_t 只有在.h 头文件定义属性办法

@property (nonatomic,copy) dispatch_block_t leftBlockAction;

在.m 文件 调用的办法里调用

if (self.leftBlockAction) {self.leftBlockAction();
}

在另个模块里间接:

MyAlertView *alert = [[MyAlertView alloc]init];
alert.leftBlockAction = ^() {NSLog(@"left button clicked");
};

** 这个公众号会继续更新技术计划、关注业内技术动向,关注一下老本不高,错过干货损失不小。
↓↓↓**

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0