十五.可选协议
1. Swift还支持在协议中定义定义可选成员要求(包括属性、方法、下标,不包括构造器)----是可选成员要求,不是可选成员。可选成员要求的意思是:该协议的实现类型既可以实现这些协议成员,也可以不实现这些协议成员。
2. 只要在协议成员前添加关键optional关键字即可定义可选协议。
3. 由于协议的实现者可以不实现可选协议的要求,因此程序面向对象编程时,调用可选协议的可选成员时,推荐使用可选链(需要在属性、方法、下标后添加问号)调用,这样可以避免当该成员没有被实现时导致的运行时错误,调用可选成员的返回值总是可选值。
4.由于可选协议主要是为了保持与Objective-C协议的兼容性,因此可选协议必须添加@objc修饰----@objc用于声明类、协议可以被Objective-C调用。即使该协议不打算被Objective-C调用,也必须添加@objc修饰。不仅如此,可选协议 只能 被类实现,不能被枚举、结构体实现,所以,可选协议一定是唯类协议。
5. 举个栗子:
@objc protocol MyProtocol
{
//定义可选属性
optional var status : String {get}
//定义可选方法
optional func test(val : Int)
//定义可选下标
optional subscorpt(ids : Int) -> {get}
}
上面定义了一个协议,使用了@objc修饰,因此该协议一定是一个唯类协议,而且该协议可以包含可选成员。
既然MyProtocol协议的所有成员都是可选的,因此可以定义一个空类来实现该协议:
//定义一个空类,实现MyProtocol协议,但并不实现该协议的任何成员
class EmptyClass : MyPtorocol
{}
6. 关联到3中的知识点,在使用可选协议调用可选属性、可选下标时可以不使用可选链----不管程序使用使用可选链,调用可选成员总是返回可选值。 使用可选协议调用可选方法时,Swift要求必须 使用可选链。
十六.输出实例和Printable协议
1. 假设有一个Person,实例为:var peron = Person(),用print(person)输出Person实例后,将看到:PrintInstance.Person,这个结果没有太大的意义。
2. 如果希望输出实例时,能真正看到该实例的内部状态,可以让该实例实例实现Prinable协议,并实现呢协议中的description只读属性。
3. description只读属性是一个非常特殊的属性,它是一个“自我描述”属性,该属性的返回值通常用于实现这样一个功能:当程序直接输出该实例时,系统将输出该实例的“自我描述”信息,用于告诉外界该实例具有的状态信息。
4. 举个栗子:
class Person : Printable
{
var name : String
var age : Int
init(name : String, age : Int)
{
self.name = name
self.age = age
}
//Printable中的只读属性
var description : String
{
return "msg: (\name) and (\age)"
}
}
则下面两个代码的结果完全相同:
print(person)
print(person.description)
十七.使用自定义类型作为字典的key
1. 前面学习字典的时候,总是使用系统的Int、String等作为key。这里将要学习使用自定义类型作为字典的key。
2. 在字典中,不允许出现相同的两个key。只要两个key满足一下两个条件,字典就会认为他们相等:
(1)两个key通过==比较返回true;
(2)两个key的hashValue属性返回相等的整数。
3. 但是自定义类型的实例无法通过==进行比较,自定义类型的实例也没有hashValue属性,为了让自定义类型满足上述两个条件,自定义类型必须进行如下改造:
(1)让自定义类型实现Equatable协议,并重载==比较运算符,是的自定义类型的实例可以通过==进行比较。
(2)让自定义类型实现Hashable协议,并实现协议中的hashValue只读属性。实现hashValue只读属性时,应该和重载的==保持一致。也就是说,当两个实例通过==比较返回true时,两个实例的hashValue也应该相等。----实现Equatable协议,并重载==运算符实际上就是重新定义了一个名为==的函数,方法中的内容由自己提供认为相等的标准。
4. Hashable、Equatable协议都是Swift提供的协议,而且Hashable协议是Equatable的子协议,因此自定义类型只要实现Hashable协议即可。
5. 通常而言,正确地重载==运算符应该满足以下条件:
(1)自反性:对任意x,x==x应该返回true;
(2)对称性:对任意x,y,如果x==y返回true,那么y==x也返回true
(3)传递性:对任意x,y,z,如果x==y返回true,y==z返回true,则x==z一定返回true。
(4)一致性:对任意x和y,如果实例中用于等价比较的关键信息没有改变,那么无论x==y调用多少次,返回的结果都应该保持一致,
6.举个栗子:
class User : Equatable, Printable
{
var name : String
var pwd : String
var age : Int
init(name : String, pwd : String, age : Int)
{
self.name = name
self.pwd = pwd
self.age = age
}
var hashValue : Int
{
//根据name,pwd的hashValue来计算User实例的hashValue
//考虑到数据可能溢出,故此处采用溢出运算符
return name.hashValue &* 31 &+ pwd.hashValue
}
var description : String
{
return "User{\(name),\(pwd),\(age)}"
}
//重载==运算符
func == (lhs : User, rhs : Usr)
{
return lhs.name == rhs.name && lhs.pwd == rhs.pwd
}
}
上面的User类满足了上面3中所说的两个条件,因此这个类的实例可以作为字典的key。