聊聊iOS中的多繼承和多重代理

Dariel發表於2018-10-19

多繼承和多重代理在swift的語言層面上是不支援的,但我們有時會遇到這樣的問題:

  1. 類B和C分別繼承自A,B1和B2繼承自B,C1和C2繼承自C.現在我們需要在B1和C1中新增相同的方法,怎麼去做?使用繼承的話只能在類A中新增,但這樣做的結果是基類A會越來越臃腫,最後變成上帝類God Class,維護起來會很困難.
  2. 在實現完某個代理後發現,我們還要在其他頁面中獲取資料.例如,IM訊息接收之後要在多個地方做回撥,比如顯示訊息內容頁面,改變小紅點,顯示訊息數.即一對多的模式,我們第一反應是用通知,但通知還是能少用就少用,用多了程式碼的可閱讀性會大大降低.

面對第一種情況,最好的解決方法是,B1和C1的公共方法專門封裝到一個地方,需要的時候就呼叫一下,多繼承就是一個最好的解決方案.

1. 多繼承

1. 實現過程

swift中的類可以遵守多個協議,但是隻可以繼承一個類,而值型別(結構體和列舉)只能遵守單個或多個協議,不能做繼承操作.

多繼承的實現:協議的方法可以在該協議的extension中實現

protocol Behavior {
    func run()
}
extension Behavior {
    func run() {
        print("Running...")
    }
}

struct Dog: Behavior {}

let myDog = Dog()
myDog.run() // Running...
複製程式碼

無論是結構體還是類還是列舉都可以遵守多個協議,所以要實現多繼承,無非就是多遵守幾個協議的問題.

下面舉個例子.

2. 通過多繼承為UIView擴充套件方法

// MARK: - 閃爍功能
protocol Blinkable {
    func blink()
}
extension Blinkable where Self: UIView {
    func blink() {
        alpha = 1
        
        UIView.animate(
            withDuration: 0.5,
            delay: 0.25,
            options: [.repeat, .autoreverse],
            animations: {
                self.alpha = 0
        })
    }
}

// MARK: - 放大和縮小
protocol Scalable {
    func scale()
}
extension Scalable where Self: UIView {
    func scale() {
        transform = .identity
        
        UIView.animate(
            withDuration: 0.5,
            delay: 0.25,
            options: [.repeat, .autoreverse],
            animations: {
                self.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
        })
    }
}

// MARK: - 新增圓角
protocol CornersRoundable {
    func roundCorners()
}
extension CornersRoundable where Self: UIView {
    func roundCorners() {
        layer.cornerRadius = bounds.width * 0.1
        layer.masksToBounds = true
    }
}

extension UIView: Scalable, Blinkable, CornersRoundable {}

 cyanView.blink()
 cyanView.scale()
 cyanView.roundCorners()
複製程式碼
聊聊iOS中的多繼承和多重代理

這樣,如果我們自定義了其他View,只需要放大和縮小效果,遵守Scalable協議就可以啦!

3. 多繼承鑽石問題(Diamond Problem),及解決辦法

請看下面程式碼

protocol ProtocolA {
    func method()
}

extension ProtocolA {
    func method() {
        print("Method from ProtocolA")
    }
}

protocol ProtocolB {
    func method()
}

extension ProtocolB {
    func method() {
        print("Method from ProtocolB")
    }
}

class MyClass: ProtocolA, ProtocolB {}
複製程式碼

此時ProtocolAProtocolB都有一個預設的實現方法method(),由於編譯器不知道繼承過來的method()方法是哪個,就會報錯.

?鑽石問題Diamond Problem,當某一個類或值型別在繼承圖譜中有多條路徑時就會發生.

解決方法:
1. 在目標值型別或類中重寫那個發生衝突的方法method().
2. 直接修改協議中重複的方法.

文章開頭我們提到的問題2,我們可以試著用多重代理去解決這個問題.

2. 多重代理

1. 多重代理的實現過程

我們以一個代理的經典問題來表述:
主人叫寵物們去吃飯,吃這個動作作為一個協議,我們要做到統一管理.

1. 定義協議
protocol MasterOrderDelegate: class {
    func toEat(_ food: String)
}
複製程式碼
2. 定義一個類: 用來管理遵守協議的類

這邊用了NSHashTable來儲存遵守協議的類,NSHashTableNSSet類似,但又有所不同,總的來說有這幾個特點:
1. NSHashTable中的元素可以通過Hashable協議來判斷是否相等.
2. NSHashTable中的元素如果是弱引用,物件銷燬後會被移除,可以避免迴圈引用.

class masterOrderDelegateManager : MasterOrderDelegate {
    private let multiDelegate: NSHashTable<AnyObject> = NSHashTable.weakObjects()

    init(_ delegates: [MasterOrderDelegate]) {
        delegates.forEach(multiDelegate.add)
    }
    
    // 協議中的方法,可以有多個
    func toEat(_ food: String) {
        invoke { $0.toEat(food) }
    }
    
    // 新增遵守協議的類
    func add(_ delegate: MasterOrderDelegate) {
        multiDelegate.add(delegate)
    }
    
    // 刪除指定遵守協議的類
    func remove(_ delegateToRemove: MasterOrderDelegate) {
        invoke {
            if $0 === delegateToRemove as AnyObject {
                multiDelegate.remove($0)
            }
        }
    }
    
    // 刪除所有遵守協議的類
    func removeAll() {
        multiDelegate.removeAllObjects()
    }

    // 遍歷所有遵守協議的類
    private func invoke(_ invocation: (MasterOrderDelegate) -> Void) {
        for delegate in multiDelegate.allObjects.reversed() {
            invocation(delegate as! MasterOrderDelegate)
        }
    }
}
複製程式碼
3. 其餘部分
class Master {
    weak var delegate: MasterOrderDelegate?
    func orderToEat() {
        delegate?.toEat("meat")
    }
}

class Dog {}
extension Dog: MasterOrderDelegate {
    func toEat(_ food: String) {
        print("\(type(of: self)) is eating \(food)")
    }
}

class Cat {}
extension Cat: MasterOrderDelegate {
    func toEat(_ food: String) {
        print("\(type(of: self)) is eating \(food)")
    }
}

let cat = Cat()
let dog = Dog()
let cat1 = Cat()

let master = Master()
// master的delegate是弱引用,所以不能直接賦值
let delegate = masterOrderDelegateManager([cat, dog])
// 新增遵守該協議的類
delegate.add(cat1)
// 刪除遵守該協議的類
delegate.remove(dog)

master.delegate = delegate
master.orderToEat()

// 輸出
// Cat is eating meat
// Cat is eating meat
複製程式碼

設定masterOrderDelegateManager的好處是,可以通過一個陣列來管理多重代理.

更多iOS相關知識點歡迎關注我的Github: SwiftTips

相關文章