Upload
yuki-asai
View
2.562
Download
3
Embed Size (px)
Citation preview
Deep dive into OSS written in Swift
yukiasai
・浅井勇樹 28歳
・ Github : yukiasai
・出身:福井県福井高専
・所属 : 株式会社マネーフォワード
・マネーフォワード - 自動家計簿アプリ
・経歴 : 株式会社ナチュラルスタイル
・ ZOZOTOWN - ファッション通販アプリ
・WEAR - ファッションコーディネートアプリ
・得意 : iOS
・趣味 : コードリーディング
自己紹介
日常のコードリーディング
・通勤時間中はだいたい Github
・おやすみ前もだいたい Github
・ Trending repositories - Github
・最近人気のリポジトリランキング
・ Search Github - Github
・使い方がわからないクラスがあったらとりあえず検索
・みんながどんな使い方をしているかわかる
社内でコードリーディング
・週1で Swiftコードリーディング会を開催
・全社の iOSエンジニアに知見を共有するのが目的
・有名どころから自作まで様々
・ Alamofire
・ Bond
・ ObjectMapper
・ SwiftTask
・などなど、、、
紹介する OSS
・ Shoyu - yukiasai/Shoyu
・ UITableViewをもっと簡単に
・ Kaiseki - yukiasai/Kaiseki
・ JSONのパースを自動で
Shoyu
UITableViewをもっと簡単に
UITableViewを普通に使うとこうなるfunc numberOfSectionsInTableView(tableView: UITableView) -> Int { return 3}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { case 0: return 5 case 1: return 3 default: fatalError() }}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { switch (indexPath.section, indexPath.row) { case (0, _): let cell = tableView.dequeueReusableCellWithIdentifier("MemberCell") as! MemberTableViewCell return cell case (1, _): let cell = tableView.dequeueReusableCellWithIdentifier("GroupCell") as! GroupTableViewCell return cell default: fatalError() }}
UITableViewあるある
・デリゲートメソッドが switch文( if文)地獄になる
・ビューの見た目とコードの見た目が乖離している
・どのセクションにどのローが表示されているか追うのが大変
・ switch文を一箇所いじるとほぼ全箇所に影響する
・バグの温床となる可能性が極めて高い
・カスタムセルを使う場合デリゲートメソッド内でいちいちキャストしてやる必要がある
Shoyuだとこうなる
tableView.source = Source() .createSection { section in section.createRows(5) { (_, row: Row<MemberTableViewCell>) in row.height = 52 row.configureCell = { cell, _ in } row.didSelect = { _ in } } } .createSection { section in section.createRows(3) { (_, row: Row<GroupTableViewCell>) in row.height = 52 row.configureCell = { cell, _ in } row.didSelect = { _ in } } }tableView.reloadData()
Shoyuの利点
・ switch文( if文)地獄から開放される
・ビューの見た目とコードの見た目が近い
・セクションの追加、ローの追加がとっても楽ちん
Shoyuで使われているテクニック
・初期化クロージャ付き init
・ジェネリクス対応の Sectionと Row
初期化クロージャ付き init
例えば Rowの init
init(@noescape closure: (Row<T> -> Void)) { closure(self)}
使うときlet row = Row<XxxxCell>() { $0.height = 52}
利点
・インデントが一つ下がるのでコードが見やすくなる
・スコープをより小さく保つことができる
OSSでこんなのもありました
Then - devxoul/Then
let view = UIView().then { $0.backgroundColor = UIColor.redColor()}
ジェネリクス対応の Sectionと Row
ジェネリック対応の Sectionと Row
public class Section<HeaderType: UIView, FooterType: UIView>: SectionType { // ごにょごにょ}
public class Row<T: UITableViewCell>: RowType { // ごにょごにょ}
利点・ビューのタイプが確定するのでタイプセーフが貫ける
・デリゲートでいちいちキャストするみたいなのをしなくて良い
今一度コードを見てみる
tableView.source = Source() .createSection { section in section.createRows(5) { (_, row: Row<MemberTableViewCell>) in row.height = 52 row.configureCell = { cell, _ in } // <- この cellのタイプはMemberTableViewCellになる row.didSelect = { _ in } } } .createSection { section in section.createRows(3) { (_, row: Row<GroupTableViewCell>) in row.height = 52 row.configureCell = { cell, _ in } // <- この cellのタイプは GroupTableViewCellになる row.didSelect = { _ in } } }tableView.reloadData()
今後対応したいこと
・ UICollectionView対応・遅延評価対応
Kaiseki
JSONのパースを自動で
Kaisekiを使うとこうなる
こんなオブジェクトを宣言
class Object: Entity { // Basic let int = Property<Int>() let string = Property<String>() // Array let array = Property<[Bool]>() // Optional let optional = Property<Int?>() // Entity let object = Property<Object?>()}
使い方let json: [String: AnyObject] = [“int”: 1, “string”: “aaa”, “array”: [true, false], “optional”: null, ....]let obj = Object.fromJSON(json: jsonData)obj.int.value // -> 1obj.string.value // -> aaa
Kaisekiで使われているテクニック
・Mirrorを用いた自動マッピング・プロトコルで構造体( Int,Optional,Array)を拡張
Mirrorを用いた自動マッピング
Mirrorを用いた自動マッピング
Mirror : Swiftのリフレクション API
class Object { var id: Int = 0 var name: String = ""}
let obj = Object()Mirror(reflecting: obj).children.forEach { child in print(child)}
出力
(Optional("id"), 0)(Optional("name"), "")
・プロパティ名と値がとれるので、、・ JSONのキー名とプロパティ名を付け合わせてパースする!
Entityクラス
public class Entity: ValueType { typealias ReflectedProperty = (label: String, property: PropertyType) private lazy var reflectedProperties: [ReflectedProperty] = { return Mirror(reflecting: self).children.filter { $1 is PropertyType }.flatMap {($0!, $1 as! PropertyType)} }() public static func fromJSON(json: AnyObject?) -> Self? { // … reflectedProperties.forEach { let key = $1.keyWith($0) if let value = dic[key] { $1.fromJSON(value) } } }}
Propertyクラス
・なぜ Properyクラスでラップしているか?・Mirrorで取得できる Childが構造体(タプル)・値を取得はできるものの変更することができない・ Child.valueがクラスの場合は参照が渡ってくる・ Property.valueを変更することで間接的に値を変更する
public class Property<Value: ValueType>: PropertyType { public var value public func fromJSON(json: AnyObject) { if json is NSNull { if let value = Value.fromJSON(nil) where Value.isOptional { self.value = value } return } if let value = Value.fromJSON(json) { self.value = value } }}
ValueType
・ Property<ValueType>に指定できる ValueType一覧・ Bool・ Int・ Float・ Double・ String・ Optinal・ Array
今一度サンプルコードを見てみる
こんなオブジェクトを宣言
class Object: Entity { // Basic let int = Property<Int>() let string = Property<String>() // Array let array = Property<[Bool]>() // Optional let optional = Property<Int?>() // Entity let object = Property<Object?>()}
使い方let json: [String: AnyObject] = [“int”: 1, “string”: “aaa”, “array”: [true, false], “optional”: null, ....]let obj = Object.fromJSON(json: jsonData)obj.int.value // -> 1obj.string.value // -> aaa
プロトコルで構造体( Int,Optional,Array)を拡張
Optionalの例を紹介
Optional<T>を ValueTypeで拡張public protocol ValueType { static func fromJSON(json: AnyObject?) -> Self? func toJSON() -> AnyObject?}
extension Optional: ValueType { public static func fromJSON(json: AnyObject?) -> Wrapped?? { guard let valueType = Wrapped.self as? ValueType.Type, let value = valueType.fromJSON(json), let wrapped = value as? Wrapped else { return .Some(.None) } return .Some(.Some(wrapped)) } public func toJSON() -> AnyObject? { switch self { case .Some(let wrapped): guard let value = wrapped as? ValueType else { return nil } return value.toJSON() case .None: return nil } }}
どういうこと?
static func fromJSONlet optionalInt = Int?.fromJSON(json: 1)print(optionalInt) // -> Optional<Optional(1)>if let unwrappedOptinalInt = optionalInt { print(unwrappedOptionalInt) // -> Optional(1)}
・上記のコードは Optional<Int>へのスタティックメソッド呼び出し・ fromJSONは Self?を返すので、 optionalIntの型は Optional<Optional<Int>>になる
func toJSON( instance func)let json = optionalInt.toJSON()
・ Optional<Int>に追加された toJSONを呼び出している
Propertyクラスで行われていること
public class Property<Value: ValueType>: PropertyType { public var value: Value public func fromJSON(json: AnyObject) { // ...NSNullの処理 if let value = Value.fromJSON(json) { // <- ここ self.value = value } } public func toJSON() -> AnyObject? { if let json = value.toJSON() { return json } // ...NSNullの処理 return nil }}
今後対応したいこと
・ Property.valueへのアクセスをもっと簡単にしたい・ Property<Bool>を Boolと同様に扱えるようにしたい
・
紹介した OSS
・ Shoyu - yukiasai/Shoyu
・ UITableViewをもっと簡単に
・ Kaiseki - yukiasai/Kaiseki
・ JSONのパースを自動で
Thank you!
yukiasai