iOS底层实例解析Swift闭包及OC闭包

Angie ·
更新时间:2024-11-13
· 1570 次阅读

目录

基础

OC-Block

分类

NSMallocBlock

源码探究

循环引用

解决方案

注意点

Swift-Closure

捕获值

基础

Block是⼀个自包含的(捕获了上下⽂的常量或者是变量的)函数代码块,可以在代码中被传递和使用。

全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:

全局函数是一个有名字但不会捕获任何值的闭包

嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包

闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

OC-Block 分类

NSGlobalBlock

位于全局区

在Block内部不使用外部变量,或者只使用静态变量和全局变量

NSMallocBlock

位于堆区

被强持有

在Block内部使用局部变量或OC属性,可以赋值给强引用/copy修饰的变量

NSStackBlock

位于栈区

没有被强持有

在Block内部使用局部变量或OC属性,不能赋值给强引用/copy修饰的变量

如下简单demo code所示

int a = 10; // 局部变量 void(^Global)(void) = ^{ NSLog(@"Global"); }; void(^Malloc)(void) = ^{ NSLog(@"Malloc,%d",a); }; void(^__weak Stack)(void) = ^{ NSLog(@"Stack,%d",a); }; NSLog(@"%@",Global); // <__NSGlobalBlock__: 0x101aa80b0> NSLog(@"%@",Malloc); // <__NSMallocBlock__: 0x600003187900> NSLog(@"%@",Stack); // <__NSStackBlock__: 0x7ff7b12c22f0>

下面重点介绍堆Block。

NSMallocBlock

Block拷贝到堆Block的时机:

手动copy

Block作为返回值

被强引用/copy修饰

系统API包含using Block

所以总结一下堆Block判断依据:

Block内部有没有使用外部变量

使用的变量类型?局部变量/OC属性/全局变量/静态变量

有没有被强引用/copy修饰

源码探究

我们创建一个捕获了局部变量的block

#import <Foundation/Foundation.h> void test() { int a = 10; void(^Malloc)(void) = ^{ NSLog(@"%d",a); }; }

执行clang -rewrite-objc main.m -o main.cpp命令,查看main.cpp文件可以看到Malloc闭包的结构如下。

struct __test_block_impl_0 { struct __block_impl impl; struct __test_block_desc_0* Desc; // 内部存储了变量a int a; /// 初始化函数。包含三个参数 // - Parameters: /// - fp: 函数指针 /// - desc: 描述 /// - _a: flag __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; // 创建Malloc闭包,传入参数如下 // fp: (void *)__test_block_func_0 // desc: &__test_block_desc_0_DATA // _a: 变量a的值(值拷贝) void(*Malloc)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a)); // __test_block_func_0实现如下 static void __test_block_func_0(struct __test_block_impl_0 *__cself) { int a = __cself->a; // bound by copy NSLog(···); }

打开llvm可以看到,该block原本是在栈上,调用了objc_retainBlock方法,而在该方法中实际调用了_Block_copy方法。

在Block.h的源码中可以找到_Block_copy方法,其官方注释是“创建一个基于堆的Block副本,或者简单地添加一个对现有Block的引用。”,从而将这个栈block拷贝到了堆上,下面我们根据该方法的源码来探究一下堆Block的原理。(只截取重点代码)

void *_Block_copy(const void *arg) { return _Block_copy_internal(arg, true); } static void *_Block_copy_internal(const void *arg, const bool wantsOne) { struct Block_layout *aBlock; ··· // 类型强转为Block_layout aBlock = (struct Block_layout *)arg; ··· // Its a stack block. Make a copy. // 分配内存 struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return NULL; memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1 // isa重新标记为Malloc Block result->isa = _NSConcreteMallocBlock; _Block_call_copy_helper(result, aBlock); return result; }

Block底层结构为Block_layout

struct Block_layout { void *isa; // isa指针 volatile int32_t flags; // contains ref count int32_t reserved; // 保留位 void (*invoke)(void *, ...); // call out funtion struct Block_descriptor_1 *descriptor; };

总结:

Block在运行时才会被copy,在堆上开辟内存空间。

循环引用 解决方案

__weak + __strong

思路: 在block里短暂持有self的生命周期。(weak 自动置空)

self.name = @"YK"; __weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(self) strongSelf = weakSelf; strongSelf.callFunc(); };

__block

思路: 值拷贝。(手动置空)

我们有如下代码,生成cpp文件看一下

#import <Foundation/Foundation.h> void test() { __block int a = 10; void(^Malloc)(void) = ^{ a++; NSLog(@"%d",a); }; Malloc(); } // 可以看到传入的第三个参数,是__Block_byref_a_0结构体类型的a变量地址,而不是上面讲过的直接存储int类型 void(*Malloc)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); // __test_block_impl_0结构体中存储的变量也是__Block_byref_a_0类型 struct __test_block_impl_0 { struct __block_impl impl; struct __test_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __test_block_impl_0(void *fp, struct __test_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; } }; // 初始化__Block_byref_a_0如下 __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0, (__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10}; // __Block_byref_a_0结构体 struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; // 指针指向原始值 int __flags; int __size; int a; // 值拷贝存储 };

总结 __block 原理

创建__Block_byref_a_0 结构体

传给block指针地址

block內修改的是与原始值同一片的内存空间

注意点

根据上述分析我们可以得出结论,如果在OC的block中捕获了没有加__block 的外部变量,在编译时就会将变量值传入(值拷贝),如果捕获了加__block 的外部变量,则会获取到变量指针对应的内存空间的地址。代码验证如下

int a = 1; __block int b = 2; void(^Malloc)(void) = ^{ NSLog(@"a,%d",a); NSLog(@"b,%d",b); }; a = 3; b = 4; Malloc(); // 输出结果如下 // a,1 // b,4 Swift-Closure

Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:利用上下文推断参数类型和返回值类型

隐式返回单表达式闭包(单表达式闭包可以省略 return 关键字)

参数名称缩写,可以用0,0,0,1表示

尾随闭包语法:如果函数的最后一个参数是闭包,则闭包可以写在形参小括号的外面。为了增强函数的可读性。

Swift 的闭包是一个引用类型,验证如下。我们知道Swift的引用类型在创建时都会调用swift_allocObject方法

// 未调用swift_allocObject let closure1 = { () -> () in print("closure1") } // 调用swift_allocObject let a = 10 let closure2 = { () -> () in print("closure2 \(a)") } 捕获值

在闭包中如果通过[variable1, variabla2]的形式捕获外部变量,捕获到的变量为let类型,即不可变

在闭包中如果直接捕获外部变量,获取的是指针,也就是说在闭包内修改变量值的话,原始变量也会被改变。

如果捕获的是指针类型(Class),无论是否用[],在闭包内对该变量进行修改,都会影响到原始变量

简单验证如下:

var variable = 10 let closure = { () -> () in variable += 1 print("closure \(variable)") } closure() // closure 11 print(variable) // 11

可见直接获取变量的话,会修改到原始值。

如果改成下面这样会编译报错”可变运算符的左侧不可变”

var variable = 10 let closure = { [variable] () -> () in variable += 1 print("closure \(variable)") } closure() print(variable)

捕获指针类型验证

class YKClass { var name = "old" } let demoS = YKStruct() let demoC = YKClass() let closure1 = { [demoC] () -> () in demoC.name = "new" print("closure1 \(demoC.name)") } closure1() // closure1 new print(demoC.name) // new let closure2 = { () -> () in demoC.name = "new2" print("closure2 \(demoC.name)") } closure2() // closure2 new2 print(demoC.name) // new2

以上就是iOS底层实例解析Swift闭包及OC闭包的详细内容,更多关于iOS底层Swift OC闭包的资料请关注软件开发网其它相关文章!



IOS swift闭包 oc Swift

需要 登录 后方可回复, 如果你还没有账号请 注册新账号