Swift之旅_Language Guide4

接着看下面的几个小节,话说剩下的几个小姐也忒特么长了吧~


Protocols

这一小节主要讲解的是Swift中协议,超长的一节。

  • Property Requirements

这里有点难翻译,直接说吧。这里说的是协议中定义属性的时候,遵循协议的类型也要在其内部定义该属性,并且如果协议中属性是gettable和settable的,则类型内属性必须是var的(不能是constant或者只读),如果协议中的属性是gettable的,则类型内可以是任何类型。看一下代码吧:

protocol FullyNamed {
    var fullName: String { set get }
}
struct Person: FullyNamed {
    var fullName: String
}
struct Animal: FullyNamed {
    //  编译错误: Type 'Animal' does not conform to protocol 'FullyNamed'
    //  必须是val
    let fullName: String
}
protocol FullyNamed {
    var fullName: String { get }
}
struct Person: FullyNamed {
    var fullName: String
}
struct Animal: FullyNamed {
    //  没有错误
    let fullName: String
}
  • Initializer Requirements

协议也可以定义构造器,在遵循协议的类型中可以将这个构造器实现成指定构造器或者便利构造器:

protocol SomeProtocol {
    init(someParameter: Int)
}
class OtherClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}
class SomeClass: SomeProtocol {
    required convenience init(someParameter: Int) {
        // initializer implementation goes here
        self.init(test: 0)
    }
    init(test: Int) {
    }
}
  • Class-Only Protocols

可以在协议后面添加上AnyObject表示这个协议只能被类遵循

class Person: SomeClassOnlyProtocol {
}
struct Size: SomeClassOnlyProtocol {
    //报错:Non-class type 'Size' cannot conform to class protocol 'SomeClassOnlyProtocol'
}
protocol SomeClassOnlyProtocol: AnyObject {
}
  • Checking for Protocol Conformance

也可以用is和as去操作协议

class Person: SomeClassOnlyProtocol {
    func test() {
        print("Hello")
    }
}
protocol SomeClassOnlyProtocol: AnyObject {
    func test()
}
let p = Person()
let p1: Any = p
print(p1 is SomeClassOnlyProtocol)
//  打印 true
(p1 as? SomeClassOnlyProtocol)?.test()
//  打印 Hello
  • Optional Protocol Requirements

和oc一样,Swift协议中的属性或者方法也是可以修饰成可以不实现的,但是Swift中比较奇怪一点。首先需要在协议前面加上@objc,然后在可以选择不实现的方法前面加上@objc optional,@objc在文档中解释是为了能够在OC中调用,optional就是可选的意思嘛(ps:加上@objc之后该协议只能被Objective-C的类遵循,无法被结构体和枚举遵循)

class Person: SomeProtocol {
}
@objc protocol SomeProtocol {
    @objc optional func test()
}
struct test: SomeProtocol {
    //错误: Non-class type 'test' cannot conform to class protocol 'SomeProtocol'
}
  • Protocol Extensions

也可以给协议扩展方法,不过这里不只是声明方法,需要实现

class Person: SomeProtocol {
}
@objc protocol SomeProtocol {
}
extension SomeProtocol {
    func test() {
        print("hello")
    }
}
let p = Person()
p.test()
//打印 hello

Generics

这一小节主要讲解的是Swift中泛型,依旧是长长的一节。

Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code. In fact, you’ve been using generics throughout the Language Guide, even if you didn’t realize it. For example, Swift’s Array and Dictionary types are both generic collections. You can create an array that holds Int values, or an array that holds String values, or indeed an array for any other type that can be created in Swift. Similarly, you can create a dictionary to store values of any specified type, and there are no limitations on what that type can be.

貌似泛型还是Swift的一大特性,并且SWIFT标准库的大部分都是用泛型代码构建的。数组字典其实都是泛型集合。

  • Generic Functions

来看看泛型的简单应用:
方法中的应用

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"

类型中的应用

class Stack<Value> {
    var items = [Value]()
    subscript(index: Int) -> Value {
        return items[index]
    }
    func push(item: Value) {
        items.append(item)
    }
    func pop() {
        items.removeLast()
    }
}
let s = Stack<String>()
s.push(item: "Hello")
s.push(item: " ")
s.push(item: "World")
s.push(item: "!")
print(s[3])
  • Extending a Generic Type

给一个泛型扩展

extension Stack {
    var top: Value? {
        return items.isEmpty ? nil : items[items.count-1]
    }
}
  • Type Constraint Syntax

也可以对一个泛型进行约束:

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}

这个函数中T必须是SomeClass的子类,U必须遵循SomeProtocol协议

  • Associated Types

关联类型。哇,这段话真的是很难翻译啊。百度好多都是直接抄文档的啥解释也没有,看了很久才搞懂一点。associatedtype修饰的东西可以代指一个类型,文档中也有例子,不过我觉得还是简化一下看的更明白:

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
}
struct Stack<Element>: Container {
    var items = [Element]()
    mutating func append(_ item: Element) {
        items.append(item)
    }
}
struct Queue<Value>: Container {
    var items = [Value]()
    mutating func append(_ item: Value) {
        items.append(item)
    }
}

这里associatedtype后面跟的其实相当于是一个泛型,接着Stack和Queue遵行Container协议,在Stack中Item代指Element类型,而在Queue中代指Value类型。

  • Adding Constraints to an Associated Type
protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
  • Extensions with a Generic Where Clause

extension中使用泛型限制条款

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

这里的Stack集合中的元素必须是遵循Equatable协议的,也就是说只有当Stack集合中的元素遵循了Equatable协议,该集合才能获得isTop这个扩展方法。后面也有提到关联类型以及下标中都可以这样使用。


Automatic Reference Counting

这一小节主要讲解的是Swift中的内存管理。和oc一样,swift也是自动管理内存,但是仍然存在一些情况,需要特殊处理。

  • Strong Reference Cycles Between Class Instances

类实例之间的强引用循环,这里介绍了一个循环引用的列子,和之前oc碰到的情况很像,先看一段代码:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = John
john = nil
unit4A = nil

结果运行的时候2个实例对象的析构函数都不会被调用,说明2者都没有被成功释放。官方文档中3张图很形象的描述了这个过程。
首先john和unit4A指针分别指向一个实例对象
接着john!.apartment指针指向unit4A所指向的实例对象,unit4A!.tenant指向john所指向的实例对象
john和unit4A指向nil,但是因为2个实例对象之间都还存在强引用,所以内存并没有成功释放。
  • Resolving Strong Reference Cycles Between Class Instances

有两种解决方法:

1.Weak References

和oc的弱引用几乎一样,需要注意的是因为弱引用在程序运行过程中可能会被设置为nil,所以weak修饰的通常是变量而不是常量。上面代码需要改成如下:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}
class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = John
john = nil
unit4A = nil

对于这段代码文档中也有图片形容.
释放之前的内存图
john = nil之后的内存图
unit4A = nil之后的内存图

不过我试过如果John不设置成nil,unit4A也是不能正常释放,并且john!.apartment也不为nil,依旧指向着那块内存空间。

2.Unowned References

Like a weak reference, an unowned reference does not keep a strong hold on the instance it refers to. Unlike a weak reference, however, an unowned reference is used when the other instance has the same lifetime or a longer lifetime. You indicate an unowned reference by placing the unowned keyword before a property or variable declaration.
An unowned reference is expected to always have a value. As a result, ARC never sets an unowned reference’s value to nil, which means that unowned references are defined using nonoptional types.

Unowned和弱引用一样的地方是对实例对象没有保持一个强引用,不同的是Unowned是用在当其他实例对象拥有相同或者更长的生命周期的时候。
unowned修饰的实例对象一直会有一个值,所以ARC从不将它设置成nil,这意味着unowned修饰的实例对象通常被定义成非可选类型。
使用如下:

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: John!)
john = nil

对应的内存图:
john = nil之前
john = nil之后

文档上没有第三张图了,不过这里也容易看出下面的内存情况,因为Customer instance没有string指针指着了,所以会被释放掉,接着CreditCard instance也没有strong指针指着,也会被释放。

  • Strong Reference Cycles for Closures

在闭包中的强引用,oc中也存在这样的情况,当你没做任何处理直接在闭包中通过self.someProperty去访问属性或者 self.someMethod()去调用方法就会对self进行一次"捕获",从而造成循环引用,使self不能正常释放。
错误代码如下:

class HTMLElement {
    let name: String
    let text: String?
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"
paragraph = nil

对应的内存图:

文档中没有给出paragraph = nil的内存图,不过paragraph = nil之后,闭包仍然引用着实例对象,所以这个实例对象不能成功释放,这里也可以看出闭包是引用类型的。

  • Resolving Strong Reference Cycles for Closures

解决闭包的强引用

class HTMLElement {
    let name: String
    let text: String?
    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"
paragraph = nil
// Prints "p is being deinitialized"

paragraph = nil之前的内存图如下:

这样子就能解决闭包强引用的问题了。

推荐阅读更多精彩内容