返回介绍

特性

发布于 2020-03-05 16:26:33 字数 17093 浏览 914 评论 0 收藏 0

特性给有关声明或类型提供更多的信息。在 Swift 中有两种特性,一种用于声明,另一种用于类型。

通过在 @ 符号后跟一个特性名称和该特性可以接受的实际参数来指定一个特性:

@ attribute name

@ attribute name ( attribute arguments )

一些接受实际参数的声明特性指定了更多关于它的信息和它们如何应用到特定的声明中。这些特性实际参数写在一对括号里,其格式由它们所属的特性定义。

声明特性

声明特性只能应用于声明中。

available

把这个特性应用到任意声明中,以指出该声明相对于特定平台和操作系统版本的生命周期。

available 特性总是和两个或两个以上用逗号隔开的特性实际参数列表同时出现。实际参数列表中的第一个参数是下列平台名称之一:

  • iOS
  • iOSApplicationExtension
  • macOS
  • macOSApplicationExtension
  • watchOS
  • watchOSApplicationExtension
  • tvOS
  • tvOSApplicationExtension
  • swift

你也可以用星号( * )指明一个声明在上述所有平台中都可用。

其余实际参数可以按任何顺序出现,它们指定关于声明生命周期的其他信息,包括重要的里程碑。

  • unavailable 实际参数表示其声明不能用于特定的平台上。
  • introduced 实际参数表示引入声明的特定平台的最低版本。其格式如下所示:

introduced: version number

version number 由一个或一个以上正整数组成,用小数点隔开。

  • deprecated 实际参数表示弃用声明的特定平台的最低版本。其格式如下所示:

deprecated: version number

可选的 version number 由一个或一个以上正整数组成,用小数点隔开。不写版本号表示现在弃用了该声明,不给出任何关于该声明何时被弃用的信息。如果你不写版本号,也不要写冒号( : )。

  • obsoleted 实际参数表示废弃声明的特定平台的最低版本。当一个声明被废弃之后,它就从特定的平台移除了,以后不能再使用。其格式如下所示:

obsoleted: version number

version number 由一个或一个以上正整数组成,用小数点隔开。

  • message 实际参数用于显示那些,由于使用了被弃用或被作废的声明而编译器显示的警告或错误文本信息。其格式如下所示:

message: message

message 实际参数由字符串字面量组成。

  • renamed 实际参数用于提供改了名称的声明的新名字。使用已经改了名称的声明时会发生错误,这时编译器会显示其新名字。格式如下所示:

renamed: new name

new name 由字符串字面量组成。

你可以结合 unavailable 实际参数和类型别名声明使用 renamed 实际参数,告诉你代码的使用者,声明已经改名字了。例如,发布一个框架或静态库时有一个声明的名称会改变,这是就很有用。

// First release
protocol MyProtocol {
    // protocol definition
}
// Subsequent release renames MyProtocol
protocol MyRenamedProtocol {
    // protocol definition
}
 
@available(*, unavailable, renamed: "MyRenamedProtocol")
typealias MyProtocol = MyRenamedProtocol

你可以在单个声明中使用多个 available 特性,以指定该声明在不同平台上是否可用。只有当前编译目标的平台和一个属性中指定的平台相同时,编译器才会使用这个 available 特性。

如果一个 available 特性除了一个平台名称实际参数外只指定一个 introduced 实际参数,可以使用下面的简化语法:

@available(platform name version number, *)
@available(swift version number)

available 特性的简化语法允许简洁地表示多个平台的可用性。尽管两种格式功能上是一样的,只要有可能,尽量使用简化语法。

@available(iOS 10.0, macOS 10.12, *)
class MyClass {
    // class definition
}

available 特性标明了 Swift 版本可用性不能再额外地标明声明的平台可用性。相反,使用多个 available 特性标明 Swift 版本可用性和一个或多个平台可用性。

@available(swift 3.0.2)
@available(macOS 10.12, *)
struct MyStruct {
    // struct definition
}

discardableResult

把这个特性用在函数或方法的声明中,当调用一个有返回值的函数或者方法却没有使用返回值时,编译器不会产生警告。

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])

方法 dynamicallyCall(withArguments:) 的声明必须有唯一的一个遵循 ExpressibleByArrayLiteral 协议的形式参数——就像上面例子中的 [Int] ——并且返回类型可以是任意类型。

你可以在动态方法调用中包含标签,如果你实现 dynamicallyCall(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

方法 dynamicallyCall(withKeywordArguments:) 的声明必须有唯一的一个遵循 ExpressibleByDictionaryLiteral 协议的形式参数,并且返回类型可以是任意类型。形式参数的 Key 必须是 ExpressibleByStringLiteral 。先前的例子使用 KeyValuePairs 作为形式参数类型,所以调用者可以包含重复形式参数标签—— a 和 b 被使用了多次来 repeat 。

如果你同时实现两个 dynamicallyCall 方法, dynamicallyCall(withKeywordArguments:) 会在方法调用包含关键字实际参数时调用。否则, dynamicallyCall(withArguments:) 会被调用。

你只能使用实际参数调用动态调用实例并且返回类型匹配你在 dynamicallyCall 实现中写明的类型。下面例子中的调用方法不会通过编译因为没有接收 KeyValuePairs<String, String> 的 dynamicallyCall(withArguments:) 实现。

repeatLabels(a: "four") // Error

dynamicMemberLookup

应用这个特性到类、结构体、枚举或者协议来允许在运行时可通过名字查找成员。类型必须实现一个 subscript(dynamicMemberLookup:) 下标脚本。

在一个显式的成员表达式中,如果没有这个命名成员的相关声明,表达式则理解为 subscript(dynamicMemberLookup:) 下标脚本,传入一个包含成员名字的字符串字面量作为实际参数。下标脚本的形式参数类型可以是任意遵循 ExpressibleByStringLiteral 协议的类型,它返回的类型可以是任意类型。大多数情况下,下标脚本的形式参数是一个 String 值。举例来说:

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

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

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

dynamicMemberLookup

应用这个特性到类、结构体、枚举或者协议来允许成员在运行时可通过名称检索。类型必须实现 subscript(dynamicMemberLookup:) 下标。

在显式成员表达式中,如果没有相关声明符合命名的名称,表达式会理解并调用类型的 subscript(dynamicMemberLookup:) 下标,传入关于成员的信息作为实际参数。下标可接受一个形式参数,要么是 key path 要么是成员名称;如果你两个下标都实现,那么接受的 key path 实际参数会被使用。

subscript(dynamicMemberLookup:) 的实现可以通过实际参数类型 KeyPath 、 WritableKeyPath 或者 ReferenceWritableKeyPath 来接受 key path。它可以接受成员名称作为实际参数,实际参数必须是遵循 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"

通过 key path 动态成员查找可用于实现支持编译时类型检查的封装。比如说:

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

给结构体或者枚举声明添加此特性来限制你可对此类型能做的变更种类。这个特性仅在库演进模式编译中允许使用。未来版本的库不能通过添加、删除、改变枚举情况或者是改变结构体存储实例属性的顺序来改变声明。这些改变在不冻结的类型中允许,但它们会打乱冻结类型的 ABI 兼容性。

注意

当编译器不在库演进模式时,所有的结构体和枚举都隐式冻结,并且你也不能使用这个特性。

在库演进模式中,与非冻结结构体和枚举交互的代码用一种允许无需重新编译就可以持续运行的方式进行编译,就算将来的库版本增加、删除或者重新排序了类成员没关系。编译器通过使用例如查询运行时信息以及添加中间层等技术实现这一功能。把结构体或者枚举标记为冻结则放弃了这个弹性机制,但获得了更多性能:未来版本的库只能对类做有限更改,但编译器可以对与类成员交互的代码进行更多优化。

冻结类,冻结结构体中的存储属性类型,以及枚举情况的关联值必须是 public 或者标记有 usableFromInline 特性。冻结结构体的属性不能添加属性观察者,另外如同 inlinable 小节中讨论的那样,给存储实例属性添加初始值的表达式必须遵守与行内函数相同的限制。

冻结枚举的 switch 语句不需要 default 情况,如同在将来的枚举中 switch 中说的那样,当你在 switch 冻结枚举时包括 default 或者 @unknown default 的情况,就会看到一个警告,因为代码永远不会被执行。

GKInspectable

用这个特性可以把一个自定义的 GameplayKit 组件属性显示到 SpriteKit 编辑器界面中。使用这个特性也就隐式地使用了 objc 特性

inlinable

给函数、方法、计算属性、下标脚本、便捷初始化器或者反初始化器的声明中应用这个特性来暴露这个声明的实现作为模块的公开接口。这就允许编译器在调用时替换行内符号为这个符号具体实现的拷贝。

这个特性不能应用在内嵌函数或者 fileprivate  和 private  声明中。定义在行内函数内的行数和闭包会隐式地允许行内,尽管它们没有被标记这个特性。

nonobjc

把这个特性应用到一个方法,属性,下标,或者初始化器的声明中,废除一个隐式 objc 特性 。尽管有可能在 Objective-C 中表示一个声明, nonobjc 特性告诉编译器,使其声明在 Objective-C 代码中不可用。

给扩展使用这个特性与对扩展中的所有不显式地用 objc 特性标记的成员使用是一样的效果。

对于一个标为 objc 特性的类中桥接的方法,你可以使用 nonobjc 特性解决其循环性,并且允许重载标为 objc 特性的类中的方法和初始化器。

一个标记为 nonobjc 特性的方法不能重写标为 objc 特性的方法。但是,一个标记为 objc 特性的方法可以重写一个标为 nonobjc 特性的方法。同样,一个标为 nonobjc 特性的方法不能满足一个标为 objc 特性方法的协议需求。

NSApplicationMain

将这个特性应用于一个类中,来指明它是应用的委托。用这个特性等同于调用 NSApplicationMain(_:_:) 函数。

如果你使用这个特性,需要提供一个 main.swift 文件,在其代码的最上层调用如下 NSApplicationMain(_:_:) 函数:

import AppKit
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

NSCopying

这个特性用于一个类的可变存储属性中。这个特性让属性值(由 copyWithZone(_:) 方法返回,而不是属性本身的值)的拷贝合成属性的setter。属性的类型必须遵循 NSCopying 协议。

从某种程度上来说, NSCopying 特性的行为类似 Objective-C 中的 copy 属性特性。

NSManaged

把这个特性应用于一个继承自 NSManagedObject 的类的实例方法或可变存储属性中,以指明 Core Data 会在运行时根据相关的实体描述动态提供它的实现。对于标记为 NSManaged 特性的属性,Core Data 还会在运行时提供存储。用这个特性还隐含 objc 特性。

objc

把这个特性用到任何可以在 Objective-C 中表示的声明上——例如,非内嵌类,协议,非泛型枚举(原始值类型只能是整数),类和协议的属性、方法(包括 setter 和 getter ),初始化器,反初始化器,下标。 objc 特性告诉编译器,这个声明在 Objective-C 代码中是可用的。

给扩展应用这个特性与为这个扩展中所有不显式标记为 nonobjc 特性的成员应用是一样的效果。

用 objc 特性标记的类必须继承自一个 Objective-C 中定义的类。如果你把 objc 用到类或协议中,它会隐式地应用于该类或协议中 Objective-C 兼容的成员上。如果一个类继承自另一个带 objc 特性标记或 Objective-C 中定义的类,编译器也会隐式地给这个类添加 objc 特性。标记为 objc 特性的协议不能继承自非 objc 特性的协议。

objc 特性同样会在下面的情况中隐式地添加:

  • 声明是子类的重写,并且父类的声明有 objc 特性;
  • 声明满足的需求来自一个拥有 objc 特性的协议;
  • 声明有 IBAction , IBOutlet , IBDesignable , IBInspectable , NSManaged , 或者 GKInspectable 特性。

如果你在一个枚举中使用 objc 特性,枚举名和每个成员名串联起来,作为枚举成员暴露给 Objective-C 代码。成员名首字母大写。例如,一个 Swift  Planet 枚举成员叫做 venus ,它作为一个叫 PlanetVenus 的成员暴露到 Objective-C 代码中。

objc 特性可以接受一个特性实际参数,由一个标识符组成。当你想在 Objective-C 中为 objc 特性标记的实体暴露一个不同的名字时,用这个特性。你可以把这个实际参数用在命名类,枚举,枚举成员,协议,方法,getter,setter,初始化器。下面的例子把 ExampleClass 中 enabled 属性的 getter 作为 isEnabled 暴露给 Objective-C 代码,而不仅仅是属性本身的名字。

@objc
class ExampleClass: NSObject {
    var enabled: Bool {
        @objc(isEnabled) get {
            // Return the appropriate value
        }
    }
}

objcMembers

给任何可以拥有 objc 特性的类声明添加这个特性。 objc 特性会隐式地添加到类的 Objective-C 兼容成员、扩展、子类以及它们所有的扩展。

大多数代码应该使用 objc 特性,以今暴露需要的声明。如果泥需要暴露很多声明,你可以用一个带有 objc 特性的扩展来给它们打组。这些特性对于那些与 Objective-C 运行时频繁沟通的库来说特别方便。在不需要的时候添加 objc 特性会增加你的二进制体积并且对性能有不利的影响。

requires_stored_property_inits

给类声明应用这个特性来要求所有的存储属性都必须在声明时提供默认值。这个属性会从所有继承 NSManagedObject 的类中推断出来。

testable

用这个特性 import 那些编译时开启了测试功能的模块中的声明,就像他们被声明为 public 访问级别一样去访问任何标记为 internal 访问级别的实体。测试还可以访问那些标记为 internal 或 public 访问级别的类和类成员,就像他们被声明为 open 访问级别一样。

UIApplicationMain

给一个类用这个特性,以指出它是应用的委托。用这个特性等同于用调用 UIApplicationMain 函数并把这个类名作为委托类的名字传进函数中。

如果你不用这个特性,需要提供一个 main.swift 文件,在其代码的最上层调用 UIApplicationMain(_:_:_:) 函数。例如,如果你的app用了一个 UIApplication 的自定义子类作为它的主要类,调用 UIApplicationMain(_:_:_:) 函数,而不是使用这个特性。

usableFromInline

给函数、方法、计算属性、下标脚本、初始化器或者反初始化器声明添加这个属性来允许符号用于定义在同一个模块作为声明的行内代码里。声明必须拥有 internal 访问级别修饰符。

类似 public 访问级别修饰符,这个特性暴露声明作为模块的公开接口。与 public 不同的是,编译器不能在模块外的代码中引用 usableFromInline 标记的声明,尽管声明的符号已经暴露出来。总之,模块外的代码依旧能通过运行时行为与声明的符号进行沟通。

用 inlinable 标记的声明会隐式地在行内代码可用。尽管不论是 inlinable 还是 usableFromInline 都能应用于 internal 声明,但你同时应用这两个特性是错误的。

warn_unqualified_access

给顶级函数、实例方法或者类和静态方法应用这个特性来在函数或者方法不带前置修饰使用时触发警告,比如模块名、类型名或者实例变量和常量。使用这个特性来降低同一生效范围内相同函数名造成的歧义。

比如说,Swift 标准库包含了顶级函数min(_:_:)和包含可比元素的序列的方法min()。序列方法使用 warn_unqualified_access 特性声明以便于在 Sequence 扩展中避免同时使用两者出现困惑。

通过 Interface Builder 使用声明特性

Interface Builder 特性是用于 Interface Builder 和 Xcode 同步的声明特性。Swift 提供了下列 Interface Builde r特性: IBAction , IBOutlet , IBDesignable ,和 IBInspectable 。这些特性概念上和 Objective-C 中的相同。

你可以把 IBOutlet 和 IBInspectable 特性应用于一个类的属性声明中。把 IBAction 特性用于一个类的方法声明,把 IBDesignable 特性用于类的声明。

IBAction 和 IBOutlet 特性都隐含 objc 特性。

类型特性

类型特性只能用于类型中。

autoclosure

这个特性用于,通过自动包装没有实际参数的表达式来延迟对表达式的求值。这个特性用于一个方法或函数声明的形式参数类型中,该形式参数是一个函数类型,这个函数类型不接受实际参数并且返回一个表达式的类型的值。有关如何使用 autoclosure 特性的示例,参见自动闭包函数类型

convention

把这个特性应用于一个函数的类型以指明它的调用约定。

convention 特性总是和下列特性实际参数中的一个同时出现:

  • swift 实际参数用于指明一个 Swift 函数引用。在 Swift 中,这是函数值的标准调用约定。
  • block 实际参数用于指明一个 Objective-C 兼容的闭包引用。函数值表示为对闭包对象的一个引用,它是一个兼容 id 的、在其中嵌入它的调用函数的 Objective-C 对象。调用函数用 C 调用约定。
  • c 实际参数用于指明一个 C 函数引用。函数值不携带上下文并且使用 C 调用确定

除了少数例外,当需要任何其他调用约定的函数时,可以使用任何调用约定的函数。非泛型全局函数,和局部函数或不捕获任何局部变量的闭包,可以转换为 C 调用约定。其他 Swift 函数不能转换为 C 调用约定。一个带有 Objective-C 闭包调用约束的函数不能转换为 C 调用约定

escaping

将这个特性应用到一个方法或函数声明的形式参数类型中,以指明可以存储该形式参数值用于稍后执行。这意味着允许那个值超过调用它的范围而存在。 escaping 类型特性的函数类型形式参数需要为属性或方法显式使用 self. 。有关如何使用 explicit 特性,参见逃逸闭包

Switch 情况特性

你只能给 switch case 添加 switch 情况特性。

unknown

给 switch 的 case 应用这个特性来表示在代码编译后这个情况不应被枚举中已知的任何情况匹配。如何使用 unknown 属性,见在未来枚举情况中使用 switch。

 

特性的语法

attribute → @ attribute-name attribute-argument-clauseopt

attribute-name → identifier

attribute-argument-clause → (balanced-tokensopt)

attributes → attribute­attributesopt

balanced-tokens → balanced-token ­balanced-tokens­opt

balanced-token → @ (­balanced-tokens­opt­)

balanced-token  balanced-tokens­opt­

balanced-token  balanced-tokens­opt­}

balanced-token → Any identifier, keyword, literal, or operator

balanced-token  Any punctuation except , or }­

 

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文