由于種種原因妒挎,簡書等第三方平臺博客不再保證能夠同步更新,歡迎移步 GitHub:https://github.com/kingcos/Perspective/。謝謝纳账!
Date | Notes | Swift | Xcode |
---|---|---|---|
2018-01-13 | 首次提交 | 4.0.3 | 9.2 |
Perspective吱涉,即透視刹泄。筆者希望可以盡力將一些不是那么透徹的點透過 Demo 和 Source Code 而看到其本質外里。由于國內軟件開發(fā)仍很大程度依賴國外的語言、知識特石,所以該系列文章中的術語將使用英文表述盅蝗,除非一些特別統(tǒng)一的詞匯或整段翻譯時將使用中文,但也會在首次提及時標注英文姆蘸。筆者英文水平有限墩莫,這樣的目的也是盡可能減少歧義,但在其中不免有所錯誤逞敷,遺漏狂秦,還請大家多多批評、指正推捐。
本文也會同步在筆者的 GitHub 的 Perspective 倉庫:https://github.com/kingcos/Perspective裂问,歡迎 Star ??。
— Kingcos
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.0.3)
Closure 在 Swift 等許多語言中普遍存在牛柒。熟悉 Objective-C 的同學一定對 Block 不陌生堪簿。兩者其實是比較類似的,相較于 Block皮壁,Closure 的寫法簡化了許多椭更,也十分靈活。
在 Swift 中闪彼,@
開頭通常代表著 Attribute甜孤。@autoclosure
屬于 Type Attribute,意味著其可以對類型(Type)作出一些限定畏腕。
How
自動(Auto-)
-
@autoclosure
名稱中即明確了這是一種「自動」的 Closure缴川,即可以讓表達式(Expression)的類型轉換為相應的 Closure 的類型,即在調用原本的func
時描馅,可以省略 Closure 參數的大括號把夸; - 其只可以修飾作為參數的 Closure,但該 Closure 必須為無參铭污,返回值可有可無恋日。
func logIfTrue(_ predicate: () -> Bool) {
if predicate() {
print("True")
}
}
// logIfTrue(predicate: () -> Bool)
logIfTrue { 1 < 2 }
func logIfTrueWithAutoclosure(_ predicate: @autoclosure () -> Bool) {
if predicate() {
print("True")
}
}
// logIfTrueWithAutoclosure(predicate: Bool)
logIfTrueWithAutoclosure(1 < 2)
Closure 的 Delay Evaluation
- Swift 中的 Closure 調用將會被延遲(Delay),即該 Closure 只有在真正被調用時嘹狞,才被執(zhí)行岂膳;
- Delay Evaluation 有利于有副作用或運算開銷較大的代碼;
- Delay Evaluation 非
@autoclosure
獨有磅网,但通常搭配使用谈截。
var array = [1, 2, 3, 4, 5]
array.removeLast()
print(array.count)
var closure = { array.removeLast() }
print(array.count)
closure()
print(array.count)
// OUTPUT:
// 4
// 4
// 3
@escaping
- 當 Closure 的真正執(zhí)行時機可能要在其所在
func
返回(Return)之后時,通常使用@esacping
,可以用于處理一些耗時操作的回調簸喂。 -
@autoclosure
與@escaping
是可以兼容的毙死,順序可以顛倒。
func foo(_ bar: @autoclosure @escaping () -> Void) {
DispatchQueue.main.async {
bar()
}
}
測試用例
swift/test/attr/attr_autoclosure.swift
- Swift 作為完全開源的一門編程語言喻鳄,這就意味著可以隨時去查看其內部的實現的機制扼倘,而根據相應的測試用例,也能將正確和錯誤的用法一探究竟除呵。
inout
autoclosure + inout doesn't make sense.
-
inout
與@autoclosure
沒有意義再菊,不兼容。 - 下面是一個簡單的
inout
Closure 的 Demo竿奏,其實并沒有什么意義袄简。一般來說也很少會去將一個func
進行inout
,更多的其實是用在值類型(Value Type)的變量(Variable)中泛啸。
var demo: () -> () = {
print("func - demo")
}
func foo(_ closure: @escaping () -> ()) {
var closure = closure // Ignored the warning
closure = {
print("func - escaping closure")
}
}
foo(demo)
demo()
// OUTPUT:
// func - demo
func bar(_ closure: inout () -> ()) {
closure = {
print("func - inout closure")
}
}
bar(&demo)
demo()
// OUTPUT:
// func - inout closure
可變參數(Variadic Parameters)
-
@autoclosure
不適用于func
可變參數绿语。
// ERROR
func variadicAutoclosure(_ fn: @autoclosure () -> ()...) {
for _ in fn {}
}
源代碼用例
swift/stdlib/public/core/Bool.swift
- 在一些其他語言中,
&&
和||
屬于短路(Short Circuit)運算符候址,在 Swift 中也不例外吕粹,恰好就利用了 Closure 的 Delay Evaluation 特性。
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()
}
}
swift/stdlib/public/core/Optional.swift
- 在 Swift 中岗仑,
??
也屬于短路運算符匹耕,這里兩個實現的唯一不同是第二個func
的defaultValue
參數會再次返回可選(Optional)型,使得??
可以鏈式使用荠雕。
@_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/stdlib/public/core/AssertCommon.swift
-
COMPILER_INTRINSIC
代表該func
為編譯器的內置函數:-
swift/stdlib/public/core/StringSwitch.swift
中提到The compiler intrinsic which is called to lookup a string in a table of static string case values.
(筆者譯:編譯器內置稳其,即在一個靜態(tài)字符串值表中查找一個字符串。
)炸卑; - WikiPedia 中解釋:
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.
(筆者譯:在計算機軟件領域既鞠,編譯器理論中,內置函數(或稱內建函數)是在給定編程語言中可以被編譯器所專門處理的的函數(子程序)盖文。通常嘱蛋,它可以用一系列自動生成的指令代替原來的函數調用,類似于內聯函數五续。與內聯函數不同的是洒敏,編譯器更加了解內置函數,因此可以更好地整合和優(yōu)化特定情況疙驾。
)凶伙。
-
-
_assertionFailure()
:斷言(Assert)失敗,返回類型為Never
它碎; - 該
func
的返回值類型為范型T
镊靴,主要是為了類型推斷铣卡,但_assertionFailure()
執(zhí)行后程序就會報錯并停止執(zhí)行,類似fatalError()
偏竟,所以并無實際返回值。
// FIXME(ABI)#21 (Type Checker): rename to something descriptive.
@_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)
}
swift/stdlib/public/SDK/Dispatch/Dispatch.swift
- 這里的 Closure 的返回值
DispatchPredicate
敞峭,本質其實是枚舉類型踊谋,可以直接填入該類型的值,也可以傳入 Closure旋讹,大大地提高了靈活性殖蚕。
@_transparent
@available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)
public func dispatchPrecondition(condition: @autoclosure () -> DispatchPredicate) {
// precondition is able to determine release-vs-debug asserts where the overlay
// cannot, so formulating this into a call that we can call with precondition()
precondition(_dispatchPreconditionTest(condition()), "dispatchPrecondition failure")
}
swift/stdlib/public/core/Assert.swift
- 斷言相關的方法很多的參數選為了 Closure,并標注了
@autoclosure
沉迹,一是可以直接將表達式直接作為參數睦疫,而不需要參數,二是當 Release 模式時鞭呕,Closure 沒有必要執(zhí)行蛤育,即可節(jié)省開銷。
@_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.
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
}
Why
- Q: 總結一下為什么要使用
@autoclosure
呢葫松? - A: 通過上述官方源代碼的用例可以得出:當開發(fā)者需要的
func
的參數可能需要額外執(zhí)行一些開銷較大的操作的時候瓦糕,可以使用。 - 因為如果開銷不大腋么,完全可以直接將參數類型設置為返回值的類型咕娄,只是此時無論是否參數后續(xù)被用到,得到的過程必然是會被調用的珊擂。
- 而如果不需要執(zhí)行多個操作圣勒,也可以不使用
@autoclosure
,而是直接傳入func
摧扇,無非是括號的區(qū)分圣贸。
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.0.3)
- 官網文檔其實指出了開發(fā)者應當盡量不要濫用
@autoclosure
,如果必須使用扳剿,也需要做到明確旁趟、清晰,否則可能會讓他人感到疑惑庇绽。
也歡迎您關注我的微博 @萌面大道V