Swiftプロトコル向けプログラミングについて

7297 ワード

具体的なニーズから言えば
アプリケーションに複数のページ内の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
        }
    }

しかし、異なるページには異なるUICollectionViewCellサブクラスがあり、どのようにしてこのアニメーションを多重化することができますか?
オブジェクト向けの多重化
  • 継承
  • オブジェクト向けの思考で問題を解決すると、UICollectionViewCellから継承されたクラスを定義することが最も考えられます.例えば、MYCollectionViewCellと呼ばれ、このアニメーションを実現し、このアニメーションを必要とするcellがすべて継承されます.
    問題を解決できますが、欠点も明らかです.継承は結合をもたらしやすいです.
    たとえば、UICollectionViewCellにもう1つの機能を追加する必要があります.この機能のインタフェースは、ABCに依存する他の3つのクラスを宣言します.Swift/Objective-Cは単に継承するしかなく,このMYCollectionViewCellに新しい機能実装も入れれば不要な結合が導入される.このとき我々は,このアニメーション機能を用いて,MYCollectionViewCellに依存するとともに,アニメーションとは無関係のABCの3つのクラスに依存したい.コードが硬直し始めた.UIViewControllerを継承する親クラスを定義したプロジェクトもあり、多くの機能を実現し、プロジェクト内のすべてのページが継承されるように要求されています.この硬直化の欠点は明らかで,次のサブクラスコードはすべてこの親に依存しており,抽出多重化は非常に難しい.また、このカスタムUIViewControllerにコードを挿入するのは本当に便利で、このクラスは機能反復に伴って徐々に膨張しやすく、メンテナンスがますます難しくなっています.
    また,1つのUITableViewCellにもこの機能が必要であれば,使用継承は実現できない.UITableViewCellUICollectionViewCellはすでにUIViewの異なるサブクラスであり、UIViewを変更しない限り、同時に機能を追加することはできません.しかし、UIViewの実装ファイルは入手できません.
  • Extension/Category

  • 厳密にはこれは典型的なオブジェクト向けではなく、Swift/Objective-C特有の機能です.これを使用すると、クラスのインプリメンテーションファイルを変更せずに、新しいメソッドを追加できます.UIViewにcategoryを追加することによって、UITableViewCellUICollectionViewCellが同時に機能を増加させる問題を解決することができます.欠点もExtension/Category固有で、クラスにこれを加えると、この機能が必要なくてもすべてのオブジェクトを汚染します.Objective-Cの場合、1つのファイルにimportというcategoryがない場合でもruntimeを使用してcategoryのメソッドにアクセスできます.
  • 組合せ
  • コンビネーションが継承より優れているのは常識で、ここでこのアニメーションを担当するクラスを定義し、layerをメソッドパラメータまたはメンバー変数として渡します.欠点は、ちょっと面倒です.この小道具類は多くなったので,接着コードをたくさん書かなければならない.
    プロトコル向けの多重化方式
    プロトコル拡張という特性の導入により,Swiftはプロトコル向けプログラミングという新しいプログラミングモデルをサポートした.実装する機能については、プロトコルを使用してインタフェースを定義し、プロトコル拡張を使用してデフォルトの実装を提供できます.
    上のコード:
    protocol BounceSelect {
        func selectWithBounce(select:Bool, animated:Bool)
    }
    
    extension BounceSelect where Self:UIView {
        
        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またはUITableViewCellはこの機能を必要とし、このプロトコルを遵守したことを宣言するだけで、他のことは何もしなくても、func selectWithBounce(select:Bool, animated:Bool)という方法を直接呼び出すことができます.
    class XYZCollectionViewCell : UICollectionViewCell, BounceSelect {
        ...
    }
    
    let cell: XYZCollectionViewCell
    cell.selectWithBounce(true)
    

    プロトコルのクラスのオブジェクトがプロトコル宣言を呼び出す方法を遵守する場合、クラス自体が実装を提供していない場合、プロトコル拡張が提供するデフォルトの実装が呼び出されます.
    実際には,結合をさらに解除することもできる.この方法はlayerのみに依存し,必ずしもUIViewであるとは限らず,NSViewであってもよいし,CALayer自体であってもよい.
    また、アニメーションの時間をカスタマイズし、デフォルトの時間を指定することもできます.
    コードを少し変更:
    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)方法の宣言と実装によって、その機能を定義する.これはプラグインのようなもので、CALayerプロパティを1つ提供すれば、どのクラスもこの機能に簡単にアクセスできます.これにより、異なるクラス間でこのコードを多重化し、タイプへの依存を解除することができます.これは継承もExtension/Categoryもできないことです.
    CALayerそのものなら?簡単:
    class XYZLayer : CALayer, BounceSelect {
    
        //                
        var animationDuration:NSTimeInterval {
            return 0.3
        }
        var layer:CALayer {
            return self
        }
    }
    

    プロトコルプログラミング向けの利点は,プロトコル+拡張により1つの機能を実現し,必要な十分な必要条件を定義できることであり,多くも少なくない.これにより結合が最小限に抑えられる.利用者は積み木のようにこれらのプロトコルを任意に組み合わせてclassやstructを書いて複雑な機能を完成させることができる.実際、Swiftの標準ライブラリはほとんどeverything is starting out as a protocolです.
    従来のプロトコル(例えばObjective-Cのprotocol,JavaのInterface)はインタフェースしか定義できず、実装を多重化できず、同じプロトコルの異なるクラスを遵守し、それぞれプロトコルインタフェースしか実現できず、使用シーンが制限されている.Swiftはプロトコル拡張の特性が1つ増えただけで、プログラミングモデルの進化をもたらした.