注册`UserDefaults.didChangeNotification`的技巧
UserDefaults.didChangeNotification应该在PreferencesViewController里进行注册,而不是在需要变化的ViewController里。因为如果是在后者注册,程序运行时,可能会出现意想不到的异常。即用户在没有打开设置的情况下,设置变了,而造成你的程序的异常。
今天下午排查了很久,最后发现是这个原因。
UserDefaults.didChangeNotification应该在PreferencesViewController里进行注册,而不是在需要变化的ViewController里。因为如果是在后者注册,程序运行时,可能会出现意想不到的异常。即用户在没有打开设置的情况下,设置变了,而造成你的程序的异常。
今天下午排查了很久,最后发现是这个原因。
与预期的不同,URL即使path相同,URL也可能是不同的。因此,应避免使用init(fileURLWithPath: String, isDirectory: Bool, relativeTo: URL?),使用appendingPathComponent(_:isDirectory:)作为替代。
let url = URL(fileURLWithPath: "foo/bar", isDirectory: false)
let baseURL = url.deletingLastPathComponent()
let newURL = URL(fileURLWithPath: "bar", isDirectory: false, relativeTo: baseURL)
let testURL = baseURL.appendingPathComponent("bar")
print(url == newURL) // prints false
print(url.path == newURL.path) // prints true
print(url == testURL) // prints true
print(url.path == testURL.path) // prints true
Hashable是最常见的协议之一。也许是由于它太常见了。以至于好多人都没法正确的实现它。下面总结一下实现它需要的原则。
基本原则只有两条:
Hashable继承了Equatable。因此,要实现Hashable必须同时实现Equatable。a与b相等时,它们的hashValue相等。反之不成立,即hashValue相等的两个相同类型的a与b,不一定相等。class Fruit:Hashable {
let name:String
var hashValue: Int {
return name.hashValue
}
init(name:String) {
self.name = name
}
static func ==(lhs: Fruit, rhs: Fruit) -> Bool {
return lhs.name == rhs.name
}
}
注意
var hashValue: Int只要求返回值,因此如何实现都可以,设置只返回固定的值,比如var hashValue: Int { return 10 }也是可以的。
不能使用hashValue来判断是否相等。
上面的代码在涉及到子类时,不同的人会有不同的理解。比如:
class Apple:Fruit {
override var hashValue: Int {
return super.hashValue - "apple".characters.count
}
}
class Banana:Fruit {
override var hashValue: Int {
return super.hashValue - "banana".characters.count
}
}
let a = Apple(name: "")
let b = Banana(name: "")
print(a == b) // true
print(a.hashValue) // prints 4799450059485596700
print(b.hashValue) // prints 4799450059485596699
上面的代码,print(a == b) // true,但是hashValue却不相等。这违背了上面提到的基本原则2。这段代码需要修改。但是如何修改,不同的人有不同的看法。
我认为最好的办法就是优先判断类型。因为,类型不同的类,就不应该相等。比如一个苹果就不应该和一个香蕉相等。
class Fruit:Hashable {
let name:String
var hashValue: Int {
return name.hashValue
}
init(name:String) {
self.name = name
}
static func ==(lhs: Fruit, rhs: Fruit) -> Bool {
return type(of:lhs) == type(of:rhs) && lhs.name == rhs.name
}
}
class Apple:Fruit {
override var hashValue: Int {
return super.hashValue - "apple".characters.count
}
}
class Banana:Fruit {
override var hashValue: Int {
return super.hashValue - "banana".characters.count
}
}
let a = Apple(name: "")
let b = Banana(name: "")
print(a == b) // false
print(a.hashValue) // prints 4799450059485596700
print(b.hashValue) // prints 4799450059485596699
即在非final的类的==里,总是先比较类型,类型相同的,再毕竟其它。这么做之后,就永远不会发生一个苹果等于一个香蕉的笑话了。但是这里还有一个问题,我们没有编写子类的==。这里暂时没有问题,但是如果遇到有其它属性的子类,就可能出现问题。
class Orange:Fruit {
let weight:Double
override var hashValue: Int {
return super.hashValue - "orange".characters.count + Int(weight)
}
init(name: String, weight: Double) {
self.weight = weight
super.init(name: name)
}
}
let o1 = Orange(name: "", weight: 0.4)
let o2 = Orange(name: "", weight: 1.2)
print(o1 == o2) // true
print(o1.hashValue) // prints 4799450059485596699
print(o2.hashValue) // prints 4799450059485596700
由于Orange没有重新定义==,比较时,直接调用了Fruit的==,导致了不符合基本原则2。补救办法就是在Orange重新定义==。
class Orange:Fruit {
let weight:Double
override var hashValue: Int {
return super.hashValue - "orange".characters.count + Int(weight)
}
static func ==(lhs: Orange, rhs: Orange) -> Bool {
return type(of:lhs) == type(of:rhs) && lhs.name == rhs.name && lhs.weight == rhs.weight
}
init(name: String, weight: Double) {
self.weight = weight
super.init(name: name)
}
}
let o1 = Orange(name: "", weight: 0.4)
let o2 = Orange(name: "", weight: 1.2)
print(o1 == o2) // false
print(o1.hashValue) // prints 4799450059485596699
print(o2.hashValue) // prints 4799450059485596700
hashValue,仅重新定义==一些特殊的情况下,也许你可以不重写hashValue,而仅重新定义==。
import Foundation
import Cocoa
class Fruit:Hashable {
let name:String
var hashValue: Int {
return name.hashValue
}
init(name:String) {
self.name = name
}
static func ==(lhs: Fruit, rhs: Fruit) -> Bool {
return lhs.name == rhs.name
}
}
class Apple:Fruit {
}
class ColoredApple:Apple {
let color:NSColor
init(name:String, color:NSColor) {
self.color = color
super.init(name: name)
}
static func ==(lhs: ColoredApple, rhs: ColoredApple) -> Bool {
return lhs.name == rhs.name && lhs.color == rhs.color
}
}
let greenApple = ColoredApple(name: "green apple", color: .green)
let apple = Apple(name: "green apple")
print(greenApple == apple) // true
print(greenApple.hashValue) // -2580839601755588497
print(apple.hashValue) //-2580839601755588497
var set:Set<Apple> = [greenApple, apple]
print(set.count) // 1
var d:Dictionary<Apple, Int> = [greenApple:10]
// eat one
d.updateValue(d[greenApple]! - 1, forKey: apple)
print(d[greenApple]!) // 9
这种做法不常见,一旦选择了不重写hashValue,就必须在该基类的所有子类都保持一致。
注意
由于Set和Dictionary使用
hashValue进行判断,放入其中的元素的hashValue在没有从其中移出时,不能改变,否则会出现问题。
可以看到apple和greenApple因为hashValue相同,它们在Set和Dictionary里被当做是同一元素/键值。
在讲解Self之前,需要先简短介绍一下type(of:)的用法。
type(of:)从Xcode 8 beta 6开始,dynamicType被替换为了type(of:)。(SE-0096)
type(of:)的功能是获得实例在运行时(runtime)的元类型(meta type)。这个在之前的Swift里,被称作是dynamicType,与静态的类型进行区别。
SelfSelf是一个特殊的类型。它是self对应的类型。而self是动态类型。比如:
class Foo {
func printType() {
print(type(of:self))
}
func printSelf() {
print(self)
}
}
class Bar:Foo {
}
let foo = Foo()
foo.printType() // Foo
foo.printSelf() // Foo
let bar = Bar()
bar.printType() // Bar
bar.printSelf() // Bar
var test = Foo()
test.printType() // Foo
test.printSelf() // Foo
test = Bar()
test.printType() // Bar
test.printSelf() // Bar
可以看到,self始终对应的是实际类型,无论变量的类型是Foo还是Bar。
由于这个特性的存在,这使得Self无法从静态类型进行转换,而只能通过self来生成。即:
class Foo {
func bar() -> Self {
return self
}
}
class Foo1 {
func bar1() -> Self {
return type(of:self).init()
}
required init() { }
}
注意
Foo1中,有一个
required init(),这是type(of:self).init()必须的。
Self的使用有以下方式:
这是最常见的使用方式。
protocol Foo {
func instance() -> Self
}
class Bar: Foo {
func instance() -> Self {
return self // Declaration: let `self`: Self
}
func other() {
let i = self // Declaration: let `self`: Bar
}
}
class otherBar:Foo {
func instance() -> Self {
return type(of:self).init()
}
required init() { }
}
####注意
Bar中的实现协议的函数里,self是Self类型。而非Self返回值的函数里,self是所在类的类型。
可以使用type(of:self).init(),获得一个新的Self实例。此时,类中必须要有一个required init()。
Self作为函数的返回值的类型时,可以直接写在类中,而不必非要有协议。
下面2和3中的情形,使用associateType的效果,要好于Self。因此,1是Self的最常用的方式。
protocol Foo {
func bar(b:Self)
}
class Bar:Foo {
func bar(b:Bar) {
print(b)
}
}
####注意
Bar中的func bar(b:Bar)是Bar而不是Self。但是这满足协议。这也符合类的多态,即Bar的子类可以调用这个函数。
protocol Foo {
var bar:Self { get set }
}
final class Bar:Foo {
var bar: Bar
init(b:Bar) {
self.bar = b
}
}
####注意
在协议的属性里使用Self时,类必须时final类。
###参考文献:
以下参考文献的一些代码针对的是3.0版本之前的Swift。部分内容需要修正才能运行。但基本原理是相同的。
Types and meta types in swift
Swift中你应该知道的一些有用的tips
Generic Protocols & Their Shortcomings 这篇难度较高,慎看。
Self in Protocol and class method
我们知道Xcode 8 beta 同时提供了Swift 3.0 和Swift 2.3来供我们选择。并且我们还知道,可以在Xcode的偏好里,选择对应的工具,如图1-1所示。
图1-1 在Xcode中选择命令行工具

此时,在终端中查看swift的版本,会是3.0的,如图1-2所示。
代码 1-2 终端查看Swift版本
~ zhaoxin$ swiftc -v
Apple Swift version 3.0 (swiftlang-800.0.42.1 clang-800.0.36.1)
Target: x86_64-apple-macosx10.9
~ zhaoxin$ xcrun swiftc -v
Apple Swift version 3.0 (swiftlang-800.0.42.1 clang-800.0.36.1)
Target: x86_64-apple-macosx10.9
Swift.org官方给出的建议是使用添加环境变量的方式。
$ export PATH=/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:"${PATH}"
但是这样做只会影响swiftc,不会影响xcrun swiftc。
~ zhaoxin$ export PATH=/Applications/Xcode-beta.app/Contents/Developer/Toolchains/Swift_2.3.xctoolchain/usr/bin:"${PATH}"
$ swiftc -v
Apple Swift version 2.3 (swiftlang-800.10.11 clang-800.0.36)
Target: x86_64-apple-macosx10.9
$ xcrun swiftc -v
Apple Swift version 3.0 (swiftlang-800.0.42.1 clang-800.0.36.1)
Target: x86_64-apple-macosx10.9
阅读xcrun的帮助,我们可以发现:
--toolchain <name>
Specifies which toolchain to use to perform the lookup. If no
--toolchain argument is provided, then the toolchain to use will
be taken from the TOOLCHAINS environment variable, if present.
OK