歡迎光臨
每天分享高質量文章

來一次有側重點的區分Swift與Objective-C

作者:在路上重名了啊
鏈接:https://juejin.im/post/5c653aa6e51d457fbf5dc298

[TOC]

@(swift)[溫故而知新]

面試中經常被問到Objective-CSwift的區別,其實區別還是很多的,重點整理一下個人覺得很重要的:面向協議編程。

一、Objective-C與Swift的異同

1.1、swift和OC的共同點:

 

  • OC出現過的絕大多數概念,比如取用計數ARC(自動取用計數)、屬性協議接口初始化擴展類命名引數匿名函式等,在Swift中繼續有效(可能最多換個術語)。

  • SwiftObjective-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的標準陣列支持三個高階函式:mapfilterreduce,以及map的擴展flatMap)

  • – swift新增了兩種權限,細化權限。open > public > internal(預設) > fileprivateprivate

  • – swift中獨有的元組型別(tuples),把多個值組合成複合值。元組內的值可以是任何型別,並不要求是相同型別的。

 

1.3、swift的不足:

 

  • – 版本不穩定

  • – 公司使用比例不高,使用人數比例偏低

  • – 有些語法其實可以只規定一種格式,不必這樣也行,那樣也行。像Go一樣禁止一切(Go有點偏激)耍花槍的東西,同一個規範,方便團隊合作和閱讀他人代碼。

 

Swift 跟 JavaScript 有什麼相同和不同點?

https://www.zhihu.com/question/24003100

從資料結構角度,Golang和Swift對比,有何優缺點?

https://www.zhihu.com/question/27746582

iOS——Objective-C與Swift優缺點對比

https://icocos.github.io/2017/09/19/iOS——Objective-C與Swift優缺點對比/

二、swift類(class)和結構體(struct)的區別

區別 class struct
定義屬性用於儲存值
定義方法用於提供功能
定義附屬腳本用於訪問值
定義建構式用於生成初始化值
通過擴展以增加預設實現的功能
遵守協議以對某類提供標準功能
是否可以繼承
是否可以取用計數
型別轉換
析構方法釋放資源

 

2.1 定義


class Class {
    // class definition goes here
}

struct Structure {
    // structure definition goes here
}

2.2 值 VS 取用

 

1、結構體 struct 和列舉 enum 是值型別,類 class 是取用型別。2、String, ArrayDictionary都是結構體,因此賦值直接是拷貝,而NSString, NSArray NSDictionary則是類,所以是使用取用的方式。3、struct class 更“輕量級”,struct 分配在棧中,class 分配在堆中。

2.3 指標

 

如果你有 C,C++ 或者 Objective-C 語言的經驗,那麼你也許會知道這些語言使用指標來取用記憶體中的地址。一個 Swift 常量或者變數取用一個取用型別的實體與 C 語言中的指標類似,不同的是並不直接指向記憶體中的某個地址,而且也不要求你使用星號(*)來表明你在創建一個取用。Swift 中這些取用與其它的常量或變數的定義方式相同。

2.4 選擇使用類和結構體

使用struct任何情況下,優先考慮使用struct,如果滿足不了,再考慮class

  • – 比如資料被多執行緒使用,而且資料沒有使用class的必要性,就使用struct

  • – 希望實體被拷貝時,不收拷貝後的新實體的影響

  • – 幾何圖形的大小,可以封裝width和height屬性,都是Double型別

  • – 指向連續序列範圍的方法,可以封裝start和length屬性,都是Int型別

  • – 一個在3D坐標系統的點, 可以封裝x, y和z屬性,都是Double型別


使用class

  • – 需要繼承

  • – 被遞迴呼叫的時候(參考鏈表的實現,node選用class而不是struct

  • – 屬性資料複雜

  • – 希望取用而不是拷貝

 


參考鏈接1:類和結構體參考鏈接2:官方文件

https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-ID82

三、Objective-C中的protocol與Swift中的protocol的區別

相比於OCSwift 可以做到protocol協議方法的具體預設實現(通過extension)相比多型更好的實現了代碼復用,而 OC 則不行。

四、面向協議(面向接口)與面向物件的區別

面向物件面向協議的的最明顯區別是對抽象資料的使用方式,面向物件採用的是繼承,而面向協議採用的是遵守協議。在面向協議設計中,Apple建議我們更多的使用 值型別struct)而非 取用型別class)。這篇文章中有一個很好的例子說明瞭面向協議比面向物件更符合某些業務需求。其中有飛機、汽車、自行車三種交通工具(均繼承自父類交通工具);老虎、馬三種動物(均繼承父類自動物);在古代馬其實也是一種交通工具,但是父類是動物,如果馬也有交通工具的功能,則:


如果採用面向物件編程,則需要既要繼承動物,還要繼承交通工具,但是父類交通工具有些功能馬是不需要的。由此可見繼承,作為代碼復用的一種方式,耦合性還是太強。事物往往是一系列特質的組合,而不單單是以一脈相承並逐漸擴展的方式構建的。以後慢慢會發現面向物件很多時候其實不能很好地對事物進行抽象。

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

總結:

面向協議相對於面向物件來說更具有可伸縮性可重用性,並且在編程的過程中更加模塊化,通過協議以及協議擴展替代一個龐大的基類,這在大規模系統編程中會有很大的便捷之處。

3.1、協議協議擴展基類有三個明顯的優點:

 

  • 1、1、型別可以遵守多個協議但是只有一個基類。這意味著型別可以隨意遵守任何想要特性的協議,而不需要一個巨大的基類。

  • 2、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-CXcode7中,可以使用帶泛型的容器也可以解決這個問題,但是只是⚠️,程式運行期間仍可能由於此問題導致的崩潰


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'
NSArray> *array = @[v1, v2];
for (id obj in array) {
    [obj func];
}

使用swift,必須指定型別,否則不是⚠️,而是❌,所以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 ViewControllerP { }
extension AnotherViewControllerP { }

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)
    func insertToHead(value: T)
    func insertToTail(node: Node)
    func insertToTail(value: T)
    func insert(node: Node, afterNode: Node) -> Bool
    func insert(value: T, afterNode: Node) -> Bool
    func insert(node: Node, beforNode: Node) -> Bool
    func insert(value: T, beforNode: Node) -> Bool

    /// 刪除(預設第一個符合條件的)
    @discardableResult func delete(node: Node) -> 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?
    func find(index: Int) -> Node?

    /// 判斷結點是否在鏈表中
    func isContain(node: Node) -> 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 {
    var value: T
    var next: Node?

    /// 便利構造方法
    ///
    /// - Parameter value: value
    convenience init(value: T{
        self.init(valuevalue, 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 SingleChainListChainListAble {
    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(valuevalue)
        self.insertToHead(node: node)
    }
    /// 在頭部插入結點
    ///
    /// - Parameter node: node
    func insertToHead(node: Node{
        node.next = self.dummy.next
        self.dummy.next = node
    }
    /// 在尾部插入值
    ///
    /// - Parameter value: value
    func insertToTail(value: T{
        let node: Node = Node.init(valuevalue)
        self.insertToTail(node: node)
    }
    /// 在尾部插入結點
    ///
    /// - Parameter node: node
    func insertToTail(node: Node{
        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) -> Bool {
        let node: Node = Node.init(valuevalue)
        return self.insert(node: node, afterNode: afterNode)
    }
    /// 在指定結點的後面插入新結點
    ///
    /// - Parameters:
    ///   - value: 新結點
    ///   - afterNode: 指定結點
    /// - Returns: true or false
    func insert(node: Node, afterNode: Node) -> 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) -> Bool {
        let node: Node = Node.init(valuevalue)
        return self.insert(node: node, beforNode: beforNode)
    }
    /// 在指定結點的前面插入新結點(雙向鏈表實現這種插入方式速度比單向鏈表快)
    ///
    /// - Parameters:
    ///   - node: 新結點
    ///   - beforNode: 指定結點
    /// - Returns: true or false
    func insert(node: Node, beforNode: Node) -> 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) -> 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? {
        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? {
        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) -> 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? = self.dummy.next
        var curNode: Node? = prevNode?.next
        var tempNode: Node? = 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?, 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 LinkedListVCUIViewController {
    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)
    }
}
赞(0)

分享創造快樂