Swift有個不知所謂(劃掉)的屬性(attribute),叫@autoclosure。
官方定義如下
An autoclosure is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function. It doesn’t take any arguments, and when it’s called, it returns the value of the expression that’s wrapped inside of it. This syntactic convenience lets you omit braces around a function’s parameter by writing a normal expression instead of an explicit closure.
https://docs.swift.org/swift-book/LanguageGuide/Closures.html
直譯的話,autoclosure(自動閉包)就是一個自動構(gòu)建的閉包。該閉包不接受任何參數(shù),不含任何處理,只返回一個值。
用代碼來解釋就是
// 原本你定義一個函數(shù)如下
func someFunc(_ para: T) {
let argument = para
}
// 你這么調(diào)用
let arg = T()
someFunc(arg)
// 后來你把形參改成了@autoclosure屬性的閉包
func someFunc(_ closure: @autoclosure () -> T) {
// 于是你在函數(shù)內(nèi)需要執(zhí)行閉包,才能獲取想要的參數(shù)
let argument = closure()
}
// 但是調(diào)用函數(shù)時,你還是可以直接傳遞參數(shù)
let arg = T()
someFunc(arg)
那...那有毛用啊???(懵逼表情)
我也覺得確實沒毛用啊...??
如果非要解釋起來,我覺得是
- 鼓勵大家使用閉包。閉包的執(zhí)行是可以控制的,將函數(shù)的參數(shù)設(shè)定為autoclosure后,可以有意識地延遲參數(shù)創(chuàng)建的時機。
- 讓懶人可以少打兩個括號
官方使用場景:
例:assert(condition:message:file:line:)函數(shù)
可以看到condition和message參數(shù)都接受一個autoclosure形式的閉包,在不同優(yōu)化設(shè)定的build下,該函數(shù)會決定閉包是否執(zhí)行。
/// Performs a traditional C-style assert with an optional message.
///
/// * In playgrounds and `-Onone` builds (the default for Xcode's Debug
/// configuration): If `condition` evaluates to `false`, stop program
/// execution in a debuggable state after printing `message`.
///
/// * In `-O` builds (the default for Xcode's Release configuration),
/// `condition` is not evaluated, and there are no effects.
///
/// * In `-Ounchecked` builds, `condition` is not evaluated, but the optimizer
/// may assume that it *always* evaluates to `true`. Failure to satisfy that
/// assumption is a serious programming error.
///
public func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)
強行思考了一些其他使用場景
- 傳遞的參數(shù)可能為某個消耗巨大的函數(shù)或者getter方法的返回值,且該參數(shù)并不是每次都需要使用
var hugeString: String {
var str = "result"
// 假設(shè)這里有很多處理
str += " is heavy"
return str
}
var isNecessary: Bool = false
func showStringIfNecessary(_ closure: @autoclosure () -> String) {
if isNecessary {
print(closure())
}
print("==============")
}
showStringIfNecessary(hugeString)
/*
==============
*/
isNecessary = !isNecessary
showStringIfNecessary(hugeString)
/*
result is heavy
==============
*/
- 在當前的隊列中,調(diào)用某些函數(shù)或者getter方法
var hugeString: String {
var str = "result"
// 假設(shè)這里有很多處理
print("[Thread] \(Thread.current)")
str += " is heavy"
return str
}
var currentQueue: DispatchQueue = DispatchQueue.global()
func createStringInCurrentQueue(_ closure: @escaping @autoclosure () -> String) {
currentQueue.async {
print(closure())
print("==============")
}
}
createStringInCurrentQueue(hugeString)
/*
[Thread] <NSThread: 0x600000acdfc0>{number = 3, name = (null)}
result is heavy
==============
*/
currentQueue = DispatchQueue.main
createStringInCurrentQueue(hugeString)
/*
[Thread] <NSThread: 0x600000adeb80>{number = 1, name = main}
result is heavy
==============
*/
用上autoclosure的一個極簡logger實現(xiàn)
- 使用宏
#file,#function等來構(gòu)建log - 出于某些我也不知道為啥的原因,不想在生產(chǎn)環(huán)境中調(diào)用這些宏
- 所以用閉包來控制log的構(gòu)建時機
- 同時又懶得每次寫閉包的括號,所以用上autoclosure
https://github.com/itsuhi-shu/Logger
import Foundation
import os
func buildLog<T>(_ message: T,
level: Logger.Level = Logger.level,
file: String = #file, function: String = #function, line: Int = #line) -> String? {
guard Logger.Level != .release else { return nil }
switch level {
case .verbose:
let fileName = (file as NSString).lastPathComponent
return """
[file:\(fileName)]:[line:\(line)]
[thread:\(Thread.current)]
\(message)
===========================================
"""
case .debug:
return "\(message)"
case .release:
return nil
}
}
func log(_ message: @autoclosure () -> String?) {
if let message = message() {
os_log("%@", message)
}
}
struct Logger {
enum Level {
case release
case debug
case verbose
}
static var level: Level = .release
}
調(diào)用如下
//
// AppDelegate.swift
// ***
//
// Created by *** on 2020/02/04.
// Copyright ? 2020 ***. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Logger.level = .debug
log(buildLog("App did Launch.", level: .verbose))
return true
}
}
output
2020-02-13 16:15:34.269027+0900 ***[5291:1333819]
[file:AppDelegate.swift]:[line:18]
[thread:<NSThread: 0x282e26f00>{number = 1, name = main}]
App did Launch.
===========================================