來一次有側重點的區分Swift與Objective-C
[TOC]
@(swift)[溫故而知新]
面試中經常被問到 Objective-C
與 Swift
的區別,其實區別還是很多的,重點整理一下個人覺得很重要的: 面向協議程式設計 。
一、Objective-C與Swift的異同
1.1、swift和OC的共同點:
-
OC
出現過的絕大多數概念,比如 引用計數 、 ARC (自動引用計數)、 屬性 、 協議 、 介面 、 初始化 、 擴充套件類 、 命名引數 、 匿名函式 等,在Swift
中繼續有效(可能最多換個術語)。 -
Swift
和Objective-C
共用一套執行時環境,Swift
的型別可以橋接到Objective-C
(下面我簡稱OC),反之亦然
1.2、swift的優點:
- swift注重安全,
OC
注重靈活 - swift注重面向協議程式設計、函數語言程式設計、面向物件程式設計,
OC
注重面向物件程式設計 - swift注重值型別,
OC
注重指標和引用 - swift是靜態型別語言,
OC
是動態型別語言 - swift容易閱讀,檔案結構和大部分語法簡易化,只有.swift檔案,結尾不需要分號
- swift中的可選型別,是用於所有資料型別,而不僅僅侷限於類。相比於
OC
中的nil
更加安全和簡明 - swift中的泛型型別更加方便和通用,而非
OC
中只能為集合型別新增泛型 - swift中各種方便快捷的高階函式(
函數語言程式設計
) ( Swift的標準陣列支援三個高階函式:map
,filter
和reduce
,以及map的擴充套件flatMap
) - swift新增了兩種許可權,細化許可權。
open
>public
>internal(預設)
>fileprivate
>private
- swift中獨有的元組型別(
tuples
),把多個值組合成複合值。元組內的值可以是任何型別,並不要求是相同型別的。
1.3、swift的不足:
- 版本不穩定
- 公司使用比例不高,使用人數比例偏低
- 有些語法其實可以只規定一種格式,不必這樣也行,那樣也行。像Go一樣禁止一切(Go有點偏激)耍花槍的東西,同一個規範,方便團隊合作和閱讀他人程式碼。
二、Objective-C中的protocol與Swift中的protocol的區別
相比於 OC
, Swift
可以做到 protocol
協議方法的具體 預設實現 (通過 extension
)相比 多型
更好的實現了 程式碼複用 ,而 OC
則不行。
三、 面向協議
( 面向介面
)與 面向物件
的區別
面向物件
和 面向協議
的的最明顯區別是 對抽象資料的使用方式
,面向物件採用的是 繼承 ,而 面向協議
採用的是 遵守協議 。在 面向協議
設計中, Apple
建議我們更多的使用 值型別 ( struct
)而非 引用型別 ( class
)。這篇文章中有一個很好的例子說明了 面向協議
比 面向物件
更符合 某些業務需求 。其中有飛機、汽車、自行車三種交通工具(均繼承自父類交通工具);老虎、馬三種動物(均繼承父類自動物);在古代馬其實也是一種交通工具,但是父類是動物,如果馬也有交通工具的功能,則:
如果採用 面向物件程式設計
,則需要既要繼承動物,還要繼承交通工具,但是父類交通工具有些功能馬是不需要的。由此可見 繼承 ,作為 程式碼複用 的一種方式, 耦合性 還是太強。 事物往往是一系列特質的組合,而不單單是以一脈相承並逐漸擴充套件的方式構建的。 以後慢慢會發現面向物件很多時候其實不能很好地對事物進行抽象。

如果採用 面向協議程式設計
,馬只需要實現出行協議就可以擁有交通工具的功能了。 面向協議
就是這樣的抽離方式,更好的職責劃分,更加具象化,職責更加單一。 很明顯 面向協議
的目的是為了降低程式碼的 耦合性 。

總結:
面向協議
相對於 面向物件
來說更具有 可伸縮性 和 可重用性 ,並且在程式設計的過程中更加 模組化 ,通過協議以及協議擴充套件替代一個 龐大的基類 ,這在 大規模系統程式設計 中會有很大的便捷之處。
3.1、 協議
和 協議擴充套件
比 基類
有三個明顯的優點:
-
1、型別可以遵守多個協議但是隻有一個基類。
這意味著型別可以隨意遵守任何想要特性的協議,而不需要一個巨大的基類。 -
2、不需要知道原始碼就可以使用協議擴充套件新增功能。
這意味著我們可以任意擴充套件協議,包括swift內建的協議,而不需要修改 基類 的 原始碼 。一般情況下我們會給 特定的類 而非 類的層級 ( 繼承體系 )新增擴充套件;但是如果必要,我們依然可以給 基類 新增擴充套件,使所有的子類 繼承 新增的功能。使用協議,所有的 屬性 、 方法 和 建構函式 都被定義在遵守協議的型別自身中。這讓我們很容易地檢視到所有東西是 怎麼被定義 和 初始化 的。 我們不需要在類的層級之間來回穿梭以檢視所有東西是如何初始化的。 忘記設定超類可能沒有什麼大問題,但是在 更復雜 的型別中,忘記合理地設定某個屬性可能會導致 意想不到 的行為。 -
3、協議可以被類、結構體和列舉遵守,而類層級約束為類型別。
協議 和 協議擴充套件 可以讓我們在更合理的地方使用 值型別 。 引用型別 和 值型別 的一個主要的區別就是型別是如何傳遞的。當我們傳遞引用型別(class)的例項時,我們傳遞的對原例項的引用。這意味著所做的任何更改都會 反射回原例項中 。當我們傳遞值型別的例項時,我們傳遞的是對 原例項的一份拷貝 。這意味著所做的任何更改都不會反射回原例項中。使用值型別確保了我們總是得到一個唯一的例項因為我們傳遞了一份對原例項的拷貝而非對原例項的引用。因此,我們能相信沒有程式碼中的其它部分會意外地修改我們的例項。這在 多執行緒 環境中尤其有用,其中不同的執行緒可以 修改資料 並建立 意外地行為 。
3.2、面向物件的特點
優點:
- 封裝
資料封裝、訪問控制、隱藏實現細節、型別抽象為類;
程式碼以邏輯關係組織到一起,方便閱讀;
高內聚、低耦合的系統結構
- 繼承
程式碼重用,繼承關係,更符合人類思維
- 多型
介面重用,父類指標能夠指向子類物件
當父類的引用指向子類物件時,就發生了向上轉型,即把子類型別物件轉成了父類型別。
向上轉型的好處是隱藏了子類型別,提高了程式碼的擴充套件性。
多型的不足:
- 父類有部分public方法是子類不需要的,也不允許子類覆蓋重寫
- 父類有一些方法是必須要子類去覆蓋重寫的,在父類的方法其實也是一個空方法
- 父類的一些方法即便被子類覆蓋重寫,父類原方法還是要執行的
- 父類的一些方法是可選覆蓋重寫的,一旦被覆蓋重寫,則以子類為準
較好的抽象型別應該:
- 更多地支援值型別,同時也支援引用型別
- 更多地支援靜態型別關聯(編譯期),同時也支援動態派發(runtime)
- 結構不龐大不復雜
- 模型可擴充套件
- 不給模型強制新增資料
- 不給模型增加初始化任務的負擔
- 清楚哪些方法該實現哪些方法不需實現
3.3、OneV's Den提到的 面向物件
的三個困境:
1、動態派發的安全性(這應該是OC的困境,在Swift中Xcode是不可能讓這種問題編譯通過的)
在 Objective-C
中下面這段程式碼編譯是不會報警告和錯誤的
NSObject *v1 = [NSObject new]; NSString *v2 = [NSString new]; NSNumber *v3 = [NSNumber new]; NSArray *array = @[v1, v2, v3]; for (id obj in array) { [obj boolValue]; } 複製程式碼
在 Objective-C
中可以藉助泛型檢查這種潛在的問題,Xocde會提示警告
@protocol SafeProtocol <NSObject> - (void)func; @end @interface SafeObj : NSObject<SafeProtocol> @end @implementation SafeObj - (void)func { } @end @interface UnSafeObj : NSObject @end @implementation UnSafeObj @end 複製程式碼
Objective-C
在 Xcode7
中,可以使用帶 泛型 的 容器 也可以解決這個問題,但是隻是:warning:,程式執行期間仍可能由於此問題導致的 崩潰
SafeObj *v1 = [[SafeObj alloc] init]; UnSafeObj *v2 = [[UnSafeObj alloc] init]; // 由於v2沒有實現協議SafeProtocol,所以此處Xcode會有警告 // Object of type 'UnSafeObj *' is not compatible with array element type 'id<SafeProtocol>' NSArray<id<SafeProtocol>> *array = @[v1, v2]; for (id obj in array) { [obj func]; } 複製程式碼
使用 swift
,必須指定型別,否則不是:warning:,而是:x:,所以 swift
在 編譯階段 就可以檢查出問題
// 直接報錯,而不是警告 // Cannot convert value of type 'String' to expected argument type 'NSNumber' var array: [NSNumber] = [] array.append(1) array.append("a") 複製程式碼
2、橫切關注點
我們很難在 不同的繼承體系 中 複用程式碼 ,用行話來講就是 橫切關注點 ( Cross-Cutting Concerns
)。比如下面的關注點 myMethod
,位於兩條繼承鏈 ( UIViewController
-> ViewCotroller
和 UIViewController
-> UITableViewController
-> AnotherViewController
) 的橫切面上。 面向物件 是一種不錯的抽象方式,但是肯定不是最好的方式。它無法描述兩個 不同事物 具有某個 相同特性 這一點。在這裡, 特性的組合 要比繼承更貼切事物的本質。
class ViewCotroller: UIViewController { func myMethod() { } } 複製程式碼
class AnotherViewController: UITableViewController { func myMethod() { } } 複製程式碼
在 面向物件程式設計
中,針對這種問題的幾種解決方案:
- 1、 Copy & Paste
快速,但是這也是壞程式碼的開頭。我們應該儘量避免這種做法。
- 2、 引入 BaseViewController
看起來這是一個稍微靠譜的做法,但是如果不斷這麼做,會讓所謂的 Base
很快變成垃圾堆。職責不明確,任何東西都能扔進 Base
,你完全不知道哪些類走了 Base
,而這個“超級類”對程式碼的影響也會不可預估。
- 3、 依賴注入
通過外界傳入一個帶有 myMethod 的物件,用新的型別來提供這個功能。這是一個稍好的方式,但是引入額外的依賴關係,可能也是我們不太願意看到的。
- 4、 多繼承
當然, Swift
是不支援多繼承的。不過如果有多繼承的話,我們確實可以從多個父類進行繼承,並將 myMethod
新增到合適的地方。有一些語言選擇了支援多繼承 (比如 C++
),但是它會帶來 OOP
中另一個著名的問題: 菱形缺陷 。
在 Swift
的 面向協議程式設計
中,針對這種問題的解決方案 ( 使用協議擴充套件新增預設實現 ):
protocol P { func myMethod() } extension P { func myMethod() { doWork() } } 複製程式碼
extension ViewController: P { } extension AnotherViewController: P { } viewController.myMethod() anotherViewController.myMethod() 複製程式碼
3、菱形問題
多繼承中,兩個父類實現了相同的方法, 子類無法確定繼承哪個父類的此方法 ,由於多繼承的拓撲結構是一個菱形,所以這個問題有被叫做菱形缺陷( Diamond Problem
)。
上面的例子中,如果我們有多繼承,那麼 ViewController
和 AnotherViewController
的關係可能會是這樣的:

如果 ViewController
和 UITableViewController
都實現了 myMethod
方法,則在 AnotherViewController
中就會出現菱形缺陷問題。
我們應該著眼於寫乾淨並安全的程式碼,乾淨的程式碼是非常易讀和易理解的程式碼。
四、程式設計實踐:基於 protocol
的連結串列實現
import UIKit protocol ChainListAble { associatedtype T: Equatable // 列印 var description: String{get} // 數量 var count: Int{get} /// 插入 func insertToHead(node: Node<T>) func insertToHead(value: T) func insertToTail(node: Node<T>) func insertToTail(value: T) func insert(node: Node<T>, afterNode: Node<T>) -> Bool func insert(value: T, afterNode: Node<T>) -> Bool func insert(node: Node<T>, beforNode: Node<T>) -> Bool func insert(value: T, beforNode: Node<T>) -> Bool /// 刪除(預設第一個符合條件的) @discardableResult func delete(node: Node<T>) -> Bool @discardableResult func delete(value: T) -> Bool @discardableResult func delete(index: Int) -> Bool //func delete(fromIndex: Int, toIndex: Int) -> Bool //func deleteAll() /// 查詢(預設第一個符合條件的) func find(value: T) -> Node<T>? func find(index: Int) -> Node<T>? /// 判斷結點是否在連結串列中 func isContain(node: Node<T>) -> Bool } /// [值型別不能在遞迴裡呼叫](https://www.codetd.com/article/40263),因此Node型別只能是class而不是struct // 有些時候你只能使用類而不能使用結構體,那就是遞迴裡 // struct報錯:Value type 'Node' cannot have a stored property that recursively contains it class Node<T: Equatable> { var value: T var next: Node? /// 便利構造方法 /// /// - Parameter value: value convenience init(value: T) { self.init(value: value, next: nil) } /// 預設指定初始化方法 /// /// - Parameters: ///- value: value ///- next: next init(value: T, next: Node?) { self.value = value } // 銷燬函式 deinit { print("\(self.value) 釋放") } } extension Node { /// 返回當前結點到連結串列尾的長度 var count: Int { var idx: Int = 1 var node: Node? = self while node?.value != nil { node = node?.next idx = idx + 1 } return idx } } class SingleChainList: ChainListAble { typealias T = String // 哨兵結點,不儲存資料 private var dummy: Node = Node.init(value: "") } extension SingleChainList { var description: String { var description: String = "" var tempNode = self.dummy while let nextNode = tempNode.next { description = description + " " + nextNode.value tempNode = nextNode } return description } var count: Int { var count: Int = 0 var tempNode = self.dummy while let nextNode = tempNode.next { count = count + 1 tempNode = nextNode } return count } /// 在頭部插入值 /// /// - Parameter value: value func insertToHead(value: T) { let node: Node = Node.init(value: value) self.insertToHead(node: node) } /// 在頭部插入結點 /// /// - Parameter node: node func insertToHead(node: Node<T>) { node.next = self.dummy.next self.dummy.next = node } /// 在尾部插入值 /// /// - Parameter value: value func insertToTail(value: T) { let node: Node = Node.init(value: value) self.insertToTail(node: node) } /// 在尾部插入結點 /// /// - Parameter node: node func insertToTail(node: Node<T>) { var tailNode: Node = self.dummy while let nextNode = tailNode.next { tailNode = nextNode } tailNode.next = node } /// 在指定結點的後面插入新value /// /// - Parameters: ///- value: 新值 ///- afterNode: 指定結點 /// - Returns: true or false func insert(value: T, afterNode: Node<T>) -> Bool { let node: Node = Node.init(value: value) return self.insert(node: node, afterNode: afterNode) } /// 在指定結點的後面插入新結點 /// /// - Parameters: ///- value: 新結點 ///- afterNode: 指定結點 /// - Returns: true or false func insert(node: Node<T>, afterNode: Node<T>) -> Bool { guard self.isContain(node: afterNode) else { return false } node.next = afterNode.next afterNode.next = node return true } /// 在指定結點的前面插入新value(雙向連結串列實現這種插入方式速度比單向連結串列快) /// /// - Parameters: ///- value: 新值 ///- beforNode: 指定結點 /// - Returns: true or false func insert(value: T, beforNode: Node<T>) -> Bool { let node: Node = Node.init(value: value) return self.insert(node: node, beforNode: beforNode) } /// 在指定結點的前面插入新結點(雙向連結串列實現這種插入方式速度比單向連結串列快) /// /// - Parameters: ///- node: 新結點 ///- beforNode: 指定結點 /// - Returns: true or false func insert(node: Node<T>, beforNode: Node<T>) -> Bool { var tempNode: Node = self.dummy while let nextNode = tempNode.next { if nextNode === beforNode { node.next = beforNode tempNode.next = node return true } tempNode = nextNode } return false } /// 刪除指定value的結點 /// /// - Parameter value: value /// - Returns: true or false func delete(value: T) -> Bool { var tempNode: Node = self.dummy while let nextNode = tempNode.next { // 此處判斷 == 是否合理 if nextNode.value == value { tempNode.next = nextNode.next return true } tempNode = nextNode } return false } /// 刪除指定的結點 /// /// - Parameter node: node /// - Returns: true or false func delete(node: Node<T>) -> Bool { var tempNode = self.dummy while let nextNode = tempNode.next { if nextNode === node { tempNode.next = nextNode.next return true } tempNode = nextNode } return false } /// 刪除指定下標的結點 /// /// - Parameter index: index /// - Returns: true or false func delete(index: Int) -> Bool { var idx: Int = 0 var tempNode: Node = self.dummy while let nextNode = tempNode.next { if index == idx { tempNode.next = nextNode.next return true } tempNode = nextNode idx = idx + 1 } return false } /// 查詢指定值的node /// /// - Parameter value: value /// - Returns: node func find(value: T) -> Node<T>? { var tempNode = self.dummy while let nextNode = tempNode.next { if nextNode.value == value { return nextNode } tempNode = nextNode } return nil } /// 查詢指定下標的結點 /// /// - Parameter index: index /// - Returns: node func find(index: Int) -> Node<T>? { var idx: Int = 0 var tempNode: Node = self.dummy while let nextNode = tempNode.next { if index == idx { return nextNode } tempNode = nextNode idx = idx + 1 } return nil } /// 判斷給定的連結串列是否在連結串列中 /// /// - Parameter node: node /// - Returns: true or false func isContain(node: Node<T>) -> Bool { var tempNode = self.dummy.next while tempNode != nil { if tempNode === node { return true } tempNode = tempNode?.next } return false } /// 單向連結串列反轉:方式一非遞迴實現 /// /// - Parameter chainList: 源連結串列 /// - Returns: 反轉後的連結串列 func reverseList() { var prevNode: Node<String>? = self.dummy.next var curNode: Node<String>? = prevNode?.next var tempNode: Node<String>? = curNode?.next prevNode?.next = nil while curNode != nil { tempNode = curNode?.next curNode?.next = prevNode prevNode = curNode curNode = tempNode } self.dummy.next = prevNode } /// 單向連結串列反轉:方式二遞迴實現 /// /// - Parameter chainList: 源連結串列 /// - Returns: 反轉後的連結串列 func reverseListUseRecursion(head: Node<T>?, isFirst: Bool) { var tHead = head if isFirst { tHead = self.dummy.next } guard let rHead = tHead else { return } if rHead.next == nil { self.dummy.next = rHead return } else { self.reverseListUseRecursion(head:rHead.next, isFirst: false) rHead.next?.next = rHead rHead.next = nil } } } class LinkedListVC: UIViewController { var chainList: SingleChainList = SingleChainList.init() override func viewDidLoad() { super.viewDidLoad() // 初始化連結串列 for i in 0..<10 { let node: Node = Node.init(value: String(i)) chainList.insertToTail(node: node) } // 查詢結點 for i in 0..<12 { if let find: Node = chainList.find(index: i) { debugPrint("find = \(find.value)") } else { debugPrint("not find idx = \(i)") } } // 刪除結點 if chainList.delete(index: 10) { debugPrint("刪除 index = \(index)成功") } else { debugPrint("刪除 index = \(index)失敗") } // 列印結點value資訊 debugPrint(chainList.description) // 列印結點個數 debugPrint(chainList.count) // 單向連結串列反轉 chainList.reverseList() // 列印結點value資訊 debugPrint(chainList.description) // 單向連結串列反轉 chainList.reverseListUseRecursion(head: nil, isFirst: true) // 列印結點value資訊 debugPrint(chainList.description) } } 複製程式碼