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

相关文章
相关标签/搜索