guard & defer

guard 是一个要求表达式的值为 true 从而继续执行的条件语句。如果表达式为 false,则会执行必须提供的 else 分支。

 func sayHello(numberOfTimes: Int) -> () {
        guard numberOfTimes > 0 else {
            return
        }
        
        for _ in 1...numberOfTimes {
            print("Hello!")
        }
        
    }

guard 语句中的 else 分支必须退出当前的区域,通过使用 return 来退出函数,continue 或者 break来退出循环,或者使用像 fatalError(_:file:line:) 这种返回 Never 的函数。

使用 guard 来避免过多的缩进和错误

比如,我们要实现一个 readBedtimeStory() 函数:

enum StoryError:Error {
        case missing
        case illegible
        case tooScary
    }
    
    //未使用guard的函数
    func readBedtimeStory() throws {
        if let url = Bundle.main.url(forResource: "books", withExtension: "txt") {
            
            if let data = try? Data(contentsOf: url), let story = String(data: data, encoding: .utf8) {
                if story.contains("") {
                    throw StoryError.tooScary
                } else {
                    print("Once upon a time...\(story)")
                }
            } else {
                throw StoryError.illegible
            }
            
        } else {
            throw StoryError.missing
        }
    }

要读一个睡前故事,我们需要能找到一本书,这本故事书必须要是可读的,并且故事不能太吓人(请不要让怪物出现在书的结尾,谢谢你!)。

请注意 throw 语句离检查本身有多远。你需要读完整个方法来找到如果没有 book.txt 会发生什么。

像一本好书一样,代码应该讲述一个故事:有着易懂的情节,清晰的开端、发展和结尾。(请尝试不要写太多「后现代」风格的代码。)

使用 guard 语句组织代码可以让代码读起来更加的线性:

//使用guard
    func readBedTimeStory2() throws {
        
        guard let url = Bundle.main.url(forResource: "books", withExtension: "txt") else {
            throw StoryError.missing
        }
        
        guard let data = try? Data(contentsOf: url), let story = String(data: data, encoding: .utf8) else {
            throw StoryError.illegible
        }
        
        if story.contains("") {
            throw StoryError.tooScary
        }
        
        print("Once upon a time...\(story)")
        
    }

这样就好多了! 每一个错误都在相应的检查之后立刻被抛出,所以我们可以按照左手边的代码顺序来梳理工作流的顺序。

defer

defer 关键字为此提供了安全又简单的处理方式:声明一个 block,当前代码执行的闭包退出时会执行该 block。

看看下面这个包装了系统调用 gethostname(2) 的函数,用来返回当前系统的主机名称:

import Darwin

func currenthostName() -> String {
        let capacity = Int(NI_MAXHOST)
        let buffer = UnsafeMutablePointer.allocate(capacity: capacity)
        
        guard gethostname(buffer, capacity) == 0 else {
            buffer.deallocate()
            return "localhost"
        }
        
        let hostname = String(cString: buffer)
        buffer.deallocate()
        
        return hostname
        
        
    }

这里有一个在最开始就创建的 UnsafeMutablePointer 用于存储目标数据,但是我既要在错误发生后销毁它,又要在正常流程下不再使用它时对其进行销毁。

这种设计很容易导致错误,而且不停地在做重复工作。

通过使用 defer 语句,我们可以排除潜在的错误并且简化代码:

func currentHostName2() -> String {
        let capacity = Int(NI_MAXHOST)
        let buffer = UnsafeMutablePointer.allocate(capacity: capacity)
        defer {
            buffer.deallocate()
        }
        
        guard gethostname(buffer, capacity) == 0 else {
            return "localhost"
        }
        
        return String(cString: buffer)
        
    }

尽管 defer 紧接着出现在 allocate(capacity:) 调用之后,但它要等到当前区域结束时才会被执行。多亏了 defer,buffer 才能无论在哪个点退出函数都可以被释放。

考虑在任何需要配对调用的 API 上都使用 defer,比如 allocate(capacity:) / deallocate()、wait() / signal() 和 open() / close()。
如果在 defer 语句中引用了一个变量,执行时会用到变量最终的值。换句话说:defer 代码块不会捕获变量当前的值。

func flipFlop() -> () {
        var position = "It's pronounced /haha/"
        defer {
            print(position)
        }
        
        position = "It's pronounced /hehe/"
        defer {
            print(position)
        }
        
    }

输出:
It's pronounced /hehe/
It's pronounced /hehe/

你可能感兴趣的:(guard & defer)