以前こちらの記事で、Alamofireを読んでいて気づいたSwiftの便利な書き方についてご紹介しました。
Alamofireから学ぶSwift実践テクニック
今回は他のライブラリから、気になった書き方をご紹介したいと思います。
追加可能なenumのように使えるstaticプロパティ
ライブラリ名
内容
SwiftyUserDefaultsはUserDefaultsを便利に使うためのライブラリです。
キーの定義とsubscript引数の入れ方に特徴があります。
extension DefaultsKeys {
static let username = DefaultsKey<String?>("username")
static let launchCount = DefaultsKey<Int>("launchCount")
}
let username = Defaults[.username]
Defaults[.hotkeyEnabled] = true
Defaults
のsubscript
の入力が「.
」から始まっており、enumのように見えますがDefaultsKeys
のstaticプロパティです。
staticプロパティの型が自身の型である場合、その型を引数に取る関数などで型名を省略して渡すことがでるようです。
class StaticPorpertyClass {
static let p1 = StaticPorpertyClass() // 同じ型であることがポイント
}
func myFunction(_ obj: StaticPorpertyClass) {
// ...
}
myFunction(.p1) // StaticPorpertyClass.p1としなくて良い
SwiftyUserDefaults.swiftのDefaultsKeys
の定義をご参照ください。
また、NotificationCenterでも同手法が使われています。
メリット
- 引数のパターンが
DefaultsKeys
型で制限できる。 - extensionで外部からパターンを増やすことができる。(enumで制限してしまうとライブラリ外部から
DefaultsKeys
を追加できない) - 引数のところで「
.
」を打つだけでDefaultsKeys
のstaticプロパティが候補で表示される。 -
DefaultsKey
に好きな情報を詰め込める。
オリジナルextensionの入口プロパティ
ライブラリ名
内容
KingfisherはUIImageViewなどに画像の非同期ダウンロード機能を拡張するライブラリです。
このライブラリでは拡張対象のクラスに対し、メソッドを直接追加するのではなく、kf
というプロパティ経由でメソッドにアクセスさせています。
let url = URL(string: "url_of_your_image")
imageView.kf.setImage(with: url)
kf
はKingfisher
クラスになります。実装はこうなっています。
public final class Kingfisher<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
/**
A type that has Kingfisher extensions.
*/
public protocol KingfisherCompatible {
associatedtype CompatibleType
var kf: CompatibleType { get }
}
public extension KingfisherCompatible {
public var kf: Kingfisher<Self> {
get { return Kingfisher(self) }
}
}
extension Image: KingfisherCompatible { }
#if !os(watchOS)
extension ImageView: KingfisherCompatible { }
extension Button: KingfisherCompatible { }
#endif
KingfisherCompatible
をつけるだけでkf
プロパティが追加されるようになっています。
extensionの実装は、Base
がどのクラスかによって切り替えることができます。
extension Kingfisher where Base: ImageView {
extension Kingfisher where Base: NSButton {
この書き方はRxSwiftでも使われています。
メリット
-
kf
がネームスペースの役割を果たすので既存メソッドと混ざらない。 - 既存メソッドと重複した名前も使用可能。
ライブラリを作るときなどに、拡張対象の型が多い場合、あるいは拡張されるプロパティやメソッドが多い場合に便利そうです。
可変長引数subscript
ライブラリ名
内容
SwiftyJSONはJSONのパーサーですが、要素にアクセスしやすいよういろいろ工夫がされています。その中で、可変長引数を持つsubscript
が定義されています。
public subscript(path: JSONSubscriptType...) -> JSON {
get {
return self[path]
}
set {
self[path] = newValue
}
}
JSONの深い所にある要素にアクセスするために使われています。
json["list",3,"what"] = "that"
うまく使えば、便利な型が作れそうです。
例として複数の値を取り出すDictionary風の型を作ってみました。
struct MultiKeyDictionary<Key: Hashable, Value> {
private let _dictionary: [Key : Value]
init(_ dictionary: [Key : Value]) {
_dictionary = dictionary
}
subscript(keys: Key...) -> [Value] {
return keys.flatMap { return _dictionary[$0] }
}
}
let source = [
"a" : "hoge",
"b" : "fuga",
"c" : "foo",
"d" : "bar"
]
let mkDic = MultiKeyDictionary(source)
print(mkDic["a"]) // -> ["hoge"]
print(mkDic["a", "d"]) // -> ["hoge", "bar"]
subscript
を使った場合は、可変長も検討してみると面白そうです。
メリット
- 可変長引数の
subscript
を1引数の延長として実装できれば、直感的でより便利な型になる。
custom operatorのチェーン
ライブラリ名
内容
SwiftStateはcustom operatorがいろいろ定義されていて、状態遷移を定義したり実行したりするのが特徴です。一つの状態遷移を表すのは.state0 => .state1
と書けるのですが、連続した複雑な状態遷移も.state0 => .state1 => .state2
のように書けるよう工夫しています。
machine = StateMachine<MyState, NoEvent>(state: .state0) { machine in
machine.addRouteChain(.state0 => .state1 => .state2, handler: { _ in
print("0 -> 1 -> 2")
})
}
custom operatorの戻り値の型も、再度同じcustom operatorの引数として受け付けるようにするのがポイントです。実際のコードではいろんなパターンが網羅されています。TransitionChain.swiftをご参照ください。細かい工夫ですが気持ちよく書けるので、custom operatorを実装する場合は考慮すると良さそうです。
メリット
- より便利で直感的にcustom operatorを使うことができる。
最後に
いくつか自分が気になった例を書いてみました。
特にSwiftyUserDefaultsとKingfisherのパターンは使い所が多そうですので、自分のプロジェクトにもすぐ応用できそうです。
SwiftyJSONとSwiftStateの例は、ライブラリを自作する際に参考にできそうです。