Swift-闭包下

585 阅读4分钟

OC Block 和 Swift 闭包相互调用

我们在 OC 中定义的 Block,在 Swift 中是如何调用的呢?我们可以通过例子来看一下

typedef void(^OCBlock)(NSError *error);

@interface OCTest : NSObject

+ (void)ocBlockCall:(OCBlock)block;

@end

在Swift 中我们这样调用,

OCTest.ocBlockCall { error in
    let err = error as NSError
    print("OC => \(err)")
}

func test(_ block: OCBlock) {
    
    let error = NSError.init(domain: NSURLErrorDomain, code: 404, userInfo: nil)
    
    block(error)
}

test { error in
    
    let err = error as NSError
    print("Swift => \(err)")
}

print("Hello, World!")

PS:Swift 文件中调用OC代码 需要把 OC文件 在XX-Bridging-Header.h 导入(xx是项目名)

image.png

通过结果我们能看到 控制台正确打印了 Swift中调用 OC 的block

image.png

同理我们在Swift中定义Closure, 在 OC 中实现,然后再次调用

Swift 文件中代码:

class Teacher: NSObject {
    @objc static var sClosure: (() -> ())?
}

OCTest.test()

Teacher.sClosure!()

print("Hello, World!")

在 OC 文件中代码:

+ (void)test {
    
    Teacher.sClosure = ^{
        NSLog(@"调用到了Swift Closure");
    };
}

PS: 在OC 文件中,我们调用Swift 代码,需要导入 xx-Swift.h 文件(xx是项目名)

image.png

在控制台上我们打印出

image.png

convention

定义

@convention: 用于修饰函数类型

  • @convention(c) : 表示这个是兼容c的函数指针的闭包

  • @convention(swift) : 表示这个是一个swift的闭包

  • @convention(block) :表示这个是一个兼容oc的block的闭包

实例

例如

class Teacher: NSObject {
    @objc static var sClosure: (() -> ())?

    func closureAction(action:((String)->Void), arg:String){
        action(arg)
    }
}

let closure_c : @convention(c) (_ str: String)->Void = { str in
    print("closure_c: \(str)")
}

let closure_oc : @convention(block) (_ str: String)->Void = { str in
    print("closure_oc: \(str)")
}

let closure_swift : @convention(swift) (_ T: String)->Void = { str in
    print("closure_swift: \(str)")
}

let teacher = Teacher()
teacher.closureAction(action: closure_c, arg: "被调用")
teacher.closureAction(action: closure_oc, arg: "被调用")
teacher.closureAction(action: closure_swift, arg: "被调用")

控制台会打印

image.png

这个 convention 在一些使用OC底层的系统代码时比较有实际的体验价值。

逃逸闭包

定义

什么是逃逸闭包?

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

逃逸闭包使用情况

  • 作为函数的参数传递

image.png 直接这么写,编译器就会提示说 我们的non-escaping 参数赋值给了 escaping 闭包,需要我们在 clo: 后面加上 @escaping 修饰就好了。

同时通过IR 分析其是被进行捕获,对其引用计数做了操作。

image.png

  • 当前闭包在函数内部异步执行或者被存储

image.png

同样,这么写 编译器也会提示报错,逃逸的闭包里捕获了非逃逸的闭包, 需要我们在 clo: 后面加上 @escaping 修饰就好了。

PS:

  • 可选类型默认是逃逸闭包!!!
  • 逃逸闭包注意循环引用问题,因为逃逸闭包的生命周期大于 函数的生命周期,函数结束了,但是闭包可能还在堆上等待调用,固持有的变量的生命周期也会延长

非逃逸闭包使用情况

只能在函数作用域内函数执行结束前被调用

image.png

总结:

非逃逸闭包比逃逸闭包有以下优点:

  • 不会产生循环引用,函数作用域内释放
  • 编译器更多性能优化 (retain, relsase)
  • 上下文的内存保存再栈上,不是堆上

自动闭包

定义

什么是自动闭包?

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

例如

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

func doAction() -> String{
    //耗时的操作
    return "Application Error1"
}

Print(true, doAction())
Print(true, "Application Error2")

其打印结果

image.png

在这个例子中我们给函数 Print() 传入了两个不同类型的参数,一个是 函数表达式,一个是 字符串,但是同样能打印出正确的结果,这就是得益于 我们加了 @autoclosure 关键字。

其作用就是 会自动把 String 打包成闭包,传递给需要的参数。

image.png

我们通过断点发现,已经在执行Print() 函数,但是 doAction() 中的打印并没执行,一直等到 Print() 函数打印了才先打出doAction() 中的信息,为啥呢?这是因为加了 @autoclosure 关键字,让你能够延迟求值,固直到你调用这个闭包,代码段才会被执行。

优点

  • 代码省略 -> 自动闭包 语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包

  • 延迟求值 -> 因为你能控制代码的执行时机,所以对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的