swift 4.0> 进阶知识点全面梳理(三)

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 ,和原本实现的异变方法一样。

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

推荐阅读更多精彩内容

  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,784评论 1 10
  • 123.继承 一个类可以从另外一个类继承方法,属性和其他特征。当一个类继承另外一个类时, 继承类叫子类, 被继承的...
    无沣阅读 1,385评论 2 4
  • 基础部分(The Basics) 当推断浮点数的类型时,Swift 总是会选择Double而不是Float。 结合...
    gamper阅读 1,275评论 0 7
  • 一切可以妥当的一定会妥当!准备迎接奇迹! 早上很早就醒了但没即可起床,等着闹钟响了女儿起床才跟着起床了。 感恩女儿...
    belivePossible阅读 106评论 0 0
  • 秋雨整整下一两天 凌晨雨声仍不停息 是否搭个车会惬意 思来又想去还是我 自驾摩托车会舒心 陪孩子读书少许刻 庆幸雨...
    zyl林阅读 192评论 10 12