1,类型方法:类型本身调用的方法;
通过在 func关键字之前使用 static关键字来明确一个类型方法。类同样可以使用 class关键字来允许子类重写父类对类型方法的实现。
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
2,Dictionary 类型使用可选的下标类型来建模不是所有键都会有值的事实,并且提供了一种通过给键赋值为 nil 来删除对应键的值的方法。
3,Swift 会通过检查重写定义都有一个与之匹配的父类定义来确保你的重写是正确的。类也可以向继承的属性添加属性观察器,以便在属性的值改变时得到通知。可以添加任何属性监视到属性中,不管它是被定义为存储还是计算属性。
4,访问父类的方法、属性和下标脚本;你可以通过使用 super 前缀访问父类的方法、属性或下标脚本,这是合适的:
一个命名为 someMethod() 的重写方法可以通过 super.someMethod() 在重写方法的实现中调用父类版本的 someMethod() 方法;
一个命名为 someProperty 的重写属性可以通过 super.someProperty 在重写的 getter 或 setter 实现中访问父类版本的 someProperty 属性;
一个命名为 someIndex 的重写下标脚本可以使用 super[someIndex] 在重写的下标脚本实现中访问父类版本中相同的下标脚本。
5,如果你提供了一个setter作为属性重写的一部分,你也就必须为重写提供一个getter。如果你不想在重写getter时修改继承属性的值,那么你可以简单通过从getter返回 super.someProperty 来传递继承的值, someProperty 就是你重写的那个属性的名字。
6,你不能给继承而来的常量存储属性或者只读的计算属性添加属性观察器。这些属性的值不能被设置,所以提供 willSet 或 didSet 实现作为重写的一部分也是不合适的。也要注意你不能为同一个属性同时提供重写的setter和重写的属性观察器。如果你想要监听属性值的改变,并且你已经为那个属性提供了一个自定义的setter,那么你从自定义的setter里就可以监听任意值的改变。
7,阻止重写:
你可以通过标记为终点来阻止一个方法、属性或者下标脚本被重写。通过在方法、属性或者下标脚本的关键字前写 final 修饰符(比如 final var , final func , final class func , fianl subscript )。你可以通过在类定义中在 class 关键字前面写 final 修饰符( final class )[表情]标记一整个类为终点。任何想要从终点类创建子类的行为都会被报告一个编译时错误。
8,struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
简单方式:
struct Fahrenheit {
var temperature = 32.0
}
9,值类型(结构体和枚举)不支持继承,所以他它们的初始化器委托的过程相当简单,因为它们只能提供它们自己为另一个初始化器委托;
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
10,类类型的初始化器委托:
为了简化指定和便捷初始化器之间的调用关系,Swift 在初始化器之间的委托调用有下面的三个规则:
1,指定初始化器必须从它的直系父类调用指定初始化器。
2,便捷初始化器必须从相同的类里调用另一个初始化器。
3,便捷初始化器最终必须调用一个指定初始化器。
两段式初始化:
在第一个阶段,每一个存储属性被引入类为分配了一个初始值。一旦每个存储属性的初始状态被确定,
第二个阶段就开始了,每个类都有机会在新的实例准备使用之前来定制它的存储属性。
两段式初始化过程可以防止属性值在初始化之前被访问,还可以防止属性值被另一个初始化器意外地赋予不同的值。
11,Swift 的两段式初始化过程与 Objective-C 的初始化类似。主要的不同点是在第一阶段,Objective-C 为每一个属性分配零或空值(例如 0 或 nil )。Swift 的初始化流程更加灵活,它允许你设置自定义的初始值,并可以自如应对 0 或 nil 不为合法值的情况。
12,指定初始化器是初始化开始并持续初始化过程到父类链的“传送”点。一个对象的内存只有在其所有储存型属性确定之后才能完全初始化。
Swift编译器执行四种有效的安全检查来确保两段式初始化过程能够顺利完成:
一:指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
(一个对象的内存只有在其所有储存型属性确定之后才能完全初始化。
指定初始化器必须保证它自己的属性在它上交委托之前先完成初始化。)
二:指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖。
三:便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖。
四:初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用 self 作为值。
直到第一阶段结束类实例才完全合法。属性只能被读取,方法也只能被调用,直到第一阶段结束的时候,这个类实例才被看做是合法的。
13,阶段 1:指定或便捷初始化器在类中被调用;
为这个类的新实例分配内存。内存还没有被初始化;
这个类的指定初始化器确保所有由此类引入的存储属性都有一个值。现在这些存储属性的内存被初始化了;
指定初始化器上交父类的初始化器为其存储属性执行相同的任务;
这个调用父类初始化器的过程将沿着初始化器链一直向上进行,直到到达初始化器链的最顶部;
一旦达了初始化器链的最顶部,在链顶部的类确保所有的存储属性都有一个值,此实例的内存被认为完全初始化了,此时第一阶段完成。
阶段 2:从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例。初始化器现在能够访问 self 并且可以修改它的属性,调用它的实例方法等等;
最终,链中任何便捷初始化器都有机会定制实例以及使用 slef 。
14,指定初始化器确保所有的子类属性都有值。然后它调用父类的指定初始化器来沿着初始化器链一直往上完成父类的初始化过程。父类的指定初始化器确保所有的父类属性都有值。
15,初始化器的继承和重写:
不像在 Objective-C 中的子类,Swift 的子类不会默认继承父类的初始化器。Swift 的这种机制防止父类的简单初始化器被一个更专用的子类继承并被用来创建一个没有完全或错误初始化的新实例的情况发生。
16,子类可以在初始化时修改继承的变量属性,但是不能修改继承过来的常量属性。
自动初始化器的继承:
假设你为你子类引入的任何新的属性都提供了默认值,请遵守以下2个规则:
规则1
如果你的子类没有定义任何指定初始化器,它会自动继承父类所有的指定初始化器。
规则2
如果你的子类提供了所有父类指定初始化器的实现——要么是通过规则1继承来的,要么通过在定义中提供自定义实现的——那么它自动继承所有的父类便捷初始化器。
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
尽管 RecipeIngredient 提供了 init(name: String) 初始化器作为一个便捷初始化器,然而 RecipeIngredient 类为所有的父类指定初始化器提供了实现。因此, RecipeIngredient 类也自动继承了父类所有的便捷初始化器。
17,可失败初始化器:
为了妥善处理这种可能失败的情况,在类、结构体或枚举中定义一个或多个可失败的初始化器。通过在 init 关键字后面添加问号( init? )来写。
严格来讲,初始化器不会有返回值。相反,它们的角色是确保在初始化结束时, self 能够被正确初始化。虽然你写了 return nil 来触发初始化失败,但是你不能使用 return 关键字来表示初始化成功了。
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
18,为了确保数字类型之间的转换保持值不变,使用 init(exactly:) 初始化器。如果类型转换不能保持值不变,初始化器失败。
示例:let wholeNumber: Double = 12345.0
let pi = 3.14159
if let valueMaintained = Int(exactly: wholeNumber) {
print("\(wholeNumber) conversion to int maintains value")
}
// Prints "12345.0 conversion to int maintains value"
let valueChanged = Int(exactly: pi)
// valueChanged is of type Int?, not Int
if valueChanged == nil {
print("\(pi) conversion to int does not maintain value")
}
// Prints "3.14159 conversion to int does not maintain value"
19,
带有原始值枚举的可失败初始化器:
带有原始值的枚举会自动获得一个可失败初始化器 init?(rawValue:) ,该可失败初始化器接收一个名为 rawValue 的合适的原始值类型形式参数如果找到了匹配的枚举情况就选择其一,或者没有找到匹配的值就触发初始化失败。
enum TemperatureUnit {
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
20,初始化失败的传递:
class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil }
self.quantity = quantity
super.init(name: name)
}
}
21,可失败初始化器 init! :
使用可失败初始化器创建一个隐式展开具有合适类型的可选项实例;
必要初始化器:
在类的初始化器前[表情]添加 required 修饰符来表明所有该类的子类都必须实现该初始化器:
当子类重写父类的必要初始化器时,必须在子类的初始化器前同样添加 required 修饰符以确保当其它类继承该子类时,该初始化器同为必要初始化器。在重写父类的必要初始化器时,不需要添加 override 修饰符。
22,通过闭包和函数来设置属性的默认值:
class SomeClass {
let someProperty: SomeType = {
return someValue
}()
//注意闭包花括号的结尾跟一个没有参数的圆括号。这是告诉 Swift 立即执行闭包。如果你忽略了这对圆括号,你就会把闭包作为值赋给了属性,并且不会返回闭包的值。
}
23,类实例之间的循环强引用(解决实例之间的循环强引用):
解决循环强引用问题,可以通过定义类之间的关系为弱引用( weak )或无主引用( unowned )来代替强引用。Swift 提供了两种办法,弱引用( weak reference )和无主引用( unowned reference )。在 ARC 给弱引用设置 nil 时不会调用属性观察者。
无主引用假定是永远有值的。总是被定义为非可选类型。你可以在声明属性或者变量时,在前面加上关键字 unowned 表示这是一个无主引用。不过 ARC 无法在实例被释放后将无主引用设为 nil ,因为非可选类型的变量不允许被赋值为 nil 。
如果你试图在实例的被释放后访问无主引用,那么你将触发运行时错误。只有在你确保引用会一直引用实例的时候才使用无主引用。
如果你试图访问引用的实例已经被释放了的无主引用,Swift 会确保程序直接崩溃。
24, let number: UInt64; number 属性定义为 UInt64 类型而不是 Int ,以确保 number 属性的存储量在32位和64位系统上都能足够容纳16位的卡号。
25,使用 unowned(unsafe) 来明确使用了一个不安全无主引用。如果你在实例的引用被释放后访问这个不安全无主引用,你的程序就会尝试访问这个实例曾今存在过的内存地址,这就是不安全操作。
定义捕获列表:
捕获列表中的每一项都由 weak 或 unowned 关键字与类实例的引用(如 self )或初始化过的变量(如 delegate = self.delegate! )成对组成。这些项写在方括号中用逗号分开。lazy var someClosure: (Int, String) -> String = { [unowned self, weak delegate = self.delegate!]
(index: Int, stringToProcess: String) -> String in
// closure body goes here
}
如果被捕获的引用永远不会变为 nil ,应该用无主引用而不是弱引用。
26,在 Swift 中有四种方式来处理错误。你可以将来自函数的错误传递给调用函数的代码中,使用 do-catch 语句来处理错误,使用抛出函数传递错误:
func canThrowErrors() throws -> String
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vendingMachine.vend(itemNamed: snackName)
}
为了明确一个函数或者方法可以抛出错误,你要在它的声明当中的形式参数后边写上 throws关键字。使用 throws标记的函数叫做抛出函数。如果它明确了一个返回类型,那么 throws关键字要在返回箭头 ( ->)之前。只有抛出函数可以传递错误。
func vend(itemNamed name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.outOfStock
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
}
使用 Do-Catch 处理错误:(通常使用姿势)
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
你可以在表达式前写 try!来取消错误传递并且把调用放进不会有错误抛出的运行时断言当中。
27,指定清理操作:
使用 defer语句来在代码离开当前代码块前执行语句合集。这个语句允许你在以任何方式离开当前代码块前执行必须要的清理工作——无论是因为抛出了错误还是因为 return或者 break这样的语句。使用 defer语句来保证文件描述符都关闭并且手动指定的内存到被释放。
[表情]defer语句延迟执行直到当前范围退出。这个语句由 defer关键字和需要稍后执行的语句组成。[表情]延迟的操作与其指定的顺序相反执行——就是说,第一个 defer语句中的代码会在第二个中代码执行完毕后执行,以此类推。
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
}
}
}
上面的示例使用 defer语句来保证 open(_:)函数能调用 close(_:)。
28,Swift 中类型转换的实现为 is 和 as 操作符。类型检查:使用类型检查操作符 ( is )来检查一个实例是否属于一个特定的子类。如果实例是该子类类型,类型检查操作符返回 true ,否则返回 false 。
for item in library {
if item is Movie {
} else if item is Song {
}
}
29,Any 和 AnyObject 的类型转换:
AnyObject 可以表示任何类类型的实例。
Any 可以表示任何类型,包括函数类型。
示例:var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
这个 things 数组包含了两个 Int 值、两个 Double 值、一个 String 值、一个 (Double, Double) 的元组。
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case 0 as Double: // 0.0
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called \(movie.name), dir. \(movie.director)")
case let stringConverter as (String) -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}
Any类型表示了任意类型的值,包括可选类型。如果你给显式声明的Any类型使用可选项,Swift 就会发出警告。
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning
things.append(optionalNumber as Any) // No warning
29,Swift 中的扩展可以:
添加计算实例属性和计算类型属性;
定义实例方法和类型方法;
提供新初始化器;
定义下标;
定义和使用新内嵌类型;
使现有的类型遵循某协议;
扩展可以向一个类型添加新的方法,但是[表情]不能重写已有的方法。
30,使用 extension 关键字来声明扩展:
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
上述属性为只读计算属性,为了简洁没有使用 get 关键字。他们都返回 Double 类型的值。
31,扩展可以添加新的计算属性,但是不能添加存储属性,也不能向已有的属性添加属性观察者。
32,(扩展)异变实例方法:
增加了扩展的实例方法仍可以修改(或异变)实例本身。结构体和枚举类型方法在修改 self 或本身的属性时必须标记实例方法为 mutating ,和原本实现的异变方法一样。