跳过正文
  1. Posts/

@autoclosure && @escape

·3 分钟·
目录

我们知道在 swift 中,闭包(closure)是一等公民,因此可以被当作参数传递,在学习 swift 的过程中经常会看到某些关键字修饰该闭包,@autoclosure@escape 就是其中比较常见的两种关键字。

@escape 和 @nonescape
#

当一个闭包被当作参数传递给一个函数,但是当该函数内容执行完毕返回之后,该闭包才会被执行,我们就称该闭包要 escape 某个函数,那 @escape 关键字就是用来表示该闭包是允许在函数返回之后被调用的。

我们用 swift 官方文档的例子来看,如下所示

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

someFunctionWithEscapingClosure(_:) 的参数是一个闭包,函数内部会把传入的闭包存到之前声明的数组里以便之后进行调用,可以看到,在函数参数的声明部分添加了 @escaping 关键字,如果这里不添加的话,就会在编译的时候报错:

error: passing non-escaping parameter 'completionHandler' to function expecting an @escaping closure
    completionHandlers.append(completionHandler)    

针对标记了 @escaping 关键字含义代表你必须在该闭包内部显式的使用 self 关键字,官方文档中又列举了另外一个例子,如下所示:

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}
 
class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}
 
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
 
completionHandlers.first?()
print(instance.x)
// Prints "100”

someFunctionWithEscapingClosure(_:) 是一个可逃逸的闭包,意味着你需要显示的调用 self 关键字, 而 someFunctionWithNonescapingClosure(_:) 是非逃逸的闭包,意味着你可以隐式的调用 self。

@autoclosure
#

例子讲解
#

通过一个🌰来说明 @autoclosure 关键字到底起到什么作用。

考虑下面这个函数 f,其需传入一个参数,类型是 ()-> Bool 的闭包。

func f(predicate: () -> Bool) {
    if predicate() {
        print("It's true")
    }
}

然后通过传入符合此类型的闭包进行调用

f(predicate: {2 > 1})
// "It's true"

但是,如果我们忽略传入闭包的 {} ,编译就会错误。

f(predicate: 2 > 1)
// error: '>' produces 'Bool', not the expected contextual result type '() -> Bool'

如文档中所说,一个 autoclosure (自主闭包?)是这样一种闭包: 当某个表达式被当做参数传递给一个函数的时候会被 wrap 成一个闭包,该闭包没有任何参数,其被调用的时候,返回的是其 wrap 的表达式的值。swift 提供这种语法糖就能够让你省略 {} 而直接写一个表达式。

结合上面的例子来看,当你写个表达式类似 2 > 1 传给函数 f 的时候,该表达式会被自动包裹到一个闭包中,会自动处理为 { 2 > 1 } 而传递给函数 f。

func f(predicate: @autoclosure () -> Bool) {
    if predicate() {
        print("It's true")
    }
}

f(predicate: 2 > 1)
// It's true

⚠️ @autoclosure 并不支持带有输入参数的写法,也就是说只有形如 () -> T 的参数才能简化

Delay Evaluation
#

swift 提供了 ?? 操作符,如下所示:

let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)

如果某 Optional 存在就会返回其值,如果没有就会返回后面的默认值,当我们去看 ?? 的实现的时候能看到如下定义:

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T?) -> T?

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T

看得出来 ?? 是一个二元操作符,optional 指代 ?? 前面的输入,defaultValue 指代 ?? 后面的参数,那我们就会想,我们上面的例子中 fullName 只是一个 String,怎么变成 () -> T 类型的呢? 这个就看前面的 @autoclosure 的威力了,前面讲过了,该关键字把表达式的值封装成闭包并且返回该表达式的值了。 其实传入该方法的第二个参数是 { fullName }

所以可以想到该方法的实现应该如下所示,(当然 fullName 为 String 类型,应该会重载第二个函数实现)

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
    switch optional {
        case .Some(let value):
            return value
        case .None:
            return defaultValue()
        }
}

这里我们还需要注意一点的是,使用 @autoclosure 来修饰的表达式可以实现延迟计算,也就是说直到该闭包被调用之前,闭包里所被包裹的表达式都不会进行取值计算,也就避免了一定的开销,尤其是上面默认值是复杂计算得到的话。

相关文章

关于 iOS10 Notification 的那些事儿

·7 分钟
概览 # 推送通知我们大家都不陌生,可以说几乎每个使用智能手机的人每天都会被不同的通知 打扰 到,正式因为合适的推送是吸引用户注意力的利器,其成为了各 App 吸引用户,将用户带回到 App 本身,提升用户的活跃度的一种必要的方式。

Objective-C中Category的一点东西

·5 分钟
Objective-C中的分类(category)是一种编译时的手段,其允许我们通过给某个已知类添加方法来扩充该类的一种方式。当然这其中是有限制的,就是不能给已知类添加新的实例变量。

理解Objective-C运行时

Objective-C 运行时对于刚刚踏入 Cocoa/Objective 世界的人是很容易忽 略的 Objective-C 语言的特性之一。原因就是尽管 Objective-C 是一门几个小时之内入门的语言,但是投身 Cocoa 的新手们会花费大量时间在 Cocoa 框架中,试图搞清楚他到底是怎么工作的。 我觉得每个开发者都应该对其有深入的了解,明白一些内部的实现细节,而不仅仅只知道代码 [target doMethodWith:var] 会被编译器转换成 objc_msgSend(target,@selector(doMethodWith:),var1); 而已。了解 Objective-C 运行时的原理有助于你对 Objective-C 语言有更深入的理解,清楚你得 App 是怎么运行的。我觉得这对无论是 Mac/iPhone 新手或者老手都会有所帮助。

iOS 远端推送部署详解

·10 分钟
最近几天被iOS的推送部署给搞懵了,现在特地整理下和大家进行分享。 iOS远端推送机制 # APNS,全称为Apple Push Notification service,是苹果通知推送服务中最重要的一环。它是苹果通知推送服务器,为所有iOS设备以及OS X设备提供强大并且可靠的推送通知服务。每个注册通知服务的设备都会和该服务器进行长连接,从而实时获取推送通知。即使当前APP不在运行状态,当通知到达的时候也会有提示发生,最常见的就是短信服务。