Swift - 了解一下Swift初始化

前言:

本篇文章,主要是针对Swift初始化的印象加深。搜集了表达比较好的初始化文章,进行了汇总。

首先我们从初始化方法顺序开始,了解初始化方法有三种,指定、便利、和必要初始化方法。从而针对自定义控件,初始化进行验证,得出自定义控件重写父类初始化方法时,必须提供必要初始化方法,否则会报错。而后又提供了,为什么Swift:必须添加init?(coder decoder: NSCoder)的原因,加深理解。

目录:
001 - 初始化方法顺序
002 - 了解一下指定、便利、必要初始化
003 - Swift:为什么必须添加init?(coder decoder: NSCoder)的原因

初始化方法顺序

与 Objective-C 不同,Swift 的初始化方法需要保证类型的所有属性都被初始化。所以初始化方法的调用顺序就很有讲究。在某个类的子类中,初始化方法里语句的顺序并不是随意的,我们需要保证在当前子类实例的成员初始化完成后才能调用父类的初始化方法:

 class Cat {
     var name: String
     init() {
         name = "cat"
     }
 }

 class Tiger: Cat {
     let power: Int
     override init() {
         power = 10
         super.init()
         name = "tiger"
     }
 }

一般来说,子类的初始化顺序是:

  • 1.设置子类自己需要初始化的参数,power = 10
  • 2.调用父类的相应的初始化方法,super.init()
  • 3.对父类中的需要改变的成员进行设定,name = "tiger"

其中第三步是根据具体情况决定的,如果我们在子类中不需要对父类的成员做出改变的话,就不存在第 3 步。而在这种情况下,Swift 会自动地对父类的对应 init 方法进行调用,也就是说,第 2 步的 super.init() 也是可以不用写的 (但是实际上还是调用的,只不过是为了简便 Swift 帮我们完成了)。这种情况下的初始化方法看起来就很简单:

 class Cat {
     var name: String
     init() {
         name = "cat"
     }
 }
 
 class Tiger: Cat {
     let power: Int
     override init() {
         power = 10
         // 如果我们不需要改变 name 的话,
         // 虽然我们没有显式地对 super.init() 进行调用
         // 不过由于这是初始化的最后了,Swift 替我们自动完成了
     }
 }

了解一下指定、便利、必要初始化

我们在深入初始化方法之前,不妨先再想想 Swift 中的初始化想要达到一种怎样的目的。

其实就是安全。在 Objective-C 中,init 方法是非常不安全的:没有人能保证 init 只被调用一次,也没有人保证在初始化方法调用以后实例的各个变量都完成初始化,甚至如果在初始化里使用属性进行设置的话,还可能会造成各种问题,虽然 Apple 也明确说明了不应该在 init 中使用属性来访问,但是这并不是编译器强制的,因此还是会有很多开发者犯这样的错误。

所以 Swift 有了超级严格的初始化方法。一方面,Swift 强化了 designated 初始化方法的地位。Swift 中不加修饰的 init 方法都需要在方法中保证所有非 Optional 的实例变量被赋值初始化,而在子类中也强制 (显式或者隐式地) 调用 super 版本的 designated 初始化,所以无论如何走何种路径,被初始化的对象总是可以完成完整的初始化的。

 class ClassA {
     let numA: Int
     init(num: Int) {
         numA = num
     }
 }
 
 class ClassB: ClassA {
     let numB: Int
 
     override init(num: Int) {
         numB = num + 1
         super.init(num: num)
     }
 }
在上面的示例代码中,注意在 init 里我们可以对 let 的实例常量进行赋值,这是初始化方法的重要特点。在 Swift 中 let 声明的值是常量,无法被写入赋值,这对于构建线程安全的 API 十分有用。而因为 Swift 的 init 只可能被调用一次,因此在 init 中我们可以为常量进行赋值,而不会引起任何线程安全的问题。


 class ClassA {
     let numA: Int
     init(num: Int) {
         numA = num
     }
 
     convenience init(bigNum: Bool) {
         self.init(num: bigNum ? 10000 : 1)
     }
 }
 
 class ClassB: ClassA {
     let numB: Int
 
     override init(num: Int) {
         numB = num + 1
         super.init(num: num)
     }
 }

只要在子类中实现重写了父类 convenience 方法所需要的 init 方法的话,我们在子类中就也可以使用父类的 convenience 初始化方法了。比如在上面的代码中,我们在 ClassB 里实现了 init(num: Int) 的重写。这样,即使在 ClassB 中没有 bigNum 版本的 convenience init(bigNum: Bool),我们仍然还是可以用这个方法来完成子类初始化:

  let anObj = ClassB(bigNum: true)
  // anObj.numA = 10000, anObj.numB = 10001

因此进行一下总结,可以看到初始化方法永远遵循以下两个原则:

    1. 初始化路径必须保证对象完全初始化,这可以通过调用本类型的 designated 初始化方法来得到保证。
    1. 子类的 designated 初始化方法必须调用父类的 designated 方法,以保证父类也完成初始化。

对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。这样做的最大的好处是可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。一个现成的例子就是上面的 init(bigNum: Bool):如果我们希望这个初始化方法对于子类一定可用,那么应当将 init(num: Int) 声明为必须,这样我们在子类中调用 init(bigNum: Bool) 时就始终能够找到一条完全初始化的路径了:

  class ClassA {
      let numA: Int
      required init(num: Int) {
          numA = num
      }
  
      convenience init(bigNum: Bool) {
          self.init(num: bigNum ? 10000 : 1)
      }
  }
  
  class ClassB: ClassA {
      let numB: Int
  
      required init(num: Int) {
          numB = num + 1
          super.init(num: num)
      }
  }

另外需要说明的是,其实不仅仅是对 designated 初始化方法,对于 convenience 的初始化方法,我们也可以加上 required 以确保子类对其进行实现。这在要求子类不直接使用父类中的 convenience 初始化方法时会非常有帮助。

自定义控件时,重写父类的初始化,必须提供必要初始化方法,否则会报错

当我们在自定义控件时,我首先想到的是,重写父类的初始化。如下面的代码:

 import UIKit
 
 class ZBTabBar: UITabBar {
   
     // 视图初始化
     override init(frame: CGRect) {
         
         super.init(frame: frame)

     }


 }

但是会报一个错误如下:


错误的意思是:必须提供父类必要初始化的方法

 required init?(coder aDecoder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }

由此,我们知道,自定义控件时,重写父类的初始化,必须提供必要初始化方法。

Swift:为什么必须添加init?(coder decoder: NSCoder)的原因

当我们重写类的时候经常提示要添加代码:

 required init?(coder aDecoder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }

这个叫必要初始化器,这种情况一般会出现在继承了遵守NSCoding protocol的类,比如UIView系列的类、UIViewController系列的类。


为什么一定要添加:

这是NSCoding protocol定义的,遵守了NSCoding protoaol的所有类必须继承。只是有的情况会隐式继承,而有的情况下需要显示实现。


什么情况下要显示添加:

当我们使用storyboard实现界面的时候,程序会调用这个初始化器。注意要去掉fatalError,fatalError的意思是无条件停止执行并打印。在obj-c中可以通过下面代码实现

 NSException *exception = [NSException exceptionWithName:@"HotTeaException" reason:@"The tea is too hot" userInfo:nil];
 @throw exception;

总结:

如果代码实现界面,那么我们只要根据编译器提示添加必要初始化器后,就不用理会,我们创建界面的工作可以在自定义的初始化器里实现。

补充:

let vc = UIViewController()方式初始化类

UIViewController类视乎只有两个初始化器:

  • 一个是必要初始化器init?(coder aDecoder: NSCoder)
  • 一个是指定初始化器init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)

疑问:
那么为什么我们可以用let vc = UIViewController()这种方式初始化类呢?

**答: **原因可能是这个初始化方式是来自uikit,也就是调用了Object-c下的UIViewController初始化方法,是object-c bridge过来的。

学习地址:

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

推荐阅读更多精彩内容