クラスを拡張する【Swift 4】
Swiftではクラスを拡張するextensionという機能があります。extensionを利用すると、新しいクラスを作成することなく既存のクラスに機能を付加することができます。またdelegateやdatasourceを継承した部分については、extensionを使って元のクラスとは別に記述することもできます。Swift 4以降では、privateで定義したプロパティにも、extensitonで拡張した部分からアクセスできるようになりました。extension自体はSwift 4以前に実装された機能ですが、アプリ開発を行う上で知っておくべき重要な機能なのでサンプルをあげながら説明します。
既存クラスにメソッドを追加する
既存のクラスに開発者が作成したメソッドを追加することで、新しいクラスを作成することなくXcodeのプロジェクト全体で共通して機能を利用することができます。UIImageを円で表示したい場合、次のようにUIImageクラスを拡張することで機能を実装できます。
extension UIImage { // ---------(1)
// UIImageを円形に
func circle() -> UIImage { // ---------(2)
// 縦横小さい方を優先して矩形を定義
let rect = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
// UIImageViewを生成して矩形の半分のサイズで角丸に
let imageView = UIImageView(frame: CGRect(origin: .zero, size: rect)) // ---------(3)
imageView.contentMode = .scaleAspectFill
imageView.image = self
imageView.layer.cornerRadius = rect.width/2
imageView.layer.masksToBounds = true
// UIImageViewから画像として出力
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale) // ---------(4)
let context = UIGraphicsGetCurrentContext()
imageView.layer.render(in: context!)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result!
}
}
UIImageクラスを拡張することをextension修飾子で宣言します(1)。実装したい機能を通常のメソッドと同様に定義します(2)。UIImageを円形にするためには、いったん形状の変更が容易なUIImageViewを生成し(3)、UIImageViewのサイズが半分のところで角丸にします。角丸になった矩形の4隅が組み合わさって円ができます。そのあとにUIGraphicsGetCurrentContextの機能を使って画像として出力します(4)。
作成したメソッドを実際に利用する際には、UIImageオブジェクトの後ろでcircle()メソッドを実行します。
// UIImageを初期化 let image = UIImage(named:"cat")?.circle() // 作成したcircle() メソッドを実行
サンプルの実行結果は次の通りです。
既存のクラスを拡張したメソッドの実装なので、特別な処理は必要なく、既存クラスのメソッドと同じように利用できます。extensionをうまく使うことで、余計なクラスを作成することなく既存のクラスを拡張して機能を作成可能です。
ソースコードを分離して記述する
delegateやdatasourceを実装した部分もクラスのextensionで扱うことができます。このことを利用すると、delegateやdatasourceを実装する際には、extensionを使って本来のクラスとは分けてソースコードを記述可能です。サンプルの最初のページで表示しているUITableViewをUIViewControllerクラスで実装する際には、次のようにソースコードを分けて記述できます。
class ViewController: UIViewController {
private let Identifier = "Cell"
private let items = ["UIImageViewサンプル", "MKMapViewサンプル"]
# 中略
// ViewControllerクラスの画面表示等の本来の処理のみを記述
}
// UITableViewDelegateを実装した部分
extension ViewController : UITableViewDelegate { // ---------(1)
// テーブルのセル選択時の処理
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // ---------(2)
// Main.storyboardを取得
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let row = indexPath.row
if row == 0 {
// CatNavigationViewControllerのIDのViewControllerを取得してモーダル表示
let vc = storyBoard.instantiateViewController(withIdentifier: "CatNavigationViewController")
self.present(vc, animated: true, completion: nil)
}else{
// MapViewControllerのIDのViewControllerを取得して画面遷移
let mapVC = storyBoard.instantiateViewController(withIdentifier: "MapViewController")
self.navigationController?.pushViewController(mapVC, animated: true)
}
}
}
// UITableViewDataSourceを実装した部分
extension ViewController : UITableViewDataSource { // ---------(3)
// テーブルのセル数
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // ---------(4)
return items.count
}
// テーブルに表示するセル
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // ---------(5)
let cell = tableView.dequeueReusableCell(withIdentifier: self.Identifier)
cell?.textLabel?.text = items[indexPath.row]
return cell!
}
}
ViewControllerクラスでUITableViewDelegateとUITableViewDataSourceを実装した部分を、extensionで分けて記述します(1)(2)。実装するdelegateとdatasourceは、クラスに実装する際と同じようにクラス名のあとに「:」で区切って記述します。実装したUITableViewDelegateとUITableViewDataSourceのメソッドは、それぞれのextensionの中に記述します(3)(4)(5)。それ以外の部分に記述すると実行されないので気をつけてください。(4)(5)のUITableViewDataSourceのメソッド内では、ViewControllerクラスで定義した変数を参照しています。このようにextensionで拡張した部分からも、本来のクラスの変数やプロパティを参照することができます。
UITableViewDelegateとUITableViewDataSourceを実装した部分をextensionで分けて記述することで、元のクラスの処理と実装したdelegateとdatasourceの処理を分けることができます。これによってソースコード内のどこでどのような処理を行っているかがわかりやすくなり、ソースコードの保守性が上がります。
まとめ
今回はSwiftの簡単なプログラミング、追加された代表的な機能の確認を行いました。次回は実際にSwiftでクラスやメソッドの作る方法等について説明を行う予定です。
