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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章