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之前的内存图如下:

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容