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是项目名)
通过结果我们能看到 控制台正确打印了 Swift中调用 OC 的block
同理我们在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是项目名)
在控制台上我们打印出
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: "被调用")
控制台会打印
这个 convention 在一些使用OC底层的系统代码时比较有实际的体验价值。
逃逸闭包
定义
什么是逃逸闭包?
当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调 用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数的函数时,你可以在形式 参数前写 @escaping 来明确闭包是允许逃逸的。
逃逸闭包使用情况
- 作为函数的参数传递
直接这么写,编译器就会提示说 我们的
non-escaping 参数赋值给了 escaping 闭包,需要我们在 clo: 后面加上 @escaping 修饰就好了。
同时通过IR 分析其是被进行捕获,对其引用计数做了操作。
- 当前闭包在函数内部异步执行或者被存储
同样,这么写 编译器也会提示报错,逃逸的闭包里捕获了非逃逸的闭包, 需要我们在 clo: 后面加上 @escaping 修饰就好了。
PS:
- 可选类型默认是逃逸闭包!!!
- 逃逸闭包注意循环引用问题,因为逃逸闭包的生命周期大于 函数的生命周期,函数结束了,但是闭包可能还在堆上等待调用,固持有的变量的生命周期也会延长
非逃逸闭包使用情况
只能在函数作用域内函数执行结束前被调用
总结:
非逃逸闭包比逃逸闭包有以下优点:
- 不会产生循环引用,函数作用域内释放
- 编译器更多性能优化 (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")
其打印结果
在这个例子中我们给函数 Print() 传入了两个不同类型的参数,一个是 函数表达式,一个是 字符串,但是同样能打印出正确的结果,这就是得益于 我们加了 @autoclosure 关键字。
其作用就是 会自动把 String 打包成闭包,传递给需要的参数。
我们通过断点发现,已经在执行Print() 函数,但是 doAction() 中的打印并没执行,一直等到 Print() 函数打印了才先打出doAction() 中的信息,为啥呢?这是因为加了 @autoclosure 关键字,让你能够延迟求值,固直到你调用这个闭包,代码段才会被执行。
优点
-
代码省略 -> 自动闭包 语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包
-
延迟求值 -> 因为你能控制代码的执行时机,所以对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的