函数类型
之前在代码的书写过程中,我们已经或多或少的接触过函数,函数本身也有自己的类型,它由形式参数类型,返回值类型组成。
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
var a = addTwoInts
a(10, 20)
addTwoInts就代表了我们当前函数的类型。
那么如果在当前的项目代码当中,出现了相同的函数名称,不过它们的函数参数不同,这个时候我们应该如何去区分哪?
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func addTwoInts(_ a: Double, _ b: Double) -> Double {
return a + b
}
var a = addTwoInts
image.png
这里两个addTwoInts函数名相同,参数不同,它们是两个不同的函数,在这个过程中,编译器并不能识别addTwoInts到底是哪个函数类型。
所以如果我们想要调用对应的函数方法,需要在变量中指定要赋值的类型。
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func addTwoInts(_ a: Double, _ b: Double) -> Double {
return a + b
}
var a: (Double, Double) -> Double = addTwoInts
a(10, 20)
var b = a
b(20, 30)
QQ20220126-105152@2x.png
image.png
这里的
0x301
其实存储的是我们的kind
, 所以说我们当前的函数在我们的swift里面也是一个引用类型。下面我们通过源码来了解一下函数。
在
metadata.h
文件中搜索Function,我们找到了TargetFunctionTypeMetadata
,这个就是我们当前的函数类型的源数据。image.png
我们看到这里有一个
TargetFunctionTypeFlags
,那么这个类型里究竟都有什么东西哪,我们来看一下。image.png
了解了当前函数的内部数据结构,我们就可以做一下对应的还原。
struct TargetFunctionTypeMetadata {
var kind: Int
var flags: Int
var arguments: ArgumentsBuffer<Any.Type>
func numberArguments() -> Int {
return self.flags & 0x0000FFFF
}
}
struct ArgumentsBuffer<Element> {
var element: Element
mutating func buffer(n: Int) -> UnsafeBufferPointer<Element> {
return withUnsafePointer(to: &self) {
let ptr = $0.withMemoryRebound(to: Element.self, capacity: 1) { start in
return start
}
return UnsafeBufferPointer(start: ptr, count: n)
}
}
mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self) {
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
}
}
}
我们可以通过这个数据结构,可以动态获取函数的相关信息。
func funcInfo() {
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
let a: (Int, Int) -> Int = add
let a1 = a(10, 20)
print(a1)
func add(_ a: Double, _ b: Double) -> Double {
return a + b
}
let b: (Double, Double) -> Double = add
let b1 = b(10, 20)
print(b1)
print("end")
let a1Type = type(of: a)
getFunctionInfo(a1Type as Any.Type)
let b1Type = type(of: b)
getFunctionInfo(b1Type as Any.Type)
}
func getFunctionInfo(_ type: Any.Type) {
let funcType = unsafeBitCast(type, to: UnsafeMutablePointer<TargetFunctionTypeMetadata>.self)
let number = funcType.pointee.numberArguments()
print("该函数有\(number)个参数,返回值类型是")
for i in 0..<number {
let argument = funcType.pointee.arguments.index(of: i).pointee
let value = customCast(type: argument)
print(value)
}
}
protocol BrigeProtocol {}
extension BrigeProtocol {
static func get(from pointer: UnsafeRawPointer) -> Any {
pointer.assumingMemoryBound(to: Self.self).pointee
}
}
struct BrigeProtocolMetadata {
let type: Any.Type
let witness: Int
}
func customCast(type: Any.Type) -> BrigeProtocol.Type {
let container = BrigeProtocolMetadata(type: type, witness: 0)
let protocolType = BrigeProtocol.Type.self
let cast = unsafeBitCast(container, to: protocolType)
return cast
}
funcInfo()
lldb输出打印结果:
30
30.0
end
该函数有2个参数,返回值类型是
Int
Int
该函数有2个参数,返回值类型是
Double
Double
什么是闭包
闭包是一个捕获了上下文的常量或者是变量的函数。
我们可以看一个官方给的案例:
///返回值为函数() -> Int,这个函数() -> Int的返回值是Int
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
这里incrementer
作为一个闭包,显然他是一个函数,其次为了保证其执行,要捕获外部变量runningTotal 到内部,所以闭包的关键就有捕获外部变量或常量
和函数
。
闭包表达式
{ (param type) -> (return type) in
///do somethings
}
闭包在语法上有这样的标准结构: {(参数列表) -> 返回值 in 闭包体}
- 作用域(也就是大括号)
- 参数和返回值
- 函数体
in
之后的代码
- 闭包即可以当做变量,也可以当做参数传递,这里我们来看一下下面的例子熟悉一下:
var closure: (Int) -> Int = { (age: Int) in
return age
}
- 同样的我们也可以把我们的闭包声明成一个可选类型:
///错误的写法
var closure: (Int) -> Int?
closure = nil
///正确的写法
var closure: ((Int) -> Int)?
closure = nil
- 还可以通过
let
关键字将闭包声明为一个常量(也就意味着一旦赋值之后就不能改变了)
let closure: (Int) -> Int
closure = { (age: Int) in
return age
}
4.同时也可以作为函数的参数
func test(param: () -> Int) {
print(param())
}
var age = 10
test { () -> Int in
age += 1
return age
}
尾随闭包
当我们把闭包表达式作为函数的最后一个参数
,如果当前的闭包表达式很长,我们可以通过尾随闭包的书写方式来提高代码的可读性
。
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool {
return by(a, b, c)
}
///原结构
test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
return (item1 + item2 < item3)
})
///后置闭包结构
test(10, 20, 30) {
}
使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处有很多:
- 利用上下文推断参数和返回值类型
var array = [1, 2, 3]
array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
- 单表达式可以隐式返回,既省略
return
关键字
var array = [1, 2, 3]
array.sort(by: {(item1, item2) in return item1 < item2 })
array.sort{(item1, item2) in item1 < item2 }
- 参数名称的简写(比如我们的$0)
var array = [1, 2, 3]
array.sort{ return $0 < $1 }
array.sort{ $0 < $1 }
- 尾随闭包表达式
var array = [1, 2, 3]
array.sort(by: <)
捕获值
在讲闭包捕获值的时候,我们先来回顾一下 OC中Block 捕获值的情形。
- (void)testBlock {
NSInteger i = 1;
void(^block) (void) = ^{
NSLog(@"block %ld:", i);
};
i += 1;
NSLog(@"before block %ld:", i);///2
block();///1
NSLog(@"after block %ld:", i);///2
}
那么如果我们想要外部的修改能够影响当前 block 内部捕获的值,我们只需要对当前的 i 添加 __block
修饰符。
- (void)testBlock {
__block NSInteger i = 1;
void(^block) (void) = ^{
NSLog(@"block %ld:", i);
};
i += 1;
NSLog(@"before block %ld:", i);///2
block();///2
NSLog(@"after block %ld:", i);///2
}
那么我们把上面的代码翻译成swift代码来看一下它会发生什么样的变化。
var i = 1
let closure = {
print("closure \(i)")
}
i += 1
print("before closure \(i)")
closure()
print("after closure \(i)")
那么我们编译成SIL文件来看一下闭包捕获外部变量的过程。
可以看到是通过
project_box
来存放 i的查看官方文档
var i = 10
var closure = {
print("closure \(i)")
}
image.png
通过上图很明显看出,前8个字节存储的是
metadata
,在swift当中,其实并没有堆栈全局Block这种区别。
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
print(makeInc())
print(makeInc())
print(makeInc())
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
print(makeIncrementer()())
print(makeIncrementer()())
print(makeIncrementer()())
我们看到,如果我们调用内部的闭包函数,返回的值是累加的,它返回的其实是runningTotal,而下面的代码,我们其实是调用的makeIncrementer,返回的是incrementer。
OC Block 和 Swift闭包相互调用
我们在OC中定义的Block,在Swift中是如何调用的那?我们来看一下。
typedef void(^ResultBlock)(NSError *error);
@interface ZGTest : NSObject
+ (void)testBlockCall:(ResultBlock)block;
@end
@implementation ZGTest
+ (void)testBlockCall:(ResultBlock)block {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:400 userInfo:nil];
block(error);
}
@end
在 Swift 中我们可以这么使用
ZGTest.testBlockCall { error in
let errorcast = error as NSError
print(errorcast)
}
func test(_ block: ResultBlock) {
let error = NSError.init(domain: NSURLErrorDomain, code: 400, userInfo: nil)
block(error)
}
同样的,比如我们在 Swift里这么定义,在OC中也是可以使用的。
class ZGTeacher: NSObject {
@objc static var closure: (() -> ())?
}
在OC中我们可以这么调用
+ (void)test {
ZGTeacher.closure = ^{
NSLog(@"end");
};
}
闭包的本质
- 闭包的核心是在其中使用的局部变量会被额外地复制或引用,使这些变量脱离其作用域后依然有效。
- 每次修改捕获值的时候其实是修改堆区当中的value。
- 当我们每次执行当前函数的时候其实每次都会创建新的内存空间。
为了更好得理解这一过程,我们可以通过结构体来还原闭包的结构,并对它进行内存绑定,来获取闭包捕获的值。
为了更清晰探知它内部的调用,我们将代码编译成IR文件,swift代码如下:
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
swift 还原闭包的结构体
在看具体的内容的时候,我们先来熟悉一下简单的 IR语法
:
- 数组
[<elementnumber> x <elementtype>]
//example
alloca [24 x i8], align 8 24个i8都是0
alloca [4 x i32] === array
- 结构体
%swift.refcounted = type { %swift.type*, i64 }
//表示形式
%T = type {<type list>} //这种和C语言的结构体类似
- 指针类型
<type> *
//example
i64* //64位的整形
-
getelementptr 指令
LLVM中我们获取数组和结构体的成员,通过getelementptr
,语法规则如下:
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
image.png
总结:
- 第一个索引不会改变返回的指针的类型,也就是说ptrval前面的*对应什么类型,返回就是什么类型。
- 第一个索引的偏移量的是由第一个索引的值和第一个ty指定的基本类型共同确定的。
- 后面的索引是在数组或者结构体内进行索引
- 每增加一个索引,就会使得该索引使用的基本类型和返回的指针的类型去掉一层。
还原闭包的数据类型
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
///{ i8*, %swift.refcounted* }
///数据结构:当前闭包的执行地址 + 捕获变量堆空间的地址
struct ClosureData<Box> {
var ptr: UnsafeRawPointer
var object: UnsafePointer<Box>
}
///实例对象的内存地址
struct HeapObject {
var metadata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
struct Box<T> {
var object: HeapObject
var value: T
}
struct NoMeanStruct {
var f: () -> Int
}
var f = NoMeanStruct(f: makeIncrementer())
let ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
ptr.initialize(to: f)
let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int>>.self, capacity: 1) {
$0.pointee
}
print(ctx.ptr)
print(ctx.object)
print("end")
输出和调试如下:
可以看到捕获的变量已经找到,还可以通过Mach-o文件来找对应的函数地址,来证实我们的猜想。 这里我们需要借助
nm -p
命令。
$ nm -p <mach-o path> | grep <函数地址(不带0x)>
注意这里函数地址是不加0x的。
image.png
QQ20220128-161723@2x.png
可以看到通过字符串也可以对应到函数名称。
@convention
@convention
:用于修饰函数类型
-
修饰Swift中的函数类型(调用C函数的时候)
C文件
image.png
image.png
Swift文件
image.png - 调用OC方法时,修饰Swift函数类型
defer
定义:defer
{} 里的代码会在当前代码块返回的时候执行,无论当前代码块是从哪个分支 return 的,即使程序抛出错误,也会执行。
如果多个 defer
语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是先出现的后执行。
defer能做什么?
这里我们看一个简单的例子:
func f() {
defer {
print("First defer")
}
defer {
print("Second defer")
}
defer {
print("End of function")
}
}
f()
lldb打印输出结果,验证了先出现的后执行。
End of function
Second defer
First defer
下面案例这里有时候如果当前方法中多次出现 closeFile
,那么我们就可以使用 defer
func append(string: String, terminator: String = "\n", toFileAt url: URL) throws {
let data = (string + terminator).data(using: .utf8)!
let fileHandle = try FileHandle(forUpdating: url)
defer {
fileHandle.closeFile()
}
guard FileManager.default.fileExists(atPath: url.path) else {
try data.write(to: url)
return
}
fileHandle.seekToEndOfFile()
fileHandle.write(data)
}
let url = URL(fileURLWithPath: NSHomeDirectory() + "/Desktop/swift.txt")
try append(string: "iOS面试突击", toFileAt: url)
try append(string: "swift", toFileAt: url)
我们在使用指针的时候
let count = 2
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(repeating: 0, count: count)
defer {
pointer.deinitialize(count: count)
pointer.deallocate()
}
比如我们在进行网络请求的时候,可能有不同的分支进行回调函数的执行
func netRquest(completion: () -> Void) {
defer {
self.isLoading = false
completion()
}
guard error == nil else { return }
}
defer
本质其实是为了管理我们的代码块。
网友评论