8、swift闭包底层探索(下)

140 阅读3分钟

oc的block和Swift闭包相互调用

swift调用oc的block:

//oc
typedef void(^ResultBlock)(NSError *error);

@interface LGTest : NSObject

+ (void)testBlockCall:(ResultBlock)block;

@end

//swift

LGTest.testBlockCall{ error in
    let errorcast = error as NSError
    print(errorcast)
}

func test(_ block: ResultBlock){
    let error = NSError.init(domain: NSURLErrorDomain, code: 400, userInf
    block(error)
}

oc调用swift的闭包:

//swift
//注意类要继承自 NSobject,闭包前加 @objc 标识
class LGTeacher: NSObject{
    @objc static var closure: (() -> ())?
}
+ (void)test{
    LGTeacher.test{}
    LGTeacher.closure = ^{
        NSLog(@"end");
    };
}
LGTest.test()

LGTeacher.closure = ^{

}
LGTeacher.closure!()

@convention

作用:用于修饰函数类型

  • 修饰Swift中的函数类型(调用c函数的时候)
  • 调用OC方法时,修饰swift函数类型

例子:

在c文件中定义一个c方法

//.h
int TesctCFUnction(int (callBack)(int a, int b));

//.cpp
int TesctCFUnction(int (callBack)(int a, int b)){
    return  callBack(10, 20);
}

在swift中调用:

let result = TesctCFUnction { (a: Int32, b: Int32) -> Int32 in
    return a + b
}

print(result) //30

但是如果这么写,就会报错:

let closure = {(a: Int32, b: Int32) -> Int32  in
    return a + b
}

let result = TesctCFUnction(closure);

Snipaste_2022-06-23_11-13-58.png

所以这个时候,就需要用上@convention

Snipaste_2022-06-23_11-15-58.png

defer

定义:

  • defer {} 里的代码会在当前代码块返回的时候执行,无论当前代码块是从哪个分支return 的,即使程序抛出错误,也会执行。

  • 如果多个 defer 语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是先出现的后执行。

func f() {
    defer { print("First defer") }
    defer { print("Second defer") }
    print("End of function")
}
f()

// 打印结果
// End of function
// Second defer
// First defer

闭包捕获引用类型

来个例子

class LGTeacher{
    var age = 10
}

func test(){
    var t = LGTeacher()
    let clousre = {
        t.age += 10
    }
    clousre()
}

编译成ll代码来看看

Snipaste_2022-06-23_11-45-48.png

结论: 闭包捕获引用类型的时候,不需要先将对象捕获到堆区,因为对象已经在堆区了,所以它只需要对对象的引用地址进行传递就可以了。

闭包捕获多个值

来个例子:

func makeIncrementer(_ amount: Int) -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

Snipaste_2022-06-23_14-59-29.png

我们把这个结构体用swift还原一下:

//数据结构 : 闭包的执行地址  + 捕获变量堆空间的地址
struct ClosureData<T>{
    var ptr: UnsafeRawPointer
    var capatureValue: UnsafePointer<T>
}

struct TowParamsClosureData<T1, T2>{
    var object: HeapObject
    var value1: UnsafePointer<T1> //第一个变量
    var value2: T2 //第二个变量
}

struct HeapObject{
    var metadata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}

跑个demo验证一下:

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

var f = NoMeanStruct(f: makeIncrementer(20))

let ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
ptr.initialize(to: f)

let ctx = ptr.withMemoryRebound(to: ClosureData<TowParamsClosureData<Int, Int>>.self, capacity: 1){
    $0.pointee
}

print(ctx.ptr)  //0x00000001000052f0
print(ctx.capatureValue.pointee.value1)   //0x000000010812aa30
print(ctx.capatureValue.pointee.value2)   //20

print("end")

逃逸闭包

定义: 当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping 来明确闭包是允许逃逸的。

1、使用逃逸闭包的第一种情况:闭包作为值来存储

class CTTeacher{
    
    var completionHandle: ((Int) -> Void)?

    func makeIncrementer(_ amout: Int, handler : @escaping (Int) -> Void) {
        var runningTool = 10
        runningTool += amout

        self.completionHandle = handler
    }

    func dosomething(){
        self.makeIncrementer(10) {
            print($0)
        }
    }
}

var t = CTTeacher()
t.dosomething()
t.completionHandle?(20)

Snipaste_2022-06-24_11-42-28.png

2、使用逃逸闭包的第二种情况:异步执行闭包

class CTTeacher{
    
    var completionHandle: ((Int) -> Void)?

    func makeIncrementer(_ amout: Int, handler : @escaping (Int) -> Void) {
        var runningTool = 10
        runningTool += amout

        DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
            handler(runningTool)
        }
    }

    func dosomething(){
        self.makeIncrementer(10) {
            print($0)
        }
    }
}

逃逸闭包:

  • 作为函数的参数传递
  • 当前闭包在函数内部异步执行或者被存储
  • 函数结束,闭包被调用,生命周期结束

非逃逸闭包:

  • 不会产生循环引用,函数作用域内释放。
  • 非逃逸闭包编译器会做更多的性能优化。
  • 上下文的内存保存在栈上,而不是堆上。

一般都使用非逃逸闭包,万不得已才使用逃逸闭包。

自动闭包

是一种用来把实际参数传递给函数表达式打包的闭包,不接受任何实际参数,当其调用时,返回内部表达式的值。

好处:用普通表达式代替闭包的写法,语法糖的一种

来个例子:

func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){
    if condition {
        print("lg_debug:\(message())")
    }
}

func dosomthing() -> String{
    //耗时的操作
    return "Application Error Occured"
}

//String, () -> String
// {} -> String
debugOutPrint(true, dosomthing) //相当于 {return dosomthing()}
debugOutPrint(true, dosomthing())

Swift多线程

1、实现多线程的手段

//1、Operation
let operation = BlockOperation{
    print("operation任务")
}

operation.start()  //开始任务
operation.cancel() //取消任务

//2、GCD
let workItem = DispatchWorkItem{
    print("gcd任务")
}

workItem.perform() //开始任务
workItem.cancel()  //取消任务

GCD轮流执行:

let workItem1 = DispatchWorkItem{
    print("workItem1")
}
let workItem2 = DispatchWorkItem{
    print("workItem2")
}
let workItem3 = DispatchWorkItem{
    print("workItem3")
}

workItem1.notify(queue: .global(), execute: workItem2)
workItem2.notify(queue: .global(), execute: workItem3)
DispatchQueue.global().async(execute: workItem1)
/*
 workItem1
 workItem2
 workItem3
 */