➜ ~ swift --version
Apple Swift version 2.1 (swiftlang-700.1.101.6 clang-700.1.76)
Target: x86_64-apple-darwin15.0.0
##動機
プロパティ名が無駄に長い場合、入力補完があるとはいえ、いちいちself.longNameProperty.hoge = fuga
と書くのが面倒臭い。
短いプロパティ名をつけても、プロパティのプロパティが短い名前とは限らない。
self.longNameProperty = UIView(frame: CGRectMake(0,0,100,100))
self.longNameProperty.backgroundColor = UIColor.whiteColor()
self.longNameProperty.autoresizesSubviews = false
self.longNameProperty.layer.cornerRadius = 8
self.longNameProperty.layer.shadowColor = UIColor.blackColor().CGColor
self.longNameProperty.layer.borderColor = UIColor.redColor().CGColor
self.longNameProperty.layer.borderWidth = 5
短い名前の変数を作って、最後に代入することもできるが、複数のプロパティを設定しないといけない場合、同じスコープに短い似たような名前の変数がたくさんできてしまうのが嫌。
let v1 = UIView(frame: CGRectMake(0,0,100,100))
v1.backgroundColor = UIColor.whiteColor()
v1.autoresizesSubviews = false
v1.layer.cornerRadius = 8
v1.layer.shadowColor = UIColor.blackColor().CGColor
v1.layer.borderColor = UIColor.redColor().CGColor
v1.layer.borderWidth = 5
self.longNameProperty = v1
let v2 //...
let v3 //...
//同じスコープにv1, v2, ..みたいなのがたくさん出るのが嫌。
また、同じような初期化をするなら、関数化すれば良いだけだが、各プロパティで全く異なる初期化処理をする場合は関数化する意味がない。
##解決案
###実装方法(ジェネリクス版)
NSObject
を拡張してスコープを作るメソッドを生やしてみる。
extension NSObject {
func scope<T: NSObject>(block: (T)->Void) -> Self {
if let obj = self as? T {
block(obj)
}
return self
}
}
###使用方法
長いプロパティ名を書く数が減り、短い変数名の氾濫を抑えられる。
インデントがついて読みやすい。
self.longNameProperty = UIView().scope { (v: UIView) in
v.frame = CGRectMake(0, 0, 100, 100)
v.backgroundColor = UIColor.whiteColor()
v.autoresizesSubviews = false
v.layer.scope { (l: CALayer) in
l.cornerRadius = 8
l.shadowColor = UIColor.blackColor().CGColor
l.borderColor = UIColor.redColor().CGColor
l.borderWidth = 5
}
}
###問題点等
- パフォーマンスは考えてない
- インデントなしの方が見やすい?
私はインデントがついている方が読みやすいと思いますが、一番初めの例のように左詰めで揃っている方が読みやすいという人もいるでしょう。 万人ウケするとは思ってません。
- クロージャの引数の型を指定しなければならない
本当は次のようにしたかったのですが、ジェネリクスにSelf
は使えないし...
良い方法があれば教えてください。->プロトコルエクステンションを使う。
self.longNameProperty = UIView().scope { v in
//...
}
-
NSObject
でしか使えない
AnyObject
やAny
は拡張できないので、NSObject
を継承していないクラスや構造体には使用できない。
##プロトコルエクステンションを使う方法(追記)
tom-uさんに教えていただいた方法。
プロトコルだとSelf
を引数の型にできるので、プロトコルエクステンションでscopeメソッドを作る。
protocol ScopeProtocol {}
extension ScopeProtocol {
func scope(block: (Self) -> Void) -> Self {
block(self)
return self
}
}
extension NSObject: ScopeProtocol {}
これで、型を指定せずに次のように書けます。
self.longNameProperty = UIView().scope { v in
v.frame = CGRectMake(0, 0, 100, 100)
//...
}
##doブロックを使う方法 (追記)
コメント欄でkishikawakatsumiさんに教えていただきました。
doとcatchがセットだと思っていましたが、doブロックは単体で使えます。
こっちの方が良いかもしれません。
do {
let v = UIView(frame: CGRectMake(0, 0, 100, 100))
v.backgroundColor = UIColor.whiteColor()
v.autoresizesSubviews = false
do {
let l = v.layer
l.cornerRadius = 8
l.shadowColor = UIColor.blackColor().CGColor
l.borderColor = UIColor.redColor().CGColor
l.borderWidth = 5
}
self.longNameProperty = v
}
##closureのみを使う方法(追記)
su_kさんに教えていただいた方法。
closureを作って直ちに使う。protocol extensionとか書かなくてもいい。
self.longPropertyName = {
let v = UIView(frame: CGRectMake(0, 0, 100, 100))
v.backgroundColor = UIColor.whiteColor()
v.autoresizesSubviews = false
_ = { // _ = がない場合はdoを使えと言ってくる
let l = v.layer
l.cornerRadius = 8
l.shadowColor = UIColor.blackColor().CGColor
l.borderColor = UIColor.redColor().CGColor
l.borderWidth = 5
}()
return v
}()
layerのところは素直にdoで良いでしょう。
control+iした時の"}"の位置がscope(){}の場合と違いますね。
##各方法の特徴(追記)
最終的にはパフォーマンスとXcodeのインデンテーション(範囲指定してctrl+i)した時の読みやすさと手間でどれを使うか決めれば良いと思います。読みやすさは個人差があると思いますが...
- scope():
拡張が必要な分、少しだけ手間。propertyが既に別な場所で作られて設定だけをしたい場合にも同じような形式で記述できるのがいい。見やすい?
- do:
拡張必要なし。ベンチマークしたわけではないですが、パフォーマンスが良いはず。
- closureのみ:
拡張必要なし。doより見やすい?