79
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SwiftのProtocol指向の実装例

Posted at

はじめに

いまプロトコル指向で実装するのが流行りということでどういうのか調べていましたが、WWDCの動画だと自分には難しかったので、ちょうどわかりやすい例がはてブにでていたので訳してみました

以下翻訳

Swiftのプロトコル拡張とジェネリクスを使ってiOSのセルの登録と再利用する

A common task when developing iOS apps is to register custom cell subclasses for both UITableView and UICollectionView. Well, that is if you don’t use Storyboards, of course.
iOSアプリを開発している時によくあるタスクとして、Storyboardを使わないのであれば、'UITableView'や
'UICollectionView'のためにカスタムセルサブクラスを実装する

Both UITableView and UICollectionView offer a similar API to register custom cell classes:
UITableViewUICollectionViewは似たようなカスタムセルクラスの登録APIを提供している

public func registerClass(cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String)
public func registerNib(nib: UINib?, forCellWithReuseIdentifier identifier: String)

A widely accepted solution to handle cell registration and dequeuing is to declare a constant for the reuse identifier:
広く受け入れられた解決方法はreuseIdentifierを宣言してセルの登録処理とdequeuingを実装する

private let reuseIdentifier = "BookCell"

class BookListViewController: UIViewController, UICollectionViewDataSource {

    @IBOutlet private weak var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let nib = UINib(nibName: "BookCell", bundle: nil)
        self.collectionView.registerNib(nib, forCellWithReuseIdentifier: reuseIdentifier)
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
    
        if let bookCell = cell as? BookCell {
            // TODO: configure cell
        }
    
        return cell
    }
}

Let’s try to generalize this code and make it simpler and safe.
じゃあこのコードをより単純に安全にしてみよう!

First of all, it would be nice to get away with declaring a constant for every reuse identifier in our app. We can just use the name of the custom cell class as a default reuse identifier.
We can create a protocol for Reusable Views and provide a default implementation constrained to UIView subclasses.
まず、アプリ内でいちいちreuseIdentifierを宣言するのをやめるのがよさそうだ。
私たちはデフォルトのreuseIdentifierとしてカスタムセルのクラス名を使うことにする。
なので再利用できるViewのためのプロトコルを作って、UIViewのサブクラスに強制的にその機能を提供することができる

protocol ReusableView: class {
    static var defaultReuseIdentifier: String { get }
}

extension ReusableView where Self: UIView {
    static var defaultReuseIdentifier: String {
        return NSStringFromClass(self)
    }
}

extension UICollectionViewCell: ReusableView {
}

By making UICollectionViewCell conform to the ReusableView protocol, we get a unique reuse identifier per cell subclass.
UICollectionViewCellはReuseableViewプロトコルに従うことによって、それぞれのセルのサブクラスから固有のreuseIdentifierを取得することができる

let identifier = BookCell.defaultReuseIdentifier
// identifier = "MyModule.BookCell"

Next, we can get rid of the hard-coded string we are using to load the Nib.
次に、Nibを読み込むためにハードコードしてる文字列を取り除くことができる

Let’s create a protocol for Nib Loadable Views and provide a default implementation using protocol extensions.
じゃあNibを読み込むためのViewsためのプロコトルを作って、プロトコル拡張を使ってその機能を提供できるようにしよう!

protocol NibLoadableView: class {
    static var nibName: String { get }
}

extension NibLoadableView where Self: UIView {
    static var nibName: String {
        return NSStringFromClass(self).componentsSeparatedByString(".").last!
    }
}

extension BookCell: NibLoadableView {
}

By making our BookCell class conform to the NibLoadableView protocol we now have a safer way to get the Nib name.
このBookCellクラスはNibLoadeableViewに従うことで、より安全にNibの名前を手に入れることができる

let nibName = BookCell.nibName
// nibName = "BookCell"

If you use a different name for the XIB file than the one provided by Xcode, you can always override the default implementation of the nibName property.
Xcodeから提供されたXIBファイルに違う名前を使いたいなら、いつでもnibNameプロパティの機能をオーバーライドできる

With these two protocols in place, we can use Swift Generics and extend UICollectionView to simplify cell registration and dequeuing.

この2つのプロトコルを使って、セルの登録処理とdequeuingをUICollectionViewを簡単に拡張し、Swiftジェネリクスを使える

extension UICollectionView {
    
    func register<T: UICollectionViewCell where T: ReusableView>(_: T.Type) {
        registerClass(T.self, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
    }
    
    func register<T: UICollectionViewCell where T: ReusableView, T: NibLoadableView>(_: T.Type) {
        let bundle = NSBundle(forClass: T.self)
        let nib = UINib(nibName: T.nibName, bundle: bundle)
        
        registerNib(nib, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
    }
    
    func dequeueReusableCell<T: UICollectionViewCell where T: ReusableView>(forIndexPath indexPath: NSIndexPath) -> T {
        guard let cell = dequeueReusableCellWithReuseIdentifier(T.defaultReuseIdentifier, forIndexPath: indexPath) as? T else {
            fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
        }
        
        return cell
    }    
}

Notice that we created two versions of the register method, one for cell subclasses implementing just ReusableView and another one for cell subclasses implementing both ReusableView and NibLoadableView. This nicely decouples the view controller from the specific cell registration method.
登録メソッドの2つのバージョンを作ってみて、セルのサブクラスの実装の1つは単にReusableViewを実装しているもの、もう片方がReusableViewNibLoadableViewの2つを実装してるものがあることに気づく。
これは特定のセルの登録メソッドからViewControllerをうまく分離している

Another nice detail is that the dequeueReusableCell method doesn’t need to take any reuse identifier and uses the cell subclass type for the return value.
ほかにもdequeueReusableCellメソッドはreuseIdentifierをとる必要がなく、セルのサブクラスの返り値を使っているのはいいdetailだ

Now the cell registration and dequeuing code looks much better :).
セルの登録処理とdequeuingはいまや見違えるようだ( ・`ω・´)

class BookListViewController: UIViewController, UICollectionViewDataSource {

    @IBOutlet private weak var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.collectionView.register(BookCell.self)
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        
        let cell: BookCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
        
        // TODO: configure cell
    
        return cell
    }
    ...
}

Conclusion

If you are coming from Objective-C it is worth to investigate powerful Swift features like Protocol Extensions and Generics to find alternate and more elegant ways to deal with Cocoa classes.

##最後に
もし君がObjective-Cから来たのであれば、SwiftのすばらしくパワフルなProtocolやGenericsといった機能を調べたり、違いを見つけたり、Cocoaクラスによりエレガントな方法を適用するのは有意義だろう


以上翻訳

圧倒的感謝 :v::sob: :v:

79
80
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
79
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?