协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。
如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。
协议总是用 var
关键字来声明变量属性,在类型声明后加上 { set get }
来表示属性是可读可写的,可读属性则用 { get }
来表示:
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在协议中定义类型属性时,总是使用 static
关键字作为前缀。当类类型遵循协议时,除了 static
关键字,还可以使用 class
关键字来声明类型属性:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
protocol FullyNamed {
var fullName: String { get }
}
FullyNamed
协议除了要求遵循协议的类型提供 fullName
属性外,并没有其他特别的要求。这个协议表示,任何遵循 FullyNamed
的类型,都必须有一个可读的 String
类型的实例属性 fullName
。
下面是一个遵循 FullyNamed
协议的简单结构体:
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 为 "John Appleseed"
这个例子中定义了一个叫做 Person
的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 FullyNamed
协议。
Person
结构体的每一个实例都有一个 String
类型的存储型属性 fullName
。这正好满足了 FullyNamed
协议的要求,也就意味着 Person
结构体正确地遵循了协议。(如果协议要求未被完全满足,在编译时会报错。)
下面是一个更为复杂的类,它采纳并遵循了 FullyNamed
协议:
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName 为 "USS Enterprise"
Starship
类把 fullName
作为只读的计算属性来实现。每一个 Starship
类的实例都有一个名为 name
的非可选属性和一个名为 prefix
的可选属性。 当 prefix
存在时,计算属性 fullName
会将 prefix
插入到 name
之前,从而得到一个带有 prefix
的 fullName
。