Swift 六、闭包(上)

函数&闭包.png

函数类型

之前在代码的书写过程中,我们已经或多或少的接触过函数,函数本身也有自己的类型,它由形式参数类型,返回值类型组成。

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)


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
    func numberArguments() -> Int {
        return self.flags & 0x0000FFFF
    }
}

struct ArgumentsBuffer {
    var element: Element

    mutating func buffer(n: Int) -> UnsafeBufferPointer {
        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 {
        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.self)
    let number = funcType.pointee.numberArguments()
    print("该函数有\(number)个参数,返回值类型是")
    for i in 0.. 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之后的代码
  1. 闭包即可以当做变量,也可以当做参数传递,这里我们来看一下下面的例子熟悉一下:
var closure: (Int) -> Int = { (age: Int) in
    return age
}
  1. 同样的我们也可以把我们的闭包声明成一个可选类型:
///错误的写法
var closure: (Int) -> Int?
closure = nil

///正确的写法
var closure: ((Int) -> Int)?
closure = nil
  1. 还可以通过 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文件来看一下闭包捕获外部变量的过程。

image.png

可以看到是通过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语法

  • 数组
[ x ]
//example
alloca [24 x i8], align 8 24个i8都是0
alloca [4 x i32] === array
  • 结构体
%swift.refcounted = type { %swift.type*, i64 }
//表示形式
%T = type {} //这种和C语言的结构体类似
  • 指针类型
 *
//example
i64* //64位的整形
  • getelementptr 指令
    LLVM中我们获取数组和结构体的成员,通过 getelementptr ,语法规则如下:
 = getelementptr , * {, [inrange]  }*
 = getelementptr inbounds , * {, [inrange]  }*
image.png

总结:

  • 第一个索引不会改变返回的指针的类型,也就是说ptrval前面的*对应什么类型,返回就是什么类型。
  • 第一个索引的偏移量的是由第一个索引的值和第一个ty指定的基本类型共同确定的。
  • 后面的索引是在数组或者结构体内进行索引
  • 每增加一个索引,就会使得该索引使用的基本类型和返回的指针的类型去掉一层。
    还原闭包的数据类型
func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

///{ i8*, %swift.refcounted* }
///数据结构:当前闭包的执行地址 + 捕获变量堆空间的地址
struct ClosureData {
    var ptr: UnsafeRawPointer
    var object: UnsafePointer
}

///实例对象的内存地址
struct HeapObject {
    var metadata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}

struct Box {
    var object: HeapObject
    var value: T
}

struct NoMeanStruct {
    var f: () -> Int
}

var f = NoMeanStruct(f: makeIncrementer())
let ptr = UnsafeMutablePointer.allocate(capacity: 1)
ptr.initialize(to: f)

let ctx = ptr.withMemoryRebound(to: ClosureData>.self, capacity: 1) {
    $0.pointee
}
print(ctx.ptr)
print(ctx.object)
print("end")

输出和调试如下:


可以看到捕获的变量已经找到,还可以通过Mach-o文件来找对应的函数地址,来证实我们的猜想。 这里我们需要借助nm -p命令。

$ nm -p  | grep <函数地址(不带0x)>

注意这里函数地址是不加0x的。


image.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.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本质其实是为了管理我们的代码块。

你可能感兴趣的:(Swift 六、闭包(上))