Swfit之關鍵字

一、@avaiable

@available(iOS 2.0, *)
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@available(swift, deprecated: 4.1, renamed: "compactMap(_:)", message: "Please use compactMap(_:) for the case where closure returns an optional value")

@available: 可用來標識計算屬性、函數、類、協議、結構體、枚舉等類型的生命週期。(依賴於特定的平臺版本 或 Swift 版本)。它的後面一般跟至少兩個參數,參數之間以逗號隔開。

  • 其中第一個參數是固定的,代表着平臺、語言、版本號,可選值有以下這幾個:
參數 說明
iOS iOSApplicationExtension
macOS macOSApplicationExtension
watchOS watchOSApplicationExtension
tvOS tvOSApplicationExtension
swift swift語法中可用

​ 可以使用 * 指代支持所有這些平臺。

  • deprecated:版本號: 從指定平臺某個版本開始棄用該聲明(還可以使用,但是官方不建議了)
  • obsoleted:版本號: 從指定平臺某個版本開始廢棄該聲明(廢棄的在調用時就會報錯)
  • message:信息: 使用的時候會有 ⚠️,給出一些警告信息
  • renamed:新聲明: 已經重新聲明的名字,fixed的時候使用
  • unavailabel: 指定平臺無效

1.1 #available在代碼中使用

根據平臺、版本號來執行不同的代碼,做設備和系統版本兼容

if #available(iOS 11.0, *) {
    print("greater than ios 11")
}else {
    // 代碼
    print("less than ios 11")
}

1.2 使用廢棄代碼時警告

二、@discardableResult

有返回值的函數,如果調用的時候沒有處理返回值,就會被編譯器警告⚠️。但有時我們就是不需要返回值的,這個時候我們可以讓編譯器忽略警告,只需要在函數定義前用 @discardableResult 聲明一下。

@discardableResult
func getName(index: Int) -> String {
    return index <= 0 ? "bill" : "loong"
}

三、dynamicCallable

將此屬性應用於類,結構,枚舉或協議,以將該類型的實例視爲可調用函數。

該類型必須實現 dynamicallyCall(withArguments :) 方法,或者dynamicallyCall(withKeywordArguments :)方法或兩者都實現。

@dynamicCallable
struct TelephoneExchange {
    func dynamicallyCall(withArguments phoneNumber: [Int]) {
        if phoneNumber == [4, 1, 1] {
            print("Get Swift help on forums.swift.org")
        } else {
            print("Unrecognized number")
        }
    }
}

let dial = TelephoneExchange()

// Use a dynamic method call.
dial(4, 1, 1)
// Prints "Get Swift help on forums.swift.org"

dial(8, 6, 7, 5, 3, 0, 9)
// Prints "Unrecognized number"

// Call the underlying method directly.
dial.dynamicallyCall(withArguments: [4, 1, 1])

dynamicCall(withArguments :) 方法的聲明必須具有一個符合 ExpressibleByArrayLiteral 協議的參數,例如上面示例中的 [Int]。 返回類型可以是任何類型。

如果實現 dynamicCall(withKeywordArguments :) 方法,則可以在動態方法調用中包含標籤。

@dynamicCallable
struct Repeater {
    func dynamicallyCall(withKeywordArguments pairs: KeyValuePairs<String, Int>) -> String {
        return pairs
            .map { label, count in
                repeatElement(label, count: count).joined(separator: " ")
            }
            .joined(separator: "\n")
    }
}

let repeatLabels = Repeater()
print(repeatLabels(a: 1, b: 2, c: 3, b: 2, a: 1))
// a
// b b
// c c c
// b b
// a

dynamicCall(withKeywordArguments :) 方法的聲明必須具有一個符合 ExpressibleByDictionaryLiteral協議的參數,並且返回類型可以是任何類型。 參數的鍵必須爲 ExpressibleByStringLiteral。 前面的示例使用 KeyValuePairs 作爲參數類型,以便調用者可以包括重複的參數標籤-a和b在調用中重複出現多次。

如果您同時實現兩個 dynamicCall 方法,則當方法調用包含關鍵字參數時,將調用 dynamicCall(withKeywordArguments :) 。 在所有其他情況下,都會調用 dynamicCall(withArguments :)

您只能使用參數和返回值調用動態可調用實例,這些參數和返回值與您在dynamicCall方法實現之一中指定的類型匹配。 以下示例中的調用未編譯,因爲沒有采用 KeyValuePairs <String,String>的dynamicCall(withArguments :) 實現。

repeatLabels(a: "four") // Error

@dynamicMemberLookup

將此屬性應用於類,結構,枚舉或協議,以使成員可以在運行時按名稱查找。 該類型必須實現一個下標(dynamicMemberLookup :)下標。

在顯式成員表達式中,如果沒有相應的命名成員聲明,則該表達式應理解爲對類型的下標(dynamicMemberLookup :)下標的調用,並將有關成員的信息作爲參數傳遞。 下標可以接受參數,該參數可以是密鑰路徑或成員名稱; 如果同時實現兩個下標,則使用帶有key path參數的下標。

下標(dynamicMemberLookup :)的實現可以使用KeyPath,WritableKeyPath或ReferenceWritableKeyPath類型的參數接受鍵路徑。 它可以使用符合ExpressibleByStringLiteral協議(在大多數情況下爲String)類型的參數來接受成員名稱。 下標的返回類型可以是任何類型。

通過成員名稱進行動態成員查找可用於圍繞在編譯時無法進行類型檢查的數據創建包裝類型,例如,將其他語言的數據橋接到Swift中時。 例如:

@dynamicMemberLookup
struct DynamicStruct {
    let dictionary = ["someDynamicMember": 325,
                      "someOtherMember": 787]
    subscript(dynamicMember member: String) -> Int {
        return dictionary[member] ?? 1054
    }
}
let s = DynamicStruct()

// Use dynamic member lookup.
let dynamic = s.someDynamicMember
print(dynamic)
// Prints "325"

// Call the underlying subscript directly.
let equivalent = s[dynamicMember: "someDynamicMember"]
print(dynamic == equivalent)
// Prints "true"

通過鍵路徑進行動態成員查找可用於以支持編譯時類型檢查的方式實現包裝器類型。 例如:

struct Point { var x, y: Int }

@dynamicMemberLookup
struct PassthroughWrapper<Value> {
    var value: Value
    subscript<T>(dynamicMember member: KeyPath<Value, T>) -> T {
        get { return value[keyPath: member] }
    }
}

let point = Point(x: 381, y: 431)
let wrapper = PassthroughWrapper(value: point)
print(wrapper.x)

五、@frozen & @unknown default

frozen 意爲凍結,是爲Swift5ABI穩定準備的一個字段,意味向編譯器保證之後不會做出改變。

將此屬性應用於結構或枚舉聲明,以限制可以對類型進行的更改的種類。 僅在庫演化模式下編譯時才允許使用此屬性。 庫的未來版本無法通過添加,刪除或重新排列枚舉的案例或結構體的存儲實例屬性來更改聲明。 非凍結類型允許進行這些更改,但它們會破壞凍結類型的ABI兼容性。

注意

當編譯器不在庫演化模式下時,所有結構和枚舉都將隱式凍結,並且將忽略此屬性。

比如 swift 源碼中 Array 的聲明,就標記了 frozen ,代表 Array 在以後的版本升級中,結構體的存儲實例屬性不會改變。

@frozen
public struct Array<Element>: _DestructorSafeContainer {
  #if _runtime(_ObjC)
  @usableFromInline
  internal typealias _Buffer = _ArrayBuffer<Element>
  // 省略
}

5.1 凍結枚舉

標記爲凍結的枚舉,意味向編譯器保證之後不會再增加新的枚舉值。

反之,代表以後可能會增加新的枚舉值,這樣在匹配使用的時候,就需要添加 @unknown default 來匹配未知情況

UISerIntefaceStyle 聲明

@available(iOS 12.0, *)
public enum UIUserInterfaceStyle : Int {
    case unspecified = 0
    case light = 1
    case dark = 2
}

使用

        if #available(iOS 12.0, *) {
            let style: UIUserInterfaceStyle = .light

            switch style {
            case .light:
                print("light")
                break
            case .dark:
                print("dark")
                break
            case .unspecified:
                print("unspecified")
            @unknown default:
                fatalError()
            }
        }

如果沒有 unknown default 的時候,會報警告

Switch covers known cases, but 'UIUserInterfaceStyle' may have additional unknown values, possibly added in future versions
Handle unknown values using "@unknown default"
開關涵蓋了已知情況,但是“ UIUserInterfaceStyle”可能具有其他未知值,可能會在將來的版本中添加
使用@unknown default 處理未知值

六、@inlinable

將此屬性應用於函數,方法,計算屬性,下標,便捷初始化程序或反初始化程序聲明,以將該聲明的實現公開爲模塊公共接口的一部分。 允許編譯器在調用站點用符號實現的副本替換對可插入符號的調用。

可插入代碼可以與在任何模塊中聲明的公共符號交互,並且可以與在同一模塊中聲明的內部符號(使用usableFromInline屬性標記)交互。 不可插入的代碼不能與私有或文件私有符號進行交互。

此屬性不能應用於嵌套在函數內部的聲明或文件私有或私有聲明。 即使無法使用此屬性標記,在可嵌入函數內部定義的函數和閉包也是隱式可嵌入的。

這個關鍵詞是可內聯的聲明,它來源於 C 語言中的inlineC中一般用於函數前,做內聯函數,它的目的是防止當某一函數多次調用造成函數棧溢出的情況。因爲聲明爲內聯函數,會在編譯時將該段函數調用用具體實現代替,這麼做可以省去函數調用的時間。
內聯函數常出現在系統庫中,OC中的runtime 中就有大量的 inline 使用:

// Array的removeAll函數
@inlinable
public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) {
  if !keepCapacity {
    _buffer = _Buffer()
  }
  else {
    self.replaceSubrange(indices, with: EmptyCollection())
  }
}

需要注意
內聯聲明不能用於標記爲 private 或者fileprivate的地方。
這很好理解,對私有方法的內聯是沒有意義的。內聯的好處是運行時更快,因爲它省略了從標準庫調用 map 實現的步驟。但這個快也是有代價的,因爲是編譯時做替換,這增加了編譯的開銷,會相應的延長編譯時間。
內聯更多的是用於系統庫的特性。

七、@main

將此屬性應用於結構,類或枚舉聲明,以指示它包含程序流的頂級入口點。 類型必須提供不帶任何參數並返回 Voidmain 類型函數。 例如:

@main
struct MyTopLevel {
    static func main() {
        // Top-level code goes here
    }
}

描述 main 屬性要求的另一種方法是,您寫此屬性的類型必須滿足與符合以下假設協議的類型相同的要求:

protocol ProvidesMain {
    static func main() throws
}

編譯爲可執行文件的Swift代碼最多可以包含一個頂級入口點,如頂級代碼中所述。

八、@propertyWrapper

將此屬性應用於類、結構體或枚舉聲明,以將該類型用作屬性包裝器。

  • 當將此屬性應用於類型時,將創建一個與該類型同名的自定義屬性。
  • 將該新屬性應用於類、結構體或枚舉的屬性,以通過包裝類型的實例包裝對該屬性的訪問。
  • 局部變量和全局變量不能使用屬性包裝器。

包裝器 必須定義 wrappedValue 實例屬性。該屬性的包裝值是該屬性的 gettersetter 公開的值。

在大多數情況下,wrappedValue 是一個計算值,但也可以是一個存儲值。包裝器負責定義和管理其包裝值所需的任何基礎存儲。編譯器通過爲包裝屬性的名稱加上下劃線(_)作爲前綴來爲包裝類型的實例綜合存儲,例如:someProperty 的包裝以 _someProperty 的形式存儲。包裝器的合成存儲具有 private 的訪問控制級別。

具有屬性包裝器的屬性可以包含 willSetdidSet 塊,但不能覆蓋編譯器合成的 getset 塊。

8.1 初始化屬性

Swift 提供了兩種形式的語法糖來初始化屬性包裝器。您可以在包裝值的定義中使用賦值語法,以將賦值右側的表達式作爲屬性包裝器的初始化程序的 wrapedValue 參數的參數傳遞。當您將屬性套用到屬性時,您也可以提供參數,並將這些參數傳遞給屬性包裝器的初始化器。例如,在下面的代碼中,SomeStruct 調用 SomeWrapper 定義的每個初始化程序。

@propertyWrapper
struct SomeWrapper {
    var wrappedValue: Int
    var someValue: Double
    init() {
        self.wrappedValue = 100
        self.someValue = 12.3
    }
    init(wrappedValue: Int) {
        self.wrappedValue = wrappedValue
        self.someValue = 45.6
    }
    init(wrappedValue value: Int, custom: Double) {
        self.wrappedValue = value
        self.someValue = custom
    }
}

struct SomeStruct {
    // Uses init()
    @SomeWrapper var a: Int

    // Uses init(wrappedValue:)
    @SomeWrapper var b = 10

    // Both use init(wrappedValue:custom:)
    @SomeWrapper(custom: 98.7) var c = 30
    @SomeWrapper(wrappedValue: 30, custom: 98.7) var d
}

8.2 獲取包裝值

包裝屬性的投影值是屬性包裝器可以用來公開其他功能的第二個值。 屬性包裝器類型的作者負責確定其投影值的含義,並定義投影值公開的接口。 要從屬性包裝器投射值,請在包裝器類型上定義一個 projectedValue 實例屬性。 編譯器通過爲包裝的屬性的名稱加上美元符號($)前綴來合成投影值的標識符,例如,someProperty 的投影值是 $someProperty。 投影的值與原始包裝的屬性具有相同的訪問控制級別。

@propertyWrapper
struct WrapperWithProjection {
    var wrappedValue: Int
    var projectedValue: SomeProjection {
        return SomeProjection(wrapper: self)
    }
}
struct SomeProjection {
    var wrapper: WrapperWithProjection
}

struct SomeStruct {
    @WrapperWithProjection var x = 123
}
let s = SomeStruct()
s.x           // Int value
s.$x          // SomeProjection value
s.$x.wrapper  // WrapperWithProjection value

8.3 UserDefaults中包裝使用

@propertyWrapper
struct UserDefaultWrapper<T> {
    let key: String
    let defaultValue: T
    
    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
    
    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.setValue(newValue, forKey: key)
        }
    }
}


struct UserDefaultsStandard {
    @UserDefaultWrapper("showGuide", defaultValue: false)
    static var showGuide: Bool
}

// 使用
print(UserDefaultsStandard.showGuide)
UserDefaultsStandard.showGuide = true
print(UserDefaultsStandard.showGuide)

九、@NSCopying

將此屬性應用於中存儲的變量屬性。 此屬性使屬性的設置器與屬性值的副本(由 copyWithZone(_ :) 方法返回)合成,而不是與屬性本身的值合成。 該屬性的類型必須符合 NSCopying 協議。

NSCopying 屬性的行爲類似於 Objective-C 複製屬性的行爲。

class Student: NSCopying {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        print("invoke copy")
        return Student(name: name, age: age)
    }
}

class Teacher {
    @NSCopying
    var student: Student?
    
    var name: String = "teacher"
}


let s1 = Student(name: "lili", age: 30)
let t = Teacher()
t.student = s1
s1.name = "bobo"
print(s1.name, t.student!.name)

--- output
invoke copy
bobo lili
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章