欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

因為一個Crash引發(fā)對Swift構(gòu)造器的思考分析

 更新時間:2019年10月20日 11:10:17   作者:鄭一一  
這篇文章主要給大家介紹了關(guān)于因為一個Crash引發(fā)對Swift構(gòu)造器的思考分析,文中通過示例代碼介紹的非常詳細,對大家的學習或者使用Swift具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧

前言

不久前,公司決定在一個 Objective-C 老工程中,開始使用 Swift 進行混合開發(fā)。期間,碰到一個與 Swift 類構(gòu)造過程相關(guān)的 Crash。在解決的過程中,對 Swift 構(gòu)造過程有了更深刻的理解,特作此記錄,期望對剛?cè)肟?Swift 開發(fā)的同學能有所幫助。

Crash 回顧

先來看一下代碼,以下定義了 BaseiewController 和 AViewController 兩個類:

// BaseViewController.h
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BaseViewController : UIViewController

- (instancetype)initWithParamenterA:(NSInteger)parameterA;

@end

NS_ASSUME_NONNULL_END

// BaseViewController.m
#import "BaseViewController.h"

@interface BaseViewController ()

@property (nonatomic, assign) NSInteger parameterA;

@end

@implementation BaseViewController

- (instancetype)initWithParamenterA:(NSInteger)parameterA {
  self = [super init];

  if (self) {
    self.parameterA = parameterA;
  }
  return self;
}

@end

以上代碼段定義了 Objective-C 類 BaseViewController,并且自定義了構(gòu)造器 initWithParamenterA。

// AViewController.swift
import UIKit

class AViewController: BaseViewController {
  let count: Int

  init(count: Int, parameterA: Int) {
    self.count = count
    super.init(paramenterA: parameterA)
  }

  // 后面的 “initCoder 從哪兒來” 小節(jié)會講講這個構(gòu)造器
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

第二塊代碼段定義了 Swift 類 AViewController,繼承自 BaseViewController,并且自定義了構(gòu)造器 init(count: Int, parameterA: Int),這個構(gòu)造器還調(diào)用到了父類的 initWithParamenterA 構(gòu)造器。細心的同學可能發(fā)現(xiàn)了,代碼中還出現(xiàn)了 init?(coder aDecoder: NSCoder) 構(gòu)造器,對此,在 initCoder 從哪兒來小節(jié)會有詳細解釋。

代碼就這么多。構(gòu)建運行工程,前往 AViewController 頁面,出乎意料,Crash??刂婆_輸出:

`Fatal error: Use of unimplemented initializer 'init(nibName:bundle:)' for class 'XXX.AViewController'`

意思是 AViewController 沒有實現(xiàn) init(nibName:bundle:) 方法,從而導致了 Crash。

對于剛?cè)肟?Swift 不久的同學可能就會有些懵逼。明明在 Objective-C 的時候這樣寫根本沒有問題啊,怎么到 Swift 這兒就 Crash 了呢?

Swift 類類型的構(gòu)造過程回顧

如果想要了解 Crash 的原因,就需要了解 UIViewController 所屬的類類型(class)構(gòu)造器的相關(guān)知識。

注:本小節(jié)大部分內(nèi)容摘自Swift 官方中文教程。

指定構(gòu)造器和便利構(gòu)造器

Swift 為類類型提供了兩種構(gòu)造器,分別是指定構(gòu)造器和便利構(gòu)造器。

類傾向于擁有極少的指定構(gòu)造器,普遍的是一個類只擁有一個指定構(gòu)造器。每一個類都必須至少擁有一個指定構(gòu)造器。指定構(gòu)造器語法如下:

init(parameters) {
  statements
}

便利構(gòu)造器是類中比較次要的、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來調(diào)用同一個類中的指定構(gòu)造器,并為部分形參提供默認值。一般只在必要的時候為類提供便利構(gòu)造器。

便利構(gòu)造器也采用相同樣式的寫法,但需要在 init 關(guān)鍵字之前放置 convenience 關(guān)鍵字,并使用空格將它們倆分開:

convenience init(parameters) {
  statements
}

類類型的構(gòu)造器代理

規(guī)則 1

指定構(gòu)造器必須調(diào)用其直接父類的的指定構(gòu)造器。

規(guī)則 2

便利構(gòu)造器必須調(diào)用同類中定義的其它構(gòu)造器。

規(guī)則 3

便利構(gòu)造器最后必須調(diào)用指定構(gòu)造器。

一個更方便記憶的方法是:

  • 指定構(gòu)造器必須總是向上代理
  • 便利構(gòu)造器必須總是橫向代理

這些規(guī)則可以通過下面圖例來說明:

類類型的繼承和重寫

跟 Objective-C 中的子類不同,Swift 中的子類默認情況下不會繼承父類的構(gòu)造器。Swift 的這種機制可以防止一個父類的簡單構(gòu)造器被一個更精細的子類繼承,而在用來創(chuàng)建子類時的新實例時沒有完全或錯誤被初始化。

構(gòu)造器的自動繼承

如上所述,子類在默認情況下不會繼承父類的構(gòu)造器。但是如果滿足特定條件,父類構(gòu)造器是可以被自動繼承的。事實上,這意味著對于許多常見場景你不必重寫父類的構(gòu)造器,并且可以在安全的情況下以最小的代價繼承父類的構(gòu)造器。
假設(shè)你為子類中引入的所有新屬性都提供了默認值,以下 2 個規(guī)則將適用:

規(guī)則 1

如果子類沒有定義任何指定構(gòu)造器,它將自動繼承父類所有的指定構(gòu)造器。(反之,如果定義了指定構(gòu)造器,就不會繼承父類的指定構(gòu)造器)

規(guī)則 2

如果子類提供了所有父類指定構(gòu)造器的實現(xiàn)——無論是通過規(guī)則 1 繼承過來的,還是提供了自定義實現(xiàn)——它將自動繼承父類所有的便利構(gòu)造器。

即使你在子類中添加了更多的便利構(gòu)造器,這兩條規(guī)則仍然適用。

注意

子類可以將父類的指定構(gòu)造器實現(xiàn)為便利構(gòu)造器來滿足規(guī)則 2。

UIViewController 的指定構(gòu)造器

UIViewController 在 Swift 中定義了兩個指定構(gòu)造器。

當使用 StoryBoard 創(chuàng)建 UIViewController 時,最終會調(diào)用:

init?(coder: NSCoder)

在使用除了 StoryBoard 之外的其它方式創(chuàng)建時,包括代碼、Xib 的創(chuàng)建,最終會調(diào)用:

init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)

分析與解決

講完了 Swift 類類型構(gòu)造器知識,先來分析一下 Swift 類 AViewController 。AViewController 定義了一個指定構(gòu)造器 init(count: Int, parameterA: Int),因此根據(jù)構(gòu)造器的自動繼承的規(guī)則 1, AViewController 不會自動繼承父類的指定構(gòu)造器,包括 init(nibName:bundle:)。也就是說 AViewController 沒有實現(xiàn) init(nibName:bundle:)。

其次 BaseViewController  是 Objective-C 類,所以可以不遵循 Swift 構(gòu)造器的規(guī)則。我們可以看到在 BaseViewController 的指定構(gòu)造器 initWithParamenterA 中,調(diào)用的是 [super init] ,這個方法并不是其父類的指定構(gòu)造器,不過就算這樣寫,編譯器也不會報錯。

@implementation BaseViewController

- (instancetype)initWithParamenterA:(NSInteger)parameterA {
 	// 在 Objective-C 中,子類的指定構(gòu)造器,不需要強制調(diào)用父類的指定構(gòu)造器。
 	// 調(diào)用 init,編譯允許通過
  self = [super init];

  if (self) {
    self.parameterA = parameterA;
  }
  return self;
}

@end

而在 AViewController 的構(gòu)造過程中,BaseViewController 的指定構(gòu)造器中 [super init] 這句代碼最終會調(diào)用當前類(AViewController)并沒有實現(xiàn)的 init(nibName:bundle:) ,從而導致了 Crash。這也就對應(yīng)了控制臺輸出的信息:

Fatal error: Use of unimplemented initializer 'init(nibName:bundle:)' for class 'XXX.AViewController'

再來簡單總結(jié)一下 Crash 的原因:

  1. 子類 AViewController 自定義了指定構(gòu)造器,但沒有實現(xiàn)父類的指定構(gòu)造器 init(nibName:bundle:)
  2. 父類 BaseViewController 的構(gòu)造器中直接調(diào)用了 [super init],導致最終調(diào)用了 AViewController 沒有實現(xiàn)的 init(nibName:bundle:) ,從而 Crash。

換句話說,如果子類 AViewController 沒有自定義指定構(gòu)造器或者父類 BaseViewController 遵循了類類型的構(gòu)造器代理的規(guī)則1,就不會發(fā)生 Crash。

據(jù)此,解決的方案也呼之欲出啦:

方法一:此處定義一個 SwiftBaseViewController 來替代 BaseViewController,其指定構(gòu)造器不允許調(diào)用 super.init ,因此也就避免了 Crash:

import UIKit

class SwiftBaseViewController: UIViewController {

  let parameterA: Int

  init(parameterA: Int) {
    self.parameterA = parameterA
   	
   	// 調(diào)用 super.init(),編譯不通過
				// 報錯信息:Must call a designated initializer of the superclass 'UIViewController'
//    super.init()
   
   	// 必須調(diào)用父類的指定構(gòu)造器
    super.init(nibName: nil, bundle: nil)
  }

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

這個方法的好處是可以從編譯器層面阻止直接調(diào)用 super.init,避免了程序員犯錯的可能。

不過這個方法的缺點是需要改變 BaseViewController 的編寫語言。遷移成本較大。

方法二:修改 BaseViewController 的構(gòu)造器實現(xiàn),將 self = [super init] 替換為 self = [super initWithNibName:nil bundle:nil]。

@implementation BaseViewController

- (instancetype)initWithParamenterA:(NSInteger)parameterA {
 	
  //self = [super init];
 	self = [super initWithNibName:nil bundle:nil];

  if (self) {
    self.parameterA = parameterA;
  }
  return self;
}

@end

這種方法是讓 Objective-C 類 BaseViewController 強制遵循 Swift 構(gòu)造器的規(guī)則,調(diào)用了父類的指定構(gòu)造器。

方法三:在子類 AViewController 中修改:

class AViewController: BaseViewController {
  var count: Int = 0
		// 使用便利構(gòu)造器
  convenience init(count: Int, parameterA: Int) {
    self.init(paramenterA: parameterA)
    self.count = count
  }
}

使用便利構(gòu)造器代替了原先的指定構(gòu)造器,根據(jù)構(gòu)造器的自動繼承規(guī)則 1,AViewController 自動繼承了父類所有的指定構(gòu)造器,包括 init(nibName:bundle:)。這個方法的缺點是,原本的常量屬性 count 需要變更為變量,并被賦予默認值。

initCoder 從哪兒來

在 Swift 的 UIViewController 子類中,如果自定義指定構(gòu)造器后,就必須實現(xiàn)構(gòu)造器 init?(coder aDecoder: NSCoder),這是為什么呢?

我們可以查看 UIViewController 的接口文件,其遵循 NSCoding 協(xié)議:

class UIViewController : NSCoding, ...

再來看一下 NSCoding 協(xié)議的內(nèi)容:

protocol NSCoding {
  func encode(with coder: NSCoder)
 
  init?(coder: NSCoder) // NS_DESIGNATED_INITIALIZER
}

其中定義了一個指定構(gòu)造器 init?(coder: NSCoder)。因為還需要遵循協(xié)議,這個構(gòu)造器同時是一個必要構(gòu)造器。

必要構(gòu)造器

在類的構(gòu)造器前添加 required 修飾符表明所有該類的子類都必須實現(xiàn)該構(gòu)造器。

根據(jù)構(gòu)造器的自動繼承規(guī)則 1,如果子類自定義了指定構(gòu)造器,那么就無法繼承父類的指定構(gòu)造器,恰巧 init?(coder: NSCoder) 還是一個必要構(gòu)造器,所以就必須在子類中實現(xiàn)該方法。

那么,這種情況就比較尷尬啦。明明就沒有在項目中使用到 StoryBoard??墒敲看味家由线@么一段代碼,顯得非常冗余:

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

那么有什么辦法可以避免重復寫這段代碼嗎?

答案是有的!方法是在 BaseViewController 中聲明該方法不可用,那么繼承自 BaseViewController 的所有子類都不需要實現(xiàn)這個方法。

Swift 版本:

@available(*, unavailable, message: "Unsupported init(coder:)")
required init?(coder aDecoder: NSCoder) {
  fatalError("init(coder:) has not been implemented")
}

Objective-C 版本:

- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;

Swift 構(gòu)造器知識拾遺

除了上面講到的一些構(gòu)造器知識,這里還會再講講一些其它比較重要的點。

默認構(gòu)造器

如果結(jié)構(gòu)體或類為所有屬性提供了默認值,又沒有提供任何自定義的構(gòu)造器,那么 Swift 會給這些結(jié)構(gòu)體或類提供一個默認構(gòu)造器。這個默認構(gòu)造器將簡單地創(chuàng)建一個所有屬性值都設(shè)置為它們默認值的實例。

class ShoppingListItem {
  var name: String?
  var quantity = 1
  var purchased = false
}
var item = ShoppingListItem()

逐一構(gòu)造器
只要你曾經(jīng)了解過 Swift,肯定聽說過許許多多關(guān)于類和結(jié)構(gòu)體的區(qū)別。對于習慣使用類的同學來說,這里不妨再多告訴你一個使用結(jié)構(gòu)體的理由。

官方文檔中提到,結(jié)構(gòu)體如果沒有定義任何自定義構(gòu)造器,它們將自動獲得逐一成員構(gòu)造器(memberwise initializer)。不像默認構(gòu)造器,即使存儲型屬性沒有默認值,結(jié)構(gòu)體也能會獲得逐一成員構(gòu)造器。

struct Size {
  var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
// Swift 5.1 甚至會為你生成省去了有默認值屬性的逐一構(gòu)造器。省去的屬性將會直接使用默認值
let zeroByTwo = Size(height: 2.0)
let twoByZero = Size(width: 2.0)

某些場景下,如果確實需要自定義一個構(gòu)造器,但又想保留逐一成員構(gòu)造器,那么請在 extension 中自定義構(gòu)造器。
不過對于類來說,所有的構(gòu)造器都必須自己來實現(xiàn)。所以從使用便利性的角度來說,結(jié)構(gòu)體無疑是一個更好的選擇。

可失敗構(gòu)造器

在 Swift 中可以定義一個構(gòu)造器可失敗的類,結(jié)構(gòu)體或者枚舉。這里的“失敗”指的是,如給構(gòu)造器傳入無效的形參,或缺少某種所需的外部資源,又或是不滿足某種必要的條件等。

為了妥善處理這種構(gòu)造過程中可能會失敗的情況。你可以在一個類,結(jié)構(gòu)體或是枚舉類型的定義中,添加一個或多個可失敗構(gòu)造器。其語法為在 init 關(guān)鍵字后面添加問號(init?)。比如 Int 存在如下可失敗構(gòu)造器:

init?(exactly source: Float)

推薦閱讀

想要更全面深入了解 Swift 的構(gòu)造過程,請閱讀下面的中英文教程:

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。

相關(guān)文章

  • Swift算法之二叉樹實現(xiàn)的方法示例

    Swift算法之二叉樹實現(xiàn)的方法示例

    二叉樹是計算機科學中最基本也是最重要的樹型結(jié)構(gòu),最常見的二叉樹生成算法通常是使用遞歸或者其他描述類語言的方法來實現(xiàn)。本文主要介紹了Swift算法之二叉樹實現(xiàn)的方法,文中介紹的非常詳細,對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。
    2017-03-03
  • Swift與C語言指針結(jié)合使用實例

    Swift與C語言指針結(jié)合使用實例

    這篇文章主要介紹了Swift與C語言指針結(jié)合使用實例,本文講解了用以輸入/輸出的參數(shù)指針、作為數(shù)組使用的參數(shù)指針、用作字符串參數(shù)的指針、指針參數(shù)轉(zhuǎn)換的安全性等內(nèi)容,需要的朋友可以參考下
    2015-05-05
  • Swift中的可選項Optional解包方式實現(xiàn)原理

    Swift中的可選項Optional解包方式實現(xiàn)原理

    這篇文章主要為大家介紹了Swift中的可選項Optional解包方式實現(xiàn)原理示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • Swift能代替Objective-C嗎?

    Swift能代替Objective-C嗎?

    這是我在網(wǎng)上上看到的答案,復制粘貼過來和大家分享一下,因為我和很多人一樣很關(guān)心Swift的出現(xiàn)對Mac開發(fā)的影響和對Objective-C的影響。
    2014-09-09
  • Swift 4最全的新特性詳細解析(推薦)

    Swift 4最全的新特性詳細解析(推薦)

    Swift 4 在 Swift 3 的基礎(chǔ)上,提供了更強大的穩(wěn)健性和穩(wěn)定性。所以下面這篇文章就來給大家總結(jié)介紹關(guān)于Swift4新特性的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
    2017-11-11
  • 詳解在swift中實現(xiàn)NSCoding的自動歸檔和解檔

    詳解在swift中實現(xiàn)NSCoding的自動歸檔和解檔

    本篇文章主要介紹了在swift中實現(xiàn)NSCoding的自動歸檔和解檔,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-03-03
  • swift 可選型的使用詳解

    swift 可選型的使用詳解

    可選性是Swift提供的一個特殊類型,它為我們編寫程序提供便利的條件。這篇文章主要介紹了swift 可選型的使用詳解,非常不錯具有參考借鑒價值,需要的朋友可以參考下
    2016-10-10
  • Swift中常量和變量的區(qū)別與聲明詳解

    Swift中常量和變量的區(qū)別與聲明詳解

    Swift語言同樣和Java和OC等語言一樣是同樣是需要聲明常量和變量的,下面就讓我們來學習一下Swift的常量和變量。這篇文章主要給大家介紹了關(guān)于Swift中常量和變量的區(qū)別與聲明的相關(guān)資料,需要的朋友可以參考下。
    2017-11-11
  • Swift實現(xiàn)復數(shù)計算器

    Swift實現(xiàn)復數(shù)計算器

    這篇文章主要為大家詳細介紹了Swift實現(xiàn)復數(shù)計算器,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Swift可選值優(yōu)化示例詳解

    Swift可選值優(yōu)化示例詳解

    這篇文章主要為大家介紹了Swift可選值優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06

最新評論