巧用Swift Never

Ref: Never-Mattt

本文是在阅读Mattt作者后写的。

Never一开始是用来取代Swift3之前的@noreturn关键字的。它本身只是一个enum,如果作为一个函数的返回值就代表这个函数永远不会返回,适用于一个函数需要不断循环做一些事情,比如iOS的事件循环等。

/// The return type of functions that do not return normally, that is, a type
/// with no values.
///
/// Use `Never` as the return type when declaring a closure, function, or
/// method that unconditionally throws an error, traps, or otherwise does
/// not terminate.
///
///     func crashAndBurn() -> Never {
///         fatalError("Something very, very bad happened")
///     }
public enum Never {
}

switch和Never

我们一般会把它作为函数的返回值(貌似也不怎么用到Never),其实也有一些其它用法。巧妙地把Never用于传参中会有意想不到的效果。

Swift没有一个标准的Result类型,但大致和下面的描述差不多:

enum Result {
    case success(Value)
    case failure(Error)
}

Result类型包含了valueserrors,一般用于异步的网络请求中,例如:

func fetch(_ request: Request, completion: (Result) -> Void) {
    // ...
}

fetch(request) { result in
    switch result {
    case .success(let value):
        print("Success: \(value)")
    case .failure(let error):
        print("Failure: \(error)")
    }
}

现在,我们考虑这样一种情况,一个函数在completion的handler里总是返回成功(success)的结果,也就没必要写failure的case分支了,但是默认情况下,我们是需要写failure分支的。现在我们利用Never可以做到不用写它了:

func alwaysSucceeds(_ completion: (Result) -> Void) {
    completion(.success("yes!"))
}

alwaysSucceeds { (result) in
    switch result {
    case .success(let string):
        print(string)
    }
}

要让Nerve支持以上写法,需要实现两个协议:Swift.Error和Comparable。

1.遵从Error协议,使得Nerve能作为Result类型的模板参数。

extension Never: Error {
    // 刚好Error是一个空协议,不用再多写什么
}

2.遵从Comparable协议,使得在对Result对象做switch的时候可比较以跳转正确的case分支。

extension Never: Comparable {
    public static func < (lhs: Never, rhs: Never) -> Bool {
        switch (lhs, rhs) {}
    }

    public static func > (lhs: Never, rhs: Never) -> Bool {
        switch (lhs, rhs) {}
    }

    public static func == (lhs: Never, rhs: Never) -> Bool {
        switch (lhs, rhs) {}
    }
}

Comparable协议只需要实现以上3个,其它函数由编译器自动补全。

完整代码如下:

extension Never: Error {

}

extension Never: Comparable {
    public static func < (lhs: Never, rhs: Never) -> Bool {
        switch (lhs, rhs) {}
    }

    public static func > (lhs: Never, rhs: Never) -> Bool {
        switch (lhs, rhs) {}
    }

    public static func == (lhs: Never, rhs: Never) -> Bool {
        switch (lhs, rhs) {}
    }
}

enum Result {
    case success(Value)
    case failure(Error)
}

func alwaysSucceeds(_ completion: (Result) -> Void) {
    completion(.success("yes!"))
}

alwaysSucceeds { (result) in
    switch result {
    case .success(let string):
        print(string)
    }
}

但是个人觉得这种方式不如用if-case-let写法来得直接:

if case .success(let value) = result {
    // Do something
}

但从语义上来说switch-Nerve在阅读上更加友好。

??运算符和Nerve

??运算符又叫空合并运算符。需要两个操作数,当lhs为空的时候就使用rhs。

强制解包(unwrap)运算符(!)是一种危险的操作,很容易引起程序崩溃。

let array: [Int] = []
let firstIem = array.first!

array为空,强制解包就会引起崩溃。

所以为了避免没必要的崩溃,我们一般会用if-let or guard-let去解包。

let array: [Int] = []
guard let firstItem = array.first else {
    fatalError("array cannot be empty")
}

如果你知道为空的时候需要抛出异常,或者就是需要结束程序的时候,这样写就有点不太优雅。我们可以利用??和Never:

func ?? (lhs: T?, rhs: @autoclosure () -> Never) -> T {
    switch lhs {
    case let value?:
        return value
    case nil:
        rhs()
    }
}

let firstItem = array.first ?? fatalError("array cannot be empty")
// Fatal error: array cannot be empty

当然,如果以后Never作为swift的基础底层类型的时候,可能会自动支持作为??的rhs,就不需要我们去实现func ??(lhs: T?, rhs: @autoclosure () -> Never) -> T

原文还有两个关于Never在未来使用的展望:throw表达式和throws标记。不过我看来没啥必要,有兴趣的朋友可以去原文看看。

你可能感兴趣的:(巧用Swift Never)