V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iOS 开发实用技术导航
NSHipster 中文版
http://nshipster.cn/
cocos2d 开源 2D 游戏引擎
http://www.cocos2d-iphone.org/
CocoaPods
http://cocoapods.org/
Google Analytics for Mobile 统计解决方案
http://code.google.com/mobile/analytics/
WWDC
https://developer.apple.com/wwdc/
Design Guides and Resources
https://developer.apple.com/design/
Transcripts of WWDC sessions
http://asciiwwdc.com
Cocoa with Love
http://cocoawithlove.com/
Cocoa Dev Central
http://cocoadevcentral.com/
NSHipster
http://nshipster.com/
Style Guides
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Useful Tools and Services
Charles Web Debugging Proxy
Smore
banxi1988
V2EX  ›  iDev

Swift Param Attribute - @autoclosure why?

  •  
  •   banxi1988 ·
    banxi1988 · 2015-06-10 18:10:19 +08:00 · 3808 次点击
    这是一个创建于 3453 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我在基本了解了 Swift 中 assert 方法的使用之后,冒出来的问题是: 为什么要用 @autoclosure
    而不是一个直接的布尔类型.

    为什么要用 @autoclosure?

    第一次看到 @autoclosure 是在 assert 方法中看到的.
    它要达到的目的是类似 C 语言中的 assert 宏.

    那首先来看 C 语言中 assert 宏是什么样的

    C 语言中的 assert

    C 语言中的assert定义在标准库中的assert.h头文件中
    我先用伪代码来表示一下:

    #如果是非调试模式
      #定义 assert(e) 不做事情
    #否则即是调试模式
      #定义 assert(e) 如果 表达式 e 求值结果 为true 的话,不做任何事情 ,否则 报告错误(错误信息,包含当前源文件,行号,方法名,参数)
    

    实际代码可以参考 : bionic的实现 assert.h

    #ifdef NDEBUG
        #define assert(e) ((void)0)
    #else
        #define assert(e) ((e) ? (void)0 : __assert2(__FILE__, __LINE__,__func__,#e))
    #endif
    

    由于C语言有预编译的特性. 使用了assert调用.如果是使用 RELEASE 模式编译的话,
    assert的调用都变成了 ((void)0) 都变成了空.
    调试模式下,当结果不为真时,打印一下错误信息.

    但是Swift这样的现代语言没有预编译,和宏展开.

    Swift中参数惰性求值问题

    在Swift中遇到这到一个问题.原话如下:

    When implementing assert() in Swift, the first challenge we encounter is that there is no obvious way for a function to accept an expression without evaluating it. For example, say we tried to use

    例如下面的使用场景:

    func fileAtPath(path:String)->Bool{
        print("\(__FUNCTION__)")
        return NSFileManager.defaultManager().fileExistsAtPath(path)
    }
    
    assert(fileAtPath("/tmp/nofile"))
    

    上面的使用场景中,如果是C语言的assert调用的话,在RELEASE模式下,assert语句整个都被擦除了.

    但是在Swift中已经没有这样的宏展开. 所以上面assert方法还会被调用.
    那如果Swift中的 assert的实现是如下的话:

    func assert(boolValue:Bool){
       #if !NDEBUG  // 如果是调试模式
       if !boolValue{
        // 报告错误
        abort() // 终止程序执行
       }
       #endif
    
    }
    

    关键的时,fileAtPath("/tmp/nofile") 会先被调用. 但是这个fileAtPath的参数表达式的执行在 RELEASE模式下是没有意义的了.
    因为assert在RELEASE模式下相当于一个空函数.

    总结: 如果不经验特殊的处理. Swift中的 assert调用,在 RELEASE模式下也会导致,参数表达式执行开销.

    @autoclosure 属性的引入

    于是Swift,尝试将实现改为接受一个闭包而不是 Bool类型的参数.
    如下:

    func assert(predicate:() -> Bool){
      #if !NDEBUG // 如果是调试模式
        if !predicate(){
          // 报告错误
          abort() // 终止程序执行
        }
      #endif
    }
    

    接收一个闭包的话,在RELEASE模式下,predicate闭包不会执行,也即其中表达式不会被执行. 避免了不必要的语句执行开销.

    但是这有一个问题就是, assert的使用将变得不方便了.
    如果像上面声明的话,我们需要像下面这样写调用assert方法

    assert({ fileAtPath("/tmp/nofile") })
    

    写起来复杂,理解起来也复杂,反正是增加了复杂性.及使用难度.

    Swift 通过 引入了编译属性 autoclosure来解决这个问题.
    顾名思义的话,即自动闭包. 自动帮我们生成闭包?
    这样assert函数的实现改为如下:

    func assert(predicate:@autoclosure () -> Bool){
      #if !NDEBUG // 如果是调试模式
        if !predicate(){
          // 报告错误
          abort() // 终止程序执行
        }
      #endif
    }
    

    调用简化如下:

    assert(fileAtPath("/tmp/nofile"))
    

    于是问题得到解决, 好写,并且,效率不受影响.

    @autoclosure Swift 语言中的 其他使用场景

    1. 逻辑与或中的表达式短路执行 逻辑与的特点是: 如果前面一个表达式求值为false的话,后面的表达式就不用求值了. 于是实现可能是如下代码:
    func &&(lhs: BooleanType, rhs: @autoclosure () -> BooleanType) -> Bool{
         return lhs.boolValue ? rhs().boolValue: false
     }
    

    逻辑或的特点是: 如果前面的一个表达式求值为true的话,后面的表达式就不用求值了. 于是实现大概是如下代码:

    func ||(lhs:BooleanType, rhs: @autoclosure () -> BooleanType) -> Bool{
        return lhs.boolValue ? true: rhs().boolValue()
    }
    
    1. 在 ?? 操作符 (Nil Coalescing Operator) 中的使用 a ?? b的执行效果相当于 a != nil ? a! : b 这个语法糖的声明如下:
    func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T
    
        func ??<T>(optional: T?, defaultValue: @autoclosure () -> T?) -> T?
    

    于是上面的方法的实现细节可能如下, from http://swifter.tips/autoclosure/

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

    @autoclosure 虽好,但不要乱用

    因为 : 对应方法的调用都,可能不知道 @autoclosure对应的表达式,可能不会被调用. 因为它看起来就是一个会被要执行的表达式.

    Swift 标准库中的 @autoclosure使用

    Swift中对 @autoclosure的使用主要是如下的函数

    func precondition(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = default, file: StaticString = default, line: UWord = default)
    
    @noreturn func preconditionFailure(@autoclosure message: () -> String = default, file: StaticString = default, line: UWord = default)
    
    @noreturn func fatalError(@autoclosure message: () -> String = default, file: StaticString = default, line: UWord = default)
    
    func assertionFailure(@autoclosure message: () -> String = default, file: StaticString = default, line: UWord = default)
    
    func assert(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = default, file: StaticString = default, line: UWord = default)
    
    func &&<T : BooleanType>(lhs: T, rhs: @autoclosure () -> Bool) -> Bool
    func ||<T : BooleanType, U : BooleanType>(lhs: T, @autoclosure rhs: () -> U) -> Bool
    
    func ??<T>(optional: T?, @autoclosure defaultValue: () -> T?) -> T?
    func ??<T>(optional: T?, @autoclosure defaultValue: () -> T) -> T
    

    上面的几个方法: assert类,上面讨论得比较多了,
    值得注意的是: precondition类, 如果不满足其条件的话,在 RELEASE模式下也会导致应用停止运行.

    一个简单的使用示例 :

    func checkTrue(@autoclosure condition:() -> Bool, @autoclosure _ message:() -> String = "assertion failed"){
        if !condition(){
            print(message())
        }
    }
    
    checkTrue(true)
    

    参考

    1. Building assert() in Swift, Part 1: Lazy Evaluation
    2. http://swifter.tips/autoclosure/
    3. Nil Coalescing Operator ?? 操作符
    3 条回复    2017-12-07 09:48:21 +08:00
    lizhuoli
        1
    lizhuoli  
       2015-06-16 10:32:47 +08:00
    这个写的不错,Swift的Assert本身确实不是非常方便。@autoclosure还没有细看,感谢了。
    mailworks
        2
    mailworks  
       2015-12-20 16:26:12 +08:00
    赞。
    ChouJum
        3
    ChouJum  
       2017-12-07 09:48:21 +08:00
    感谢,@autoclosure 的存在解疑答惑了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2760 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 12:09 · PVG 20:09 · LAX 04:09 · JFK 07:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.