三国杀武将|手游三国杀边锋版

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

米米狗 2019-05-20 15:33:33 4256
本文來自 在路上重名了啊 ,作者 米米狗

[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有點偏激)耍花槍的東西,同一個規范,方便團隊合作和閱讀他人代碼。

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

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

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

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

image.png

2.1 定義

class Class {
    // class definition goes here
}

struct Structure {
    // structure definition goes here
}

2.2 值 VS 引用

1、結構體 struct 和枚舉 enum 是值類型,類 class 是引用類型。

2、String, Array和 Dictionary都是結構體,因此賦值直接是拷貝,而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:官方文檔

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

相比于OC,Swift 可以做到protocol協議方法的具體默認實現(通過extension)相比多態更好的實現了代碼復用,而 OC 則不行。

四、面向協議(面向接口)與面向對象的區別

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


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

image.png

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

總結:

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

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中,可以使用帶泛型的容器也可以解決這個問題,但是只是??,程序運行期間仍可能由于此問題導致的崩潰

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,必須指定類型,否則不是嘆號,而是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 的關系可能會是這樣的:

image.png

如果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)
    }
}

參考博客

1、面向協議編程與 Cocoa 的邂逅 (上)2、面向協議編程與 Cocoa 的邂逅 (下)3、[譯] Swift 面向協議編程入門

淺談Swift和OC的區別

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