談談Swift面向協議編程

CarrieDvi 7年前發布 | 30K 次閱讀 Swift Apple Swift開發

從一個具體需求說起

應用中有多個頁面內的 UICollectionViewCell 需要實現一個相同的小動畫:被選中時,先縮小到原來的0.8倍,再回彈到0.9倍。動畫本身實現起來不難:

func selectWithBounce(select:Bool, animated:Bool = true){
        let bounce = CAKeyframeAnimation(keyPath: "transform")

    let origin = CATransform3DIdentity
    let smallest = CATransform3DMakeScale(0.8, 0.8, 1)
    let small = CATransform3DMakeScale(0.9, 0.9, 1)

    let originValue = NSValue(CATransform3D: origin)
    let smallestValue = NSValue(CATransform3D:smallest)
    let smallValue = NSValue(CATransform3D:small)

    if animated {
        bounce.duration = 0.2
        bounce.removedOnCompletion = false
        if select {
            bounce.values = [originValue, smallestValue, smallValue]
            self.layer.addAnimation(bounce, forKey: "bounce")
        }else{
            bounce.values = [smallestValue, originValue]
            self.layer.addAnimation(bounce, forKey: "bounce")
        }
    }
    if select {
        self.layer.transform = small
    }else{
        self.layer.transform = origin
    }
}</code></pre> 

然而不同的頁面有不同的 UICollectionViewCell 子類,怎樣方便地讓它們都能復用這個動畫實現呢?

面向對象的復用方式

  • 繼承

如果用面向對象的思維解決問題,最容易想到的就是定義個一個繼承自 UICollectionViewCell 的父類,實現這個動畫,然后所有需要這個動畫的cell都繼承它。

能解決問題,但缺點也很明顯:如果再來一個實現其他功能的方法需要復用,就沒有辦法了。Swift/Objective-C只能單繼承,如果把一段實現另一個功能的代碼也放到這個父類里,就引入了不必要的耦合。繼承一個類,就必須接受這個類的所有代碼,兩部分代碼沒有關系卻必須捆綁銷售,代碼就開始僵化了。

有的項目里定義了繼承 UIViewController 的父類,實現了很多功能,要求項目里所有的頁面都要繼承它。這種僵化的毛病就很明顯了,下面的子類代碼全都依賴這個父類,想抽出來復用非常難。

  • 組合

組合優于繼承這是老生常談了,用在這里就要定義一個類專門負責做這個動畫,然后把 layer 作為方法參數或者成員變量傳進來。缺點嘛,就是有點麻煩。這種小工具類多了,要寫很多膠水代碼。

  • Extension/Category

嚴格意義上講這個不能算典型的面向對象,而是Swift/Objective-C特有的功能。實現就不說了。缺點也是Extension/Category固有的,給一個類加上這東西會污染所有的對象,即使他們根本不需要這個功能。

面向協議的復用方式

協議擴展這個特性的引入,使得Swift支持了一種新的編程范式:面向協議編程。針對某個要實現的功能,我們可以使用協議定義出接口,然后利用協議擴展提供默認的實現。

上代碼:

protocol BounceSelect {
    func selectWithBounce(select:Bool, animated:Bool)
}

extension BounceSelect where Self:UICollectionViewCell {

    func selectWithBounce(select:Bool, animated:Bool = true){
        let bounce = CAKeyframeAnimation(keyPath: "transform")

        let origin = CATransform3DIdentity
        let smallest = CATransform3DMakeScale(0.8, 0.8, 1)
        let small = CATransform3DMakeScale(0.9, 0.9, 1)

        let originValue = NSValue(CATransform3D: origin)
        let smallestValue = NSValue(CATransform3D:smallest)
        let smallValue = NSValue(CATransform3D:small)

        if animated {
            bounce.duration = 0.2
            bounce.removedOnCompletion = false
            if select {
                bounce.values = [originValue, smallestValue, smallValue]
                self.layer.addAnimation(bounce, forKey: "bounce")
            }else{
                bounce.values = [smallestValue, originValue]
                self.layer.addAnimation(bounce, forKey: "bounce")
            }
        }
        if select {
            self.layer.transform = small
        }else{
            self.layer.transform = origin
        }
    }
}

這樣,一個 UICollectionViewCell 需要這個功能,只需要聲明遵守了這個協議即可,其他什么都不用做,直接就可以調用 func selectWithBounce(select:Bool, animated:Bool) 這個方法:

class XYZCollectionViewCell : UICollectionViewCell, BounceSelect {
    ...
}

let cell: XYZCollectionViewCell
cell.selectWithBounce(true)

遵守某個協議的類的對象調用協議聲明的方法時,如果類本身沒有提供實現,協議擴展提供的默認實現會被調用。

事實上,我們還可以進一步解除耦合。這個方法依賴的只有layer,不一定要是UICollectionViewCell。

另外我們可以允許用戶自定義動畫的時長,并提供默認的時長。

稍微改下代碼:

protocol BounceSelect {
    var layer:CALayer {get}
    var animationDuration:NSTimeInterval {get}
    func selectWithBounce(select:Bool, animated:Bool)

}

extension BounceSelect {

    var animationDuration:NSTimeInterval {
        return 0.2
    }

    func selectWithBounce(select:Bool, animated:Bool = true){
        let bounce = CAKeyframeAnimation(keyPath: "transform")

        let origin = CATransform3DIdentity
        let smallest = CATransform3DMakeScale(0.8, 0.8, 1)
        let small = CATransform3DMakeScale(0.9, 0.9, 1)

        let originValue = NSValue(CATransform3D: origin)
        let smallestValue = NSValue(CATransform3D:smallest)
        let smallValue = NSValue(CATransform3D:small)

        if animated {
            bounce.duration = animationDuration
            bounce.removedOnCompletion = false
            if select {
                bounce.values = [originValue, smallestValue, smallValue]
                self.layer.addAnimation(bounce, forKey: "bounce")
            }else{
                bounce.values = [smallestValue, originValue]
                self.layer.addAnimation(bounce, forKey: "bounce")
            }
        }
        if select {
            self.layer.transform = small
        }else{
            self.layer.transform = origin
        }
    }
}

這個協議通過 var layer:CALayer {get} 定義了接入的條件,通過 func selectWithBounce(select:Bool, animated:Bool) 方法的聲明和實現,定義了它的功能。它就像一個插件,不管是 UIView 還是 NSView,只要提供一個 CALayer 屬性,都可以方便地接入這個功能。這樣就可以在不同的類之間復用這段代碼了,解除了對類型的依賴。這是繼承和

Extension/Category都無法做到的。

如果是CALayer本身呢?很簡單:

class XYZLayer : CALayer, BounceSelect {

    //如果想修改動畫時長就重載這個屬性
    var animationDuration:NSTimeInterval {
        return 0.3
    }
    var layer:CALayer {
        return self
    }
}

面向協議編程的好處在于,通過協議+擴展實現一個功能,能夠定義所需要的 充分必要 條件,不多也不少。這樣就最大程度減少了耦合。使用者可以像搭積木一樣隨意組合這些協議,寫一個class或struct來完成復雜的功能。實際上,Swift的標準庫幾乎是everything is starting out as a protocol。

傳統的協議(比如Objective-C的protocol,Java的Interface)只能定義接口,不能復用實現,遵守同一個協議的不同的類,只能分別實現協議接口,使用場景受限了很多。Swift只是多了一個協議擴展的特性,但卻帶來了編程范式的進化。

 

來自:http://www.jianshu.com/p/4544b8837556

 

 本文由用戶 CarrieDvi 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!