Swift 中的 @autoclosure

因爲種種緣由,掘金等第三方平臺博客再也不保證可以同步更新,歡迎移步 GitHub:github.com/kingcos/Per…。謝謝!git

Date Notes Swift Xcode Source Code
2018-04-05 更新並明確源代碼所用版本 4.1 9.3 Swift 4.1 Release
2018-01-13 首次提交 4.0.3 9.2 -

您能夠在 github.com/kingcos/Per… 中看到更多更新的博文,謝謝您的關注。github

@autoclosure

What

Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages.編程

The Swift Programming Language (Swift 4.1)swift

閉包(Closure)在 Swift 等許多語言中廣泛存在。熟悉 Objective-C 的同窗必定對 Block 不陌生。二者實際上是比較相似的,相較於 Block,閉包的寫法簡化了許多,也十分靈活。api

在 Swift 中,@ 開頭一般表明着屬性(Attribute)。@autoclosure 屬於類型屬性(Type Attribute),意味着其能夠對類型(Type)做出一些限定。閉包

How

自動(Auto-)

  • @autoclosure 名稱中即明確了這是一種「自動」閉包,便可以讓返回該參數類型的閉包做爲參數;
  • 其只能夠修飾做爲參數的閉包類型,但該閉包不能有參數,不然會報錯:「error: argument type of @autoclosure parameter must be '()'」。
func logIfTrue(_ predicate: () -> Bool) {
    if predicate() {
        print(#function)
    }
}

// logIfTrue(predicate: () -> Bool)
logIfTrue { 1 < 2 }

func logIfTrueWithAutoclosure(_ predicate: @autoclosure () -> Bool) {
    if predicate() {
        print(#function)
    }
}

// logIfTrueWithAutoclosure(predicate: Bool)
logIfTrueWithAutoclosure(1 < 2)

// OUTPUT:
// logIfTrue
// logIfTrueWithAutoclosure
複製代碼

延遲調用(Delay Evaluation)

  • Swift 中的閉包會被延遲調用,即只有在真正被調用時,才被執行;
  • 延遲調用特性有利於非必須執行且運算開銷較大的代碼;
  • 該特性非 @autoclosure 獨有,但一般搭配使用。
var array = [1, 2, 3, 4, 5]

array.removeLast()
print(array.count)

var closureVar = { array.removeLast() }
print(array.count)

closureVar()
print(array.count)

// OUTPUT:
// 4
// 4
// 3
複製代碼

@escaping

  • 當閉包的真正執行時機可能要在其所在函數返回(Return)以後時,一般使用 @escaping,能夠用於處理一些耗時操做的回調;
  • @autoclosure@escaping 是能夠兼容的,放置順序能夠顛倒。
func doWith(_ completion: () -> Void) {
    completion()
}

func doWithAutoclosureAndEscaping(_ escaper: @autoclosure @escaping () -> Void) {
    doWith {
        escaper()
    }
}

func doWithEscapingAndAutoclosure(_ escaper: @escaping @autoclosure () -> Void) {
    doWith {
        escaper()
    }
}
複製代碼

Source Code

Test Cases

$SWIFT_SOURCE_CODE_PATH/test/attr/attr_autoclosure.swiftapp

  • inout@autoclosure 不兼容,且沒有實際意義;
  • @autoclosure 不適用於函數的可變參數(Variadic Parameters)。

Use Cases

$SWIFT_SOURCE_CODE_PATH/stdlib/public/core/編程語言

短路(Short Circuit)運算符

// Bool.swift
extension Bool {
  @_inlineable // FIXME(sil-serialize-all)
  @_transparent
  @inline(__always)
  public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows
      -> Bool {
    return lhs ? try rhs() : false
  }

  @_inlineable // FIXME(sil-serialize-all)
  @_transparent
  @inline(__always)
  public static func || (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows
      -> Bool {
    return lhs ? true : try rhs()
  }
}

// Optional.swift
@_inlineable // FIXME(sil-serialize-all)
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
    rethrows -> T {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}

@_inlineable // FIXME(sil-serialize-all)
@_transparent
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?)
    rethrows -> T? {
  switch optional {
  case .some(let value):
    return value
  case .none:
    return try defaultValue()
  }
}
複製代碼
  • Swift 中的 &&|| 以及 ?? 屬於短路運算符,即當表達式左邊的結果已經能夠決定整個運算符的返回值時(運算符的本質也是函數),右邊便沒有必要運算。利用了 @autoclosure 使得運算符右邊能夠爲閉包,再憑藉 Delay Evaluation 特性保證了「短路」。
var flag = 0
var age: Int? = nil

func getAgeA() -> Int? {
    flag += 10
    return 20
}

func getAgeB() -> Int? {
    flag += 100
    return nil
}

age ?? getAgeA() ?? getAgeB()
print(flag)

// OUTPUT:
// 10
複製代碼

斷言(Assert)

  • 斷言相關的方法將某些參數設置爲閉包類型,並標註了 @autoclosure,一是能夠直接將閉包直接做爲參數;二是當 Release 模式時,Closure 沒有必要執行,便可節省開銷(XCTest 和 Dispatch 中的部分方法同理)。
// AssertCommon.swift
@_inlineable // FIXME(sil-serialize-all)
public // COMPILER_INTRINSIC
func _undefined<T>( _ message: @autoclosure () -> String = String(),
  file: StaticString = #file, line: UInt = #line
) -> T {
  _assertionFailure("Fatal error", message(), file: file, line: line, flags: 0)
}

// Assert.swift
@_inlineable // FIXME(sil-serialize-all)
@_transparent
public func assert( _ condition: @autoclosure () -> Bool,
  _ message: @autoclosure () -> String = String(),
  file: StaticString = #file, line: UInt = #line
) {
  // Only assert in debug mode.
  // 在 Debug 模式且條件不成立,斷言失敗
  if _isDebugAssertConfiguration() {
    if !_branchHint(condition(), expected: true) {
      _assertionFailure("Assertion failed", message(), file: file, line: line,
        flags: _fatalErrorFlags())
    }
  }
}

@_inlineable // FIXME(sil-serialize-all)
@_transparent
public func precondition( _ condition: @autoclosure () -> Bool,
  _ message: @autoclosure () -> String = String(),
  file: StaticString = #file, line: UInt = #line
) {
  // Only check in debug and release mode. In release mode just trap.
  if _isDebugAssertConfiguration() {
    if !_branchHint(condition(), expected: true) {
      _assertionFailure("Precondition failed", message(), file: file, line: line,
        flags: _fatalErrorFlags())
    }
  } else if _isReleaseAssertConfiguration() {
    let error = !condition()
    Builtin.condfail(error._value)
  }
}

@_inlineable // FIXME(sil-serialize-all)
@inline(__always)
public func assertionFailure( _ message: @autoclosure () -> String = String(),
  file: StaticString = #file, line: UInt = #line
) {
  if _isDebugAssertConfiguration() {
    _assertionFailure("Fatal error", message(), file: file, line: line,
      flags: _fatalErrorFlags())
  }
  else if _isFastAssertConfiguration() {
    _conditionallyUnreachable()
  }
}

@_inlineable // FIXME(sil-serialize-all)
@_transparent
public func preconditionFailure( _ message: @autoclosure () -> String = String(),
  file: StaticString = #file, line: UInt = #line
) -> Never {
  // Only check in debug and release mode. In release mode just trap.
  if _isDebugAssertConfiguration() {
    _assertionFailure("Fatal error", message(), file: file, line: line,
      flags: _fatalErrorFlags())
  } else if _isReleaseAssertConfiguration() {
    Builtin.int_trap()
  }
  _conditionallyUnreachable()
}

@_inlineable // FIXME(sil-serialize-all)
@_transparent
public func fatalError( _ message: @autoclosure () -> String = String(),
  file: StaticString = #file, line: UInt = #line
) -> Never {
  _assertionFailure("Fatal error", message(), file: file, line: line,
    flags: _fatalErrorFlags())
}

@_inlineable // FIXME(sil-serialize-all)
@_transparent
public func _precondition( _ condition: @autoclosure () -> Bool, _ message: StaticString = StaticString(),
  file: StaticString = #file, line: UInt = #line
) {
  // Only check in debug and release mode. In release mode just trap.
  if _isDebugAssertConfiguration() {
    if !_branchHint(condition(), expected: true) {
      _fatalErrorMessage("Fatal error", message, file: file, line: line,
        flags: _fatalErrorFlags())
    }
  } else if _isReleaseAssertConfiguration() {
    let error = !condition()
    Builtin.condfail(error._value)
  }
}

@_inlineable // FIXME(sil-serialize-all)
@_transparent
public func _debugPrecondition( _ condition: @autoclosure () -> Bool, _ message: StaticString = StaticString(),
  file: StaticString = #file, line: UInt = #line
) {
  // Only check in debug mode.
  if _isDebugAssertConfiguration() {
    if !_branchHint(condition(), expected: true) {
      _fatalErrorMessage("Fatal error", message, file: file, line: line,
        flags: _fatalErrorFlags())
    }
  }
}

@_inlineable // FIXME(sil-serialize-all)
@_transparent
public func _sanityCheck( _ condition: @autoclosure () -> Bool, _ message: StaticString = StaticString(),
  file: StaticString = #file, line: UInt = #line
) {
#if INTERNAL_CHECKS_ENABLED
  if !_branchHint(condition(), expected: true) {
    _fatalErrorMessage("Fatal error", message, file: file, line: line,
      flags: _fatalErrorFlags())
  }
#endif
}
複製代碼

Summary

It’s common to call functions that take autoclosures, but it’s not common to implement that kind of function.函數

NOTE優化

Overusing autoclosures can make your code hard to understand. The context and function name should make it clear that evaluation is being deferred.

The Swift Programming Language (Swift 4.1)

  • 一般,開發者並沒有必要去實現帶有 @autoclosure 的函數,若是確有必要,也須要作到明確、清晰。

Extension

COMPILER_INTRINSIC

The compiler intrinsic which is called to lookup a string in a table of static string case values.(筆者譯:編譯器內置,即在一個靜態字符串值表中查找一個字符串。)

$SWIFT_SOURCE_CODE_PATH/stdlib/public/core/StringSwitch.swift

In computer software, in compiler theory, an intrinsic function (or builtin function) is a function (subroutine) available for use in a given programming language which implementation is handled specially by the compiler. Typically, it may substitute a sequence of automatically generated instructions for the original function call, similar to an inline function. Unlike an inline function, the compiler has an intimate knowledge of an intrinsic function and can thus better integrate and optimize it for a given situation.(筆者譯:在計算機軟件領域,編譯器理論中,內置函數(或稱內建函數)是在給定編程語言中能夠被編譯器所專門處理的的函數(子程序)。一般,它能夠用一系列自動生成的指令代替原來的函數調用,相似於內聯函數。與內聯函數不一樣的是,編譯器更加了解內置函數,所以能夠更好地整合和優化特定狀況。)。

WikiPedia

  • COMPILER_INTRINSIC 表明其爲編譯器的內置函數。

也歡迎您關注個人微博 @萌面大道V

Reference

相關文章
相關標籤/搜索