概述

在iOS开发中Block使用比较广泛,对于使用以及一些常规的技术点这里不再赘述,主要利用C++角度分析Block内部数据底层实现,解开开发中为什么这样编写代码解决问题。

Block底层结构窥探

Block本质也是一个OC对象,内部也有isa指针,最终继承NSObject。它是封装了函数调用以及函数调用环境的OC对象。

接下来编写一个Block,利用clang编译器指令可以将我们编写的OC代码转换成C++代码,更好的看清Block底层结构。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool { // 定义block
void(^block)(void) = ^{
NSLog(@"-----------");
}; // 执行block
block();
}
return 0;
}

执行命令

 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

上面编写OC代码转换成C++代码main.cpp

#pragma clang assume_nonnull end

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
}; // Block底层的结构
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 保存内部代码块执行的函数地址
// 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;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; // Block内部执行代码块封装的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_3df4b0_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)/*block自己结构的内存大小*/}; int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // 定义block
// block真实类型 struct __main_block_impl_0 *
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA); // 执行block
block->FuncPtr(block);
}
return 0;
}

Block底层数据结构是__main_block_impl_0类型,定义的block变量是指向__main_block_impl_0类型的对象类型指针。

__main_block_impl_0构造方法的需要两个参数,第一个参数:__main_block_func_0函数是对Block内部代码块的封装。第二个参数类型是__main_block_desc_0_DATA类型指针,内部主要对Block的信息封装,比如包含block的占用内部空间大小。

__main_block_impl_0 构造方法中将封装block代码块的__main_block_func_0函数地址传入__main_block_impl_0 对象的FuncPtr成员保存。

当执行block,直接通过指针访问到FuncPtr成员的函数地址进行调用。

Block变量的捕获

在开发中block内部会访问外部的变量,为了保证内部能正常访问外部的变量,block有个变量捕获的机制。

编写代码验证

#import <Foundation/Foundation.h>

// 全局变量
int weight = 120; int main(int argc, const char * argv[]) {
@autoreleasepool { // 定义block
int age = 10;
static int height = 178; void(^block)(void) = ^{
NSLog(@"age is %d", age);
NSLog(@"height is %d", height);
NSLog(@"weight is %d", weight);
}; // 执行block
block();
}
return 0;
}

clang转成对应的C++代码

#pragma clang assume_nonnull end

int weight = 120;

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; // 局部变量age的捕获
int *height; // static修饰的局部变量的捕获
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; // 执行block内部代码块函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // 取出内部捕获的age
int *height = __cself->height; // 取出内部捕获的height // 打印age
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_848c45_mi_0, age);
// 打印height
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_848c45_mi_1, (*height));
// 打印weight
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_848c45_mi_2, weight);
} 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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int age = 10;
static int height = 178; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}

默认定义一个局部变量都为auto自动变量(离开大括号作用域自动销毁),block内部的代码块不确定什么时候执行,固然不能通过指针捕获。

static修饰变量在数据段,全局有效,因此可以通过内存地址捕获。

全局变量本身在整个程序全局都可以访问,生命周期整个程序。所以无需捕获,在block执行时直接访问即可。

Block的类型

block有3中类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

为了更好的研究block,先将Xcode的Automatic Reference Counting关闭。

没有访问auto变量为__NSGlobalBlock__类型

访问了auto变量为__NSStackBlock__ 类型

__NSStackBlock__ 类型block进行copy操作,会将栈中空间的block拷贝到堆中引用计数器+1且类型__NSMallocBlock__

注意必须是对__NSStackBlock__ 类型block进行copy操作才会拷贝到堆中。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool { int age = 12;
void(^block)(void) = [^{
NSLog(@"%d", age);
} copy]; // 释放block
[block release];
}
return 0;
}

block的copy

对象类型的auto变量

#import <Foundation/Foundation.h>
#import "CHPerson.h" typedef void(^CHBlock)(void); int main(int argc, const char * argv[]) {
@autoreleasepool { CHPerson *person = [[CHPerson alloc] init]; // 在ARC下 block被q强引用指针引用被拷贝的堆中
CHBlock block = ^{
NSLog(@"----%@", person);
}; block();
NSLog(@"---------------------");
}
return 0;
}

clang编译器生成对应的c++代码

#pragma clang assume_nonnull end

typedef void(*CHBlock)(void);

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
CHPerson *person; // block内部捕获CHPerson
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CHPerson *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; // block代码块函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
CHPerson *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_b0818f_mi_0, person);
}
// __NSStackBlock__类型block赋值到堆中执行的copy操作
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
} // block销毁会对内部引用的对象类型进行release
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
} static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
// __NSStackBlock__类型block赋值到堆中执行的copy操作 函数指针
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
// block销毁会对内部引用的对象类型进行release 函数指针
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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // 创建Person对象
CHPerson *person = ((CHPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((CHPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CHPerson"), sel_registerName("alloc")), sel_registerName("init")); CHBlock block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344)); // 执行block
block->FuncPtr(block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_b0818f_mi_1);
}
return 0;
}

__bock修饰符

默认block内部无法修改auto变量的值

#import <Foundation/Foundation.h>
#import "CHPerson.h" typedef void(^CHBlock)(void); int height = 170; int main(int argc, const char * argv[]) {
@autoreleasepool { // __block修饰的auto变量
int age = 10;
static int weight = 120;
CHPerson *person = [[CHPerson alloc] init];
person.age = 12; NSMutableArray *array = [NSMutableArray array]; CHBlock block = ^{
[array addObject:@"123"];
[array addObject:@"jake"];
person.age = 22; // 下列写法报错
// age = 20;
// person = nil;
// weight = 125;
// array = nil;
}; block();
NSLog(@"-------------");
}
return 0;
}

auto变量在block内部捕获只是值传递,内部无法修改auto变量的值。而全局变量与static修饰的变量是可以修改的,那是因为他们作用域是整个程序并且static修饰变量在block捕获的是变量地址,因此可以block内部修改。

虽然可以通过定义static或者全局变量来实现在block内部修改变量,但是全局变量和static会修改变量的作用域,因此开发中一般使用__block

__block不能修饰全局变量、静态变量(static)

#import <Foundation/Foundation.h>
#import "CHPerson.h" typedef void(^CHBlock)(void); int main(int argc, const char * argv[]) {
@autoreleasepool { // __block修饰的auto变量
__block int age = 10;
__block CHPerson *person = [[CHPerson alloc] init];
__block NSMutableArray *array = [NSMutableArray array]; CHBlock block = ^{ // 下列写法报错
age = 20;
person = nil;
array = nil;
}; block();
NSLog(@"-------------");
}
return 0;
}

clang编译器生成对应的c++代码

#pragma clang assume_nonnull end

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);
} typedef void(*CHBlock)(void); // 编译器将__block int age 变量包装成一个对象
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
}; // 编译器将__block CHPerson *person 变量包装成一个对象
struct __Block_byref_person_1 {
void *__isa;
__Block_byref_person_1 *__forwarding; // 指向自己
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
CHPerson *person;
}; // 编译器将__block NSMutableArray *array 变量包装成一个对象
struct __Block_byref_array_2 {
void *__isa;
__Block_byref_array_2 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSMutableArray *array;
}; struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc; // 内部指向__block变量包装的对象
__Block_byref_age_0 *age; // by ref
__Block_byref_person_1 *person; // by ref
__Block_byref_array_2 *array; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, __Block_byref_person_1 *_person, __Block_byref_array_2 *_array, int flags=0) : age(_age->__forwarding), person(_person->__forwarding), array(_array->__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_age_0 *age = __cself->age; // bound by ref
__Block_byref_person_1 *person = __cself->person; // bound by ref
__Block_byref_array_2 *array = __cself->array; // bound by ref (age->__forwarding->age) = 20;
(person->__forwarding->person) = __null;
(array->__forwarding->array) = __null;
} static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign(&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign(&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->array, 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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // 编译器将__block int age 变量包装成一个对象
__Block_byref_age_0 age = {
0,
(__Block_byref_age_0 *)&age,
0,
sizeof(__Block_byref_age_0),
10 // age
}; // 编译器将__block CHPerson *person 变量包装成一个对象
__Block_byref_person_1 person = {
0,
(__Block_byref_person_1 *)&person,
33554432,
sizeof(__Block_byref_person_1),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((CHPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((CHPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CHPerson"), sel_registerName("alloc")), sel_registerName("init")) }; // 编译器将__block NSMutableArray *array 变量包装成一个对象
__Block_byref_array_2 array = {
0,
(__Block_byref_array_2 *)&array,
33554432,
sizeof(__Block_byref_array_2),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))
}; CHBlock block = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_age_0 *)&age,
(__Block_byref_person_1 *)&person,
(__Block_byref_array_2 *)&array,
570425344
)); block->FuncPtr(block); NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_1js09xx15fz7j7qyrylx4xm80000gn_T_main_b553f0_mi_0);
}
return 0;
}

__block之所以可以修改外部的auto变量,是因为编译器将__block变量包装成一个对象。

__block的内存管理


ARC下解决循环引用

MRC下解决循环引用

iOS开发系列-Block本质篇的更多相关文章

  1. iOS开发系列-Block

    概述 在iOS 4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调.这不免让我们想到在C函数中,我们可以定义一个指向函数的指针并且调用. #im ...

  2. iOS开发系列

    因为最近面试了一些人,校招.初中级.高级.架构师,各种级别的,发现大家水平差异很大,有的高级的工程师很多问题都回答不上来,所以想梳理下iOS的知识点,写成一个系列,如果时间允许的话,会录制成视频放到网 ...

  3. iOS开发系列--Swift进阶

    概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...

  4. iOS开发系列--让你的应用“动”起来

    --iOS核心动画 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌.在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建 ...

  5. iOS开发系列--让你的应用“动”起来

    --iOS核心动画 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌.在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建 ...

  6. IOS开发系列 --- 核心动画

    原始地址:http://www.cnblogs.com/kenshincui/p/3972100.html 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥i ...

  7. iOS开发系列--让你的应用“动”起来【转载】

    概览 原文链接:http://www.cnblogs.com/kenshincui/p/3972100.html 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥i ...

  8. iOS开发系列--数据存取

    概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列-Objective-C之Foundation框架的文章中提到归档.plist文件存储, ...

  9. iOS开发系列--并行开发其实很容易

    --多线程开发 概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算.可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的, ...

随机推荐

  1. JavaWeb学习篇之----Session&&Cookie

    今天继续来看看JavaWeb的相关知识,这篇文章主要来讲一下Session和Cookie的相关知识,首先我们来看一下Cookie的相关知识: 一.Cookie 简介: Cookie是客户端技术,服务器 ...

  2. 40 VSCode下.json文件的编写——(1) linux/g++ (2).json中参数与预定义变量的意义解释

    0 引言 转入linux/VSCode编程之后,迫切了解到有必有较为系统地学习一下VSCode中相关配置文件的写法.下面将分为 linux/g++编译指令..json文件关键词/替换变量的意义.编译链 ...

  3. spark在不同环境下的搭建|安装|local|standalone|yarn|HA|

    spark的集群环境安装搭建 1.spark local模式运行环境搭建 常用于本地开发测试,本地还分为local单线程和local-cluster多线程; 该模式被称为Local[N]模式,是用单机 ...

  4. JVM内核-原理、诊断与优化学习笔记(十一):JVM字节码执行

    文章目录 javap javap 举个

  5. mac的终端运行ifconfig

    lo0:loopback回环地址一般是127.0.0.0,loopback指本地环回接口(或地址),亦称回送地址().此类接口是应用最为广泛的一种虚接口,几乎在每台路由器上都会使用. gif0: so ...

  6. 剑指offer——26反转链表

    题目描述 输入一个链表,反转链表后,输出新链表的表头.   题解: 每次只反转一个节点,先记住cur->next, 然后pre->cur,即可;   class Solution { pu ...

  7. 分享一套高级Java笔试题(实拍高清图)

    分享一套高级Java笔试题 微信群里群友分享的 刚好他在笔试 有些问题不会发到群里求助 如果你最近正好在面试 需要参考需要提升 这套试题或许对你有用 下面是部分分享原图 下面是微信群中群友的热议 非常 ...

  8. Python匹马行天下之面向对象

    概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发“更快更好更强...” 面向过程编程最易被初学 ...

  9. python接口自动化(get请求)

    python接口自动化(get请求) get请求的目的:查询资源 一.导包 二.请求的URL 三.请求的参数 四.获取请求的URL 五.获取响应的状态码 六.获取响应的本文信息 #导包 import ...

  10. C++中的指针(*)、引用(&)、const详解(一、定义变量)

    一.前言 本人作为一个工作了5年的程序员,程序生涯最初是从c/c++开始的,但是始终不能很熟悉的理解c语言中的指针和c++中的引用,归其原因,一部分自己没有静下心来思考,一部分原因是其自身的复杂性. ...