最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

因?yàn)橐粋€Crash引發(fā)對Swift構(gòu)造器的思考分析

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

前言

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

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)造器。細(xì)心的同學(xué)可能發(fā)現(xiàn)了,代碼中還出現(xiàn)了 init?(coder aDecoder: NSCoder) 構(gòu)造器,對此,在 initCoder 從哪兒來小節(jié)會有詳細(xì)解釋。

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

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

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

對于剛?cè)肟?Swift 不久的同學(xué)可能就會有些懵逼。明明在 Objective-C 的時(shí)候這樣寫根本沒有問題啊,怎么到 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)造器,并為部分形參提供默認(rèn)值。一般只在必要的時(shí)候?yàn)轭愄峁┍憷麡?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 中的子類默認(rèn)情況下不會繼承父類的構(gòu)造器。Swift 的這種機(jī)制可以防止一個父類的簡單構(gòu)造器被一個更精細(xì)的子類繼承,而在用來創(chuàng)建子類時(shí)的新實(shí)例時(shí)沒有完全或錯誤被初始化。

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

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

規(guī)則 1

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

規(guī)則 2

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

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

注意

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

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

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

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

init?(coder: NSCoder)

在使用除了 StoryBoard 之外的其它方式創(chuàng)建時(shí),包括代碼、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 沒有實(shí)現(xiàn) init(nibName:bundle:)。

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

@implementation BaseViewController

- (instancetype)initWithParamenterA:(NSInteger)parameterA {
 	// 在 Objective-C 中,子類的指定構(gòu)造器,不需要強(qiáng)制調(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)用當(dāng)前類(AViewController)并沒有實(shí)現(xiàn)的 init(nibName:bundle:) ,從而導(dǎo)致了 Crash。這也就對應(yīng)了控制臺輸出的信息:

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

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

  1. 子類 AViewController 自定義了指定構(gòu)造器,但沒有實(shí)現(xiàn)父類的指定構(gòu)造器 init(nibName:bundle:)
  2. 父類 BaseViewController 的構(gòu)造器中直接調(diào)用了 [super init],導(dǎo)致最終調(diào)用了 AViewController 沒有實(shí)現(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(),編譯不通過
				// 報(bào)錯信息: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,避免了程序員犯錯的可能。

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

方法二:修改 BaseViewController 的構(gòu)造器實(shí)現(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 強(qiáng)制遵循 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:)。這個方法的缺點(diǎn)是,原本的常量屬性 count 需要變更為變量,并被賦予默認(rèn)值。

initCoder 從哪兒來

在 Swift 的 UIViewController 子類中,如果自定義指定構(gòu)造器后,就必須實(shí)現(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)。因?yàn)檫€需要遵循協(xié)議,這個構(gòu)造器同時(shí)是一個必要構(gòu)造器。

必要構(gòu)造器

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

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

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

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

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

答案是有的!方法是在 BaseViewController 中聲明該方法不可用,那么繼承自 BaseViewController 的所有子類都不需要實(shí)現(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)造器知識,這里還會再講講一些其它比較重要的點(diǎn)。

默認(rèn)構(gòu)造器

如果結(jié)構(gòu)體或類為所有屬性提供了默認(rèn)值,又沒有提供任何自定義的構(gòu)造器,那么 Swift 會給這些結(jié)構(gòu)體或類提供一個默認(rèn)構(gòu)造器。這個默認(rèn)構(gòu)造器將簡單地創(chuàng)建一個所有屬性值都設(shè)置為它們默認(rèn)值的實(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ū)別。對于習(xí)慣使用類的同學(xué)來說,這里不妨再多告訴你一個使用結(jié)構(gòu)體的理由。

官方文檔中提到,結(jié)構(gòu)體如果沒有定義任何自定義構(gòu)造器,它們將自動獲得逐一成員構(gòu)造器(memberwise initializer)。不像默認(rèn)構(gòu)造器,即使存儲型屬性沒有默認(rèn)值,結(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 甚至?xí)槟闵墒∪チ擞心J(rèn)值屬性的逐一構(gòu)造器。省去的屬性將會直接使用默認(rèn)值
let zeroByTwo = Size(height: 2.0)
let twoByZero = Size(width: 2.0)

某些場景下,如果確實(shí)需要自定義一個構(gòu)造器,但又想保留逐一成員構(gòu)造器,那么請?jiān)?extension 中自定義構(gòu)造器。
不過對于類來說,所有的構(gòu)造器都必須自己來實(shí)現(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)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。

相關(guān)文章

  • SwiftUI中@ViewBuilder的相關(guān)知識點(diǎn)解密

    SwiftUI中@ViewBuilder的相關(guān)知識點(diǎn)解密

    IOS開發(fā)目前最主流的框架當(dāng)屬SwiftUI了,這篇文章主要給大家介紹了關(guān)于SwiftUI中@ViewBuilder的一些相關(guān)知識點(diǎn),文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2021-07-07
  • 通過示例分析Swift單例模式

    通過示例分析Swift單例模式

    這篇文章主要介紹了通過示例分析Swift單例模式的三種方法,分別是全局變量,內(nèi)部變量,dispatch_once方式,有需要的小伙伴可以參考下。
    2015-06-06
  • Swift心得筆記之集合類型

    Swift心得筆記之集合類型

    本文為大家講解的是swift語言中的集合類型數(shù)據(jù),這是swift開發(fā)必須掌握的知識點(diǎn),感興趣的同學(xué)參考下。
    2015-04-04
  • Swift枚舉的一些小用法總結(jié)

    Swift枚舉的一些小用法總結(jié)

    這篇文章主要給大家介紹了關(guān)于Swift枚舉的一些小用法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Swift具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • Swift面試題及答案整理

    Swift面試題及答案整理

    雖然Swift出現(xiàn)的時(shí)間不久,但是它已經(jīng)成為最流行的編程語言之一了。Swift的知識浩如煙海,但是怎么測試你掌握了多少?通過下面這篇整理關(guān)于Swift面試題及答案,可能會對你所掌握的Swift進(jìn)行一個判斷,需要的朋友可以參考借鑒。
    2017-01-01
  • Swift開發(fā)之UITableView狀態(tài)切換效果

    Swift開發(fā)之UITableView狀態(tài)切換效果

    這篇文章主要介紹了Swift開發(fā)之UITableView狀態(tài)切換效果的相關(guān)資料,非常不錯,具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-08-08
  • Swift心得筆記之字符串

    Swift心得筆記之字符串

    Swift 的 String 類型是值類型 。 如果您創(chuàng)建了一個新的字符串,那么當(dāng)其進(jìn)行常量、變量賦值操作或在函數(shù)/方法中傳遞時(shí),會進(jìn)行值拷貝。 任何情況下,都會對已有字符串值創(chuàng)建新副本,并對該新副本進(jìn)行傳遞或賦值操作。
    2015-04-04
  • swift3.0 創(chuàng)建sqlite數(shù)據(jù)庫步驟方法

    swift3.0 創(chuàng)建sqlite數(shù)據(jù)庫步驟方法

    本篇文章主要介紹了swift3.0 創(chuàng)建sqlite數(shù)據(jù)庫步驟方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • 詳解Swift的內(nèi)存管理

    詳解Swift的內(nèi)存管理

    這篇文章主要介紹了Swift的內(nèi)存管理,對內(nèi)存管理感興趣的同學(xué),可以參考下
    2021-04-04
  • Swift調(diào)用Objective-C代碼

    Swift調(diào)用Objective-C代碼

    目前Swift語言所編寫的應(yīng)用才剛剛可以使用Xcode 6 GM版本提交,而Objective-C作為蘋果的主開發(fā)語言存在了很多年了。目前尚無成熟的Swift庫可用,所以當(dāng)前編寫應(yīng)用可以說基本離不開調(diào)用Objective-C代碼的情況。
    2014-09-09

最新評論

新民市| 嘉兴市| 长沙市| 中山市| 三门峡市| 县级市| 武隆县| 从化市| 沅陵县| 安福县| 兴文县| 佛坪县| 封丘县| 扎囊县| 龙南县| 深水埗区| 沾益县| 永丰县| 石首市| 阿克苏市| 方山县| 雷州市| 双柏县| 宿松县| 湘潭市| 桐庐县| 华蓥市| 武义县| 毕节市| 永平县| 临泽县| 河南省| 崇阳县| 含山县| 前郭尔| 高尔夫| 衡阳市| 新邵县| 乐亭县| 佳木斯市| 辛集市|