Swift @autoclosure 屬性的使用

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)

那...那有毛用啊???(懵逼表情)

我也覺得確實沒毛用啊...??

如果非要解釋起來,我覺得是

  1. 鼓勵大家使用閉包。閉包的執(zhí)行是可以控制的,將函數(shù)的參數(shù)設(shè)定為autoclosure后,可以有意識地延遲參數(shù)創(chuàng)建的時機。
  2. 懶人可以少打兩個括號
官方使用場景:

例: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)
強行思考了一些其他使用場景
  1. 傳遞的參數(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
==============
*/
  1. 在當前的隊列中,調(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)

  1. 使用宏#file, #function等來構(gòu)建log
  2. 出于某些我也不知道為啥的原因,不想在生產(chǎn)環(huán)境中調(diào)用這些宏
  3. 所以用閉包來控制log的構(gòu)建時機
  4. 同時又懶得每次寫閉包的括號,所以用上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.
===========================================
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容