- 投稿日:2019-02-18T22:18:30+09:00
Mac mini はアプリ開発者の救世主かもしれない
はじめに
モバイルアプリ開発にとって、マシンスペックは開発効率に直結しますよね。
iOSアプリ開発を行なっているときの、ビルド時間が長いこと長いこと。。。さすがに時間が勿体なさすぎたので、
MacBook Pro 13-inch (2017) -> Mac mini (2018) に乗り換えました。その結果、めちゃめちゃ快適になりました。
今後、弊社アプリチーム全体に高スペックMacを導入すべく、簡単に比較を行いました。
その結果を載せます。ビルド時間比較
私が関わっているiOSアプリのクリーンビルドを10回行いましたので、
そのビルド時間平均を記載します。
- 環境
- Mojave
- Xcode 10.1
Mac mini MacBook Pro 44.8184 sec 92.1546 sec 約1/2の時間でビルドが完了するようになりました。
計測時間詳細
Mac mini MBP 43.655 93.531 45.136 92.335 44.224 91.599 44.480 92.748 44.399 92.554 44.926 91.984 44.748 91.456 44.511 92.276 46.918 91.549 45.187 91.514 当然ですが、概ねここのベンチマーク結果と大きくは外れませんでした。
https://browser.geekbench.com/mac-benchmarks
今回のマシンはMulti-Coreスコアが、MBPが9075に対して、Mac miniが24243です。スペック比較
今回利用したマシンスペックは、こんな感じです。
Mac mini MBP CPU Intel Core i7-8700B Intel Core i5-7360U Memory 32GB 2,666MHz DDR4 16GB 2,133MHz LPDDR3 Storage 512GB SSD 256GB SSD 乗り換え時に気をつけるポイント
- Mac miniには入力/出力インターフェースを外付けする必要があるので、持っていない場合は別途購入が必要です。
- キーボード
- マウス or トラックパッド
- ディスプレイ
- GPUのアップグレードはできません。もし、増設したい場合はeGPUを買いましょう。
さいごに
ビルド時間が半分になったことで、ストレスが大きく軽減されました。
さらに、ビルド中にSketch, Chrome, Slack などのチェックをしていても、
全くカクツクことはなく、ここも大きなストレス軽減ポイントの一つですね。Mac min は25万くらいで、強力なマシンスペックを得られます。
iMac Proはちょっと無理。。。って場合に、Mac miniは乗り換えの有力候補になると思いますので、参考までにどうぞ。2,3月は費消の時期なので、交渉したらサクッとOKもらえるかもしれませんよ。
- 投稿日:2019-02-18T21:39:49+09:00
Swiftの命名規則を理解する(Swift API Design Guidelines - Naming 日本語まとめ)
私は普段Swiftをメインで書いていますが、Namingがとても苦手です。
どうやったらNamingがうまくなるか、考えました、
公式ドキュメントを見つけて、考えるのをやめました。Namingがうまくなるためには、公式ドキュメントに従うのが一番です。
そこでSwift API Design GuidelinesのNamingを読みました。
https://swift.org/documentation/api-design-guidelines/#naming他の方の翻訳もありますが、当時から公式ドキュメントが更新されていたようですので、
2019/02時点での翻訳をまとめてみました。
- 翻訳というより、日本語意訳に近いと思います。
- 構成や日本語表現を独断と偏見で一部編集しています。
- 忙しい人は、「fuwamakiまとめ」だけ見れば十分かもしれません。
- 忙しくない人や英語ができる人は、公式ドキュメントを見てください。
命名
クリアな使い方にしましょう
1. 曖昧さを避けるために全ての単語を含めよう
呼び出し側を読む人にとって曖昧な意味に捉えられないように、全ての単語を含めよう。
良い例:
extension List { public mutating func remove(at position: Index) -> Element } employees.remove(at: x)良くない例:
[color=red] employees.remove(x)
-> xが何を示しているか明確でないのはアカン。
2. 不要な単語は省略しよう
全ての単語は、呼び出し側にとって意味のある情報であるべき。
意図を明確にするため、意味の違いを明確にするために多くの単語を使うこともある。
ただ、読み手にとって不要な情報(なくても理解できるもの)は省略しよう。良くない例:
public mutating func removeElement(_ member: Element) -> Element?
allViews.removeElement(cancelButton)->
Elementは呼び出し部分で、意味のある情報を与えないのでアカン。良い例:
public mutating func remove(_ member: Element) -> Element? allViews.remove(cancelButton) // clearer曖昧さを避けるために型情報を繰り返すことが時々あるが、
一般的には型よりパラメータの役割を説明する単語を使うことが望ましい。
詳細は次項にて。3. 役割に応じて変数・パラメータ・関連型を命名しよう
型制約ではなく、役割に応じて変数・パラメータ・関連型を命名しよう。
良くない例:
var string = "Hello"
protocol ViewController {
associatedtype ViewType : View
}
class ProductionLine {
func restock(from widgetFactory: WidgetFactory)
}-> エンティティの役割を表す名前を選択すべき、アカン。
良い例:
var greeting = "Hello" protocol ViewController { associatedtype ContentView : View } class ProductionLine { func restock(from supplier: WidgetFactory) }関連付いた型がProtocol制約に密接に結びついて、Protocol名が役割になっている場合、
衝突を回避するためにProtocol名にProtocolを付けるのは問題ない。protocol Sequence { associatedtype Iterator : IteratorProtocol } protocol IteratorProtocol { ... }4. 弱い型情報には理解補助をしましょう
パラメータの役割を明確にするために、弱い型情報には理解補助で単語を補う。
パラメータ型が[NSObject, Any, AnyObject, Int, String]などの基本型の場合、
使用時の型情報とコンテキストが意図をちゃんと伝えられない可能性がある。良くない例:
func add(_ observer: NSObject, for keyPath: String)
grid.add(self, for: graphics) // vague-> 宣言は明確だが、使用法が曖昧でアカン。
良い例
弱く型付けられたパラメータの前に、役割を示す名詞を付ける
func addObserver(_ observer: NSObject, forKeyPath path: String) grid.addObserver(self, forKeyPath: graphics) // clearfuwamakiまとめ
- 必要な単語を全て含んだ命名にしよう
- 意味を示さない単語は省略してOK
- 型制約じゃなくて役割に応じた命名をしよう
- 意味を示しきれない型情報には理解補助で単語を補ってOK
スムーズな利用を心掛けよう
1. メソッド名と関数名で、文法的な英語のフレーズにしよう
良くない例:
x.insert(y, position: z)
x.subViews(color: y)
x.nounCapitalize()良い例:
x.insert(y, at: z) “x, insert y at z” x.subViews(havingColor: y) “x's subviews having color y” x.capitalizingNouns() “x, capitalizing nouns”メインの意味を示す引数の後に、フレーズとしての流暢さが低下する引数が続くのは許容
AudioUnit.instantiate( with: description, options: [.inProcess], completionHandler: stopProgressBar)-> withがメインの意味を示す引数。optionとcompletionHandlerは付属する引数。
2. factoryメソッドは、makeから始めよう
ex.)
x.makeIterator().3. initializerやfactoryメソッドの最初の引数は、関数名で始まる英語フレーズにしてはいけない
ex.)
x.makeWidget(cogCount: 47)良くない例:
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
let ref = Link(to: destination)-> API作成者が最初の引数を使って文法的な連続性を示そうとしていてアカン。
良い例:
let foreground = Color(red: 32, green: 64, blue: 128) let newPart = factory.makeWidget(gears: 42, spindles: 14) let ref = Link(target: destination)呼び出しが値保存型変換をしていない限り、最初の引数にラベルがあることを示す。
let rgbForeground = RGBColor(cmykForeground)4. 副作用に応じて関数・メソッドを命名しよう
副作用がない場合は、名詞として読もう
ex.)
x.distance(to: y),i.successor()副作用がある場合は、命令動詞句として読もう
ex.)
print(x),x.sort(),x.append(y)mutatingとnonmutatingのメソッド名は一貫したものにしよう
mutating methodは、似た意味を持つnonmutatingな変化をすることがあるけれど、
それはインスタンスが置き換わるのではなく、新しい値が返されている。動作が動詞で記述される際は、変更方法に応じて
edまたはingをつけよう
Mutating Nonmutating x.sort() z = x.sorted() x.append(y) z = x.appending(y) 操作が名詞で記述される場合は、
formをつけよう
Nonmutating Mutating x = y.union(z) y.formUnion(z) j = c.successor(i) c.formSuccessor(&i) Booleanメソッドとプロパティは使用法が変化しない場合、そのまま明示しよう
ex.)
x.isEmpty,line1.intersects(line2)プロトコルは何が名詞として読まれるべきかを記述しよう
ex.)
Collectioncapabilityを示すプロトコルには接尾に
able,ible,ingをつけようex.)
Equatable,ProgressReportingその他の型,プロパティ,変数,定数は名詞として読む
fuwamakiまとめ
- メソッド名&関数名で英語のフレーズしよう
- 生成メソッドは、makeから始めよう
- initと生成メソッドは、関数名と引数でフレーズ作っちゃダメ
※副作用のとこはまとめられませんでしたごめんなさい
Term of Art(専門用語)をうまく使う
Term of Artとは…特定の分野または職業の範囲内で、特別な意味を持つ単語・フレーズ
1. 曖昧な用語を避けよう
一般用語を使って良い場面では、わざわざ専門用語を使わず一般用語を使おう。
例)
epidermis(表皮) よりskin(肌)を使って問題なければ、一般用語であるskinを使いましょう。epidermisのような専門用語は、それでなければダメな場合にのみ利用しましょう。2. 専門用語は、確立された意味を示す時に使おう
一般用語より専門用語を使うのは、それがなければ曖昧で不明瞭になるモノを正確に示すため。
例)
APIのような技術用語も、確立された意味を示す場合に使いましょう。専門家を驚かせない!
我々が用語に新しい意味を見出して利用していたら、専門家は驚き、怒るでしょう。アカン。
初心者を惑わせない!
初心者はその専門用語の意味を調べるためWeb検索して、伝統的な意味を見つけるでしょう。
伝統的な意味でその専門用語を使いましょう。3. 略語は避けよう
特に非標準の略語は、人によって専門用語化してしまうのでアカン。
使用している略語の意味は、web検索で簡単に見つけられるようにすべき。4. 先例に習おう
既にある文化を無視して、初心者に向けて分かりやすくするのは、アカン。
例1) 連続したデータ構造に対して
○
Array
✕List
Listの方が分かりやすいかもしれないが、
配列は現代コンピューティングにおいて基本であり、
全てのプログラマがArrayを学び、知っています。例2) 数学のような特定のプログラミング領域において
○
sin(x)
✕verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)略語を避けることは大切だが、sin(x)は数学者の間で数世紀前から使われていた共通用語。
fuwamakiまとめ
- 曖昧な用語は避けよう、意味に合った単語を使おう
- 専門用語でしか表せない時に専門用語を使おう
- 略語は避けよう
- 先例には従おう
参考
https://qiita.com/moaible/items/fbe87b343b9eaa48816e
https://qiita.com/yamoridon/items/b89a18a037631b6770b9
https://qiita.com/mono0926/items/11203c8cce0d7f4548db
- 投稿日:2019-02-18T20:43:29+09:00
コードで書くStackViewとAutoLayout
Storyboardでしか書いたことなかったけど、データが動的な場合はコードの方が楽なのでメモ。
import UIKit class MainViewController : UIViewController { let listView = ListView() override func viewDidLoad() { self.view.addSubview(listView) listView.translatesAutoresizingMaskIntoConstraints = false listView.widthAnchor.constraint(equalToConstant: 300).isActive = true listView.heightAnchor.constraint(equalToConstant: 100).isActive = true listView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0).isActive = true listView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0).isActive = true let item1 = Item() let item2 = Item() let item3 = Item() stackView.addArrangedSubview(item1) stackView.addArrangedSubview(item2) stackView.addArrangedSubview(item3) } } class StackView : UIView { let stackView = UIStackView() override init(frame: CGRect) { super.init(frame: frame) self.setup() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.setup() } func setup(){ self.backgroundColor = UIColor.white self.addSubview(stackView) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.distribution = .fillEqually stackView.alignment = .fill stackView.spacing = 10.0 stackView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true stackView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true stackView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true } } class Item : UIView { let imageView = UIImageView() let name = UILabel() override init(frame: CGRect) { super.init(frame: frame) self.setup() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setup(){ self.addSubview(imageView) imageView.backgroundColor = UIColor.gray imageView.translatesAutoresizingMaskIntoConstraints = false imageView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true imageView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true self.addSubview(name) name.text = "TEST" name.translatesAutoresizingMaskIntoConstraints = false name.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true name.rightAnchor.constraint(equalTo: self.rightAnchor, constant:0).isActive = true name.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 5).isActive = true name.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true } }メモ
- stackViewにはwidth,heightの指定が必須。指定しないとサイズが0になる
- Item(StackViewに追加するView)は
addSubViewじゃなくてaddArrangedSubviewで追加する- Itemのサイズは指定しないと最小になる。サイズを持つ要素(UILabelとか)の場合はテキスト分のサイズに。ただ
distributionとalignmentで拡大のルールは決められる。- Webでいうflexboxと考え方は一緒。
思ったよりAutoLayoutをコードで書くのがめんどくさくないので今後はコードで書いていきたい。
isActive = trueはよく忘れる。
- 投稿日:2019-02-18T02:53:16+09:00
[初心者向け]swiftにおける値渡しの方法の一部をご紹介
環境
- PC: MacBook Pro (15-inch, 2018)
- Xcode: Version 10.1
- Swift: 4.2
対象の読者
- Swift初心者の方
- 値渡しの方法を学習したい方
執筆したきっかけ
自分は,2019年2月16日と17日に行われたハッカソン(P2HACKS)に運営兼ヘルパーとして参加しました.
P2HACKSは,”チーム開発を経験する”を目的とした育成型ハッカソンです.そのため,参加者の多くが未経験者でした.これに対しヘルパーとして参加した結果,画面間の値渡しに苦戦していたチームが見られました.したがって,少しまとめてみてはどうだろうと思い執筆しましたプロジェクトの用意
GitHubにあげてあります.
よろしければ,cloneしてください値渡しの方法
1. segueを使用して画面遷移&値渡し
ViewController.swift@IBAction func byPerformSegue(_ sender: Any) { self.performSegue(withIdentifier: "toSegueViewController", sender: nil) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "toSegueViewController" { let nextVC = segue.destination as! SegueViewController nextVC.text = "fromViewController" } }まずは,segueを使用したパターンです.segueはstoryboardにて,ドラックアンドドロップで設定することができるため,非常に簡単に実装することができると思います.詳しいsegueを使用したパターンの実装方法は,こちら【iOS】画面遷移の基礎【Swift3.0】を参考にしてみてください.
個人的な考え方ですが,segueを使用したパターンの欠点として下記のことが考えられると思います
1. 画面遷移を行う関数を呼び出す箇所と,値渡しをする準備を行う関数が別の場所にある
2. segueのidが増えていくと,条件分岐が増える今回の様に簡単なアプリであれば問題ないと思うのですが,UIパーツが増えるなどしてアプリの規模が大きくなると,1で述べたように離れていることから可読性が低下すると考えています.
また,それぞれのsegueにidを振ることから,増えたら増えただけ条件分岐が深くなります.こちらについては,[swift] Storyboardのsegue idを「かんたん・きれい・色あせない」方法で管理するにて紹介されているenumを使用してswitch文を使用することをオススメします.2. navigationControllerを使用して画面遷移&値渡し
ViewController.swift@IBAction func byNavicationPush(_ sender: Any) { let nextVC = self.storyboard?.instantiateViewController(withIdentifier: "navigationPushView") as! NavigationPushViewController nextVC.text = "fromViewController" self.navigationController?.pushViewController(nextVC, animated: true) }次にnavigationControllerを使用するパターンです.withIdentifierは,Storyboard上の各ViewControllerの「Storyboard ID」です.こちらは,自身がnavigationController下にいることが条件です.このパターンを使用すると,前の前の画面に値を渡したいor戻りたいという時,簡単に実装できます(navigationControllerはスタックでviewを管理しているため).(参考サイトはこちらです.[Swift3]2つ前の画面に戻る方法 )
(初心者向けなので,Delegateやオブザーバー同期,Rxによるデータバインディングの話はしません)
また,pushだけではなくpresentによる遷移も行えますself.navigationController?.present(nextVC, animated: true, completion: nil)3. presentを使用して画面遷移&値渡し
ViewController.swift@IBAction func byPresent(_ sender: Any) { let nextVC = self.storyboard?.instantiateViewController(withIdentifier: "presentView") as! PresentViewController nextVC.text = "fromViewController" self.present(nextVC, animated: true, completion: nil) } }次にpresentを使用するパターンです.こちらは,自身がnavigationController下にいる必要がありません.使い所としては,遷移先から新たに画面遷移を行わない時だと思います.ちなみに,遷移先を閉じるときは,下記のコードが必要になります.
self.dismiss(animated: true, completion: nil)遷移先にUIButtonなどを配置して,適宜呼び出すと良いと思います.また,navigationControllerをしてpresentを行った場合は,下記のコードがオススメです
self.navigationController?.popViewController(animated: true)dismissは,自身を破棄して前の画面に戻るための関数です.一方で,popViewControllerはpopを行って1つ前の画面に戻る関数です.storyboard上でnavigationControllerを設定した場合,自動的にbackボタンが出来上がります.そのbackボタンをタップすると,内部的にはpopViewControllerを呼んでいたような気がします(あんまり自信ないです).
4. 異なるstoryboardにある画面に,navigationControllerを使用して画面遷移&値渡し
ViewController.swift@IBAction func byNavigationPushForOtherStoryboard(_ sender: Any) { let storyboard: UIStoryboard = UIStoryboard(name: "Sub", bundle: nil) let nextVC = storyboard.instantiateViewController(withIdentifier: "otherStoryboard") as! OtherStoryboardViewController nextVC.text = "fromViewController" self.navigationController?.pushViewController(nextVC, animated: true) }最後に,異なるstoryboardにある画面に対して,navigationControllerを使用するパターンです.
チーム開発,特にハッカソンなど短期間で一気に開発する場合,コンフリクトが起きることがあります.iOSアプリにおいては,storyboardによるコンフリクトが多いのではないでしょうか?
これに対するアドバイスとしては,ViewControllerとstoryboardの関係を1対1にすることです.そうすることで,開発者単位やブランチ単位で扱うstoryboardを最小限にできるはずです.
遷移先のViewControllerの「Is Initial View Controller」に,チェックが入っていることを確認してください(矢印のこと).また,name: "Sub"のSubは,storyboardの名前です.参考
- 投稿日:2019-02-18T01:49:25+09:00
SwiftでDropBox APIを使ってみる [準備編]
DropBoxを使ってiosアプリからテキストファイルを書き換えができるまで頑張ってみた軌跡です。
ご指摘、改善できる箇所があればご教授いただけると幸いです。下記のSwiftyDropboxリファレンスを参考に作成していきます。
http://dropbox.github.io/SwiftyDropbox/api-docs/latest/環境
MacBookPro
Xcode : Version 10.1 (10B61)
swift : Version 4.2.1#1 DropBoxにアプリ登録
*DropBoxアカウントが必要です。
1.DropBoxのホーム画面へ
https://www.dropbox.com/developers/apps2.My apps画面右上の"Create app"ボタンをクリック
3.Create a new app on the DBX Platform の設定
Choose an API : DropBox API
Choose the type of access you need : Full Dropbox
Name your app : 好きなアプリ名#2 Xcodeプロジェクト作成
次のステップのcocoapodでライブラリをインストールするので作成、起動できるかテストを行う
#3 cocoapodでDropbox APIのライブラリのインストール
1.ターミナルで#2で作成したプロジェクトの階層まで移動
2.Podfileを生成pod init3.Podfile編集
target 'アプリ名' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! #追加 pod 'SwiftyDropbox' end#4プロジェクトを設定
1.Info.plistファイルをテキスト編集ソフト(私の場合はvisual studio code)で開く
2.下記のコードを追加<key>LSApplicationQueriesSchemes</key> <array> <string>dbapi-8-emm</string> <string>dbapi-2</string> </array>3.CFBundleURLTypes内の配列にCFBundleURLSchemesを追加
db-########となるように記述<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> =============================追加================================== <key>CFBundleURLSchemes</key> <array> <string>db-#1で作成したキー</string> </array> ================================================================= </dict> </array>以上で下準備は完了です!
次の記事で実装を行なっていきたいと思います!
- 投稿日:2019-02-18T00:00:58+09:00
SwiftyJSON/Decodableを利用してAPI通信を行い、UITableViewCellに表示する
これは続きです!
PART1: https://qiita.com/ostk0069/items/8bd4173ff4f083e35951この記事はPARTが2つに分かれています。これはPART2に該当します。PART1ではPART2のベースのコードとなるUITableViewの実装を行なっています。ぜひ確認してみてください。
今回実装にあたって実装したものをGitHubに作成しておいたのでコードだけ見たい人はこちらからどうぞ。
とりあえずswift内のデータを表示させたもの(PART1の内容)*
https://github.com/takumaosada/customCellTutorial*終了後、Alamofire, SwiftyJSONを用いてAPI通信を行い表示させたもの
https://github.com/takumaosada/customCellTutorial/tree/feature/swifty_json*終了後、Alamofire, Decodable(codable)を利用してAPI通信を行い表示させたもの
https://github.com/takumaosada/customCellTutorial/tree/feature/decodableCarthageからpackageをインストールする
という話をするにあたって人によっては「CocoaPodsにしよう」という人もいると思います。僕はCarthageでやってと言われたのでCarthageでやっただけのことなので好きな方を選んでください。
インストール方法に関しては他の方の記事を参考にした方がわかりやすいと思うので載せておきます。
- Carthage
Carthageを使ってビルド時間を短縮しよう
【Swift】Carthage導入手順
Carthage について
- CocoaPods
【Swift】CocoaPods導入手順
iOSライブラリ管理ツール「CocoaPods」の使用方法今回インストールするのはAlamofire, SwiftyJSONの2つです。(decodableはswiftが標準で使うことのできるものです)
ちなみに僕はカーセッジと呼びたい派です。
使用するAPIについて
今回使用するのは駅すぱあとのAPIです。利用方法は以下の記事に詳細に書かれています。
駅すぱあとAPIを使ってみた
これを使用する上で申請が必要なので申請するのがめんどいのは他のAPIを使うことを推奨します。
この後わかることなのですが、駅すぱあとのAPIは俗に言うnestedJSON(出力されるJSONが複雑で扱いにくい)のでむしろ他のAPIを使う方が苦労しません。
[2018] 個人でも使える!おすすめAPI一覧を参考にするといいと思います。APIからJSONを受け取る前に
実装していくにあたって駅すぱあとのAPIを利用する場合はデフォルトの設定のXcodeがHTTPSではなく、HTTPのURLのAPIを利用することを拒否してきます。なので解消することをお忘れなく!
解消方法: 「http://」のAPIを実行できるようにする(Swift)SwiftJSONとDecodableを利用するにあたって
これはどちらもJSONをいい感じに変換して使用する方法です。なのでどちらか(あるいは他にも選択肢はあると思いますが)選ぶ必要があります。
どっちがいいのかと言う正解みたいなのはないとは思いまずが、最近ではdecodableの方が主流と聞きました。持ち間違っていたらコメントください。1. SwiftyJSONを利用する方法
他の言語でJSONの処理をやったことがある人はdecodableよりもこちらの方が考え方として慣れているかなと思います。(そもそもcodableの考え方はswiftにしかないっぽい)今回はとりあえずこれ以上の機能を考えていないのでViewControllerに記述しちゃいたいと思います。
ViewControllerへの記述
ViewController.swiftimport UIKit import Alamofire import SwiftyJSON class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var stationList: UITableView! var stations:[Station] = [Station]() override func viewDidLoad() { super.viewDidLoad() stationList.dataSource = self stationList.delegate = self stationList.register(UINib(nibName: "StationTableViewCell", bundle: nil), forCellReuseIdentifier: "StationTableViewCell") self.setupStations() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func setupStations() { // stations = [Station(name: "飯田橋", prefecture: "東京都新宿区"), Station(name: "九段下", prefecture: "東京都千代田区"), Station(name: "御茶ノ水", prefecture: "東京都文京区") ]; let url = URL(string: "http://api.ekispert.jp/v1/json/station?key=[駅すぱあとのKEY]")! Alamofire.request(url, method: .get).responseJSON { response in switch response.result { case .success: let json:JSON = JSON(response.result.value ?? kill) let resData:JSON = json["ResultSet"]["Point"] var stationInfo: [Station] = [] resData.forEach { (_, resData) in let station: Station = Station.init(name: resData["Station"]["Name"].string!, prefecture: resData["Prefecture"]["Name"].string!) stationInfo.append(station) } self.stations = stationInfo self.stationList.reloadData() case .failure(let error): print(error) } } } func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return stations.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "StationTableViewCell", for: indexPath ) as! StationTableViewCell cell.setCell(station: stations[indexPath.row]) return cell } }その後、Buildする
一覧が取得されていることが確認されました。次に今回書いたコードについて解説していきます。
ViewController.swiftAlamofire.request(url, method: .get).responseJSON { response in switch response.result { case .success: let json:JSON = JSON(response.result.value ?? kill) let resData:JSON = json["ResultSet"]["Point"] var stationInfo: [Station] = [] resData.forEach { (_, resData) in let station: Station = Station.init(name: resData["Station"]["Name"].string!, prefecture: resData["Prefecture"]["Name"].string!) stationInfo.append(station) } self.stations = stationInfo self.stationList.reloadData() case .failure(let error): print(error) } }今回で変わっているのはここだけです。Alamofireの使い方については今回は省略します。受け取ったデータを
resDataと置き、その後forEach文で取ってきた値を1つ1つ代入しています。最後にそれをstationsとしてまとめることで他のファイルの記述に合わせています。最後にself.stationList.reloadData()を記述することを忘れないようにしましょう。今回使用したコードの詳細はこちらにあります。
https://github.com/takumaosada/customCellTutorial/tree/feature/swifty_jsondecodableを利用する方法
こちらがdecodable(codable)の公式のドキュメントです。
https://developer.apple.com/documentation/swift/swift_standard_library/encoding_decoding_and_serialization
僕の解釈としては受け取ったJSONを自分がやりやすいようにカスタマイズして出力するのがSwiftyJSONだとすると、受け取ったJSONを、受け取ったJSONの構造のまま使う制約を設けることでデータを出力させやすくするのがCodableだと思っています。そのためチーム開発するときにメンバーがstructの構造を見ればすぐにどんなJSONを受け取ったのか理解することができるメリットがあります。ここで言うstructの構造というのは受け取るJSONの設計図みたいなものです。じゃあ実際にそれをコードに起こしていくのが次になります。structの構造を確認する
Station.swift/* { "ResultSet":{ "Point": [ { "Station":{ "code":"00000", "Name":"ほげ", "Yomi":"ほげ" }, "Prefecture":{ "Name":"ほげ県" }, } ] } } */今回受け取る駅すぱあとのJSONはこのような構造になっていました。これに合わせてstructを構築していったものが次になります。
それに合わせてstructを記述していく
Station.swiftimport Foundation class StationSet : NSObject { var code: String var name: String var prefecture: String init(code: String, name: String, prefecture: String){ self.code = code self.name = name self.prefecture = prefecture } } struct Data: Decodable { var ResultSet: ResultSet } public struct ResultSet: Decodable { var Point: [Point] } public struct Point: Decodable { public let Station: Station public let Prefecture: Prefecture } public struct Station: Decodable { public let code: String public let name: String public let yomi: String public enum CodingKeys: String, CodingKey { case code case name = "Name" case yomi = "Yomi" } } public struct Prefecture: Decodable { public let name: String public enum CodingKeys: String, CodingKey { case name = "Name" } }今まで作っていたmodelの
Stationは名前が被っているのでやむなくStationSetに変更しました。それに応じて他のファイルでの記述も変更が必要です。nested JSONにおけるstructの構造を理解する上でCodingKey理解は必須です。 (自分はまだ完全に理解してないです。)ViewControllerへの記述
ViewController.swiftimport UIKit import Alamofire import SwiftyJSON class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var stationList: UITableView! var stations:[StationSet] = [StationSet]() override func viewDidLoad() { super.viewDidLoad() stationList.dataSource = self stationList.delegate = self stationList.register(UINib(nibName: "StationTableViewCell", bundle: nil), forCellReuseIdentifier: "StationTableViewCell") self.setupStationSets() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func setupStationSets() { //stations = [StationSet(name: "飯田橋", prefecture: "東京都新宿区"), StationSet(name: "九段下", prefecture: "東京都千代田区"), StationSet(name: "御茶ノ水", prefecture: "東京都文京区") ]; let url = URL(string: "http://api.ekispert.jp/v1/json/station?key=[駅すぱあとのKEY]")! Alamofire.request(url, method: .get).responseJSON { response in switch response.result { case .success: do { let resultSetInfo = try JSONDecoder().decode(Data.self, from: response.data!) let stations = resultSetInfo.ResultSet.Point.map({ (Point) -> StationSet in let name = Point.Station.name let code = Point.Station.code let prefecture = Point.Prefecture.name let stationArray = StationSet(code: code, name: name, prefecture: prefecture) return stationArray }) self.stations = stations DispatchQueue.main.async { self.stationList.reloadData() } } catch { print(error) } case .failure(let error): print(error) } } } func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.stations.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "StationTableViewCell", for: indexPath ) as! StationTableViewCell cell.setCell(station: self.stations[indexPath.row]) return cell } }Buildしましょう
次に解説をしていきます。
ViewController.swiftAlamofire.request(url, method: .get).responseJSON { response in switch response.result { case .success: do { let resultSetInfo = try JSONDecoder().decode(Data.self, from: response.data!) let stations = resultSetInfo.ResultSet.Point.map({ (Point) -> StationSet in let name = Point.Station.name let code = Point.Station.code let prefecture = Point.Prefecture.name let stationArray = StationSet(code: code, name: name, prefecture: prefecture) return stationArray }) self.stations = stations DispatchQueue.main.async { self.stationList.reloadData() } } catch { print(error) } case .failure(let error): print(error) }SwiftyJSONの時と結構似てます。
forEach文ではなくmapで一つ一つに代入してまたもやstationsとしてまとめている感じです。このコードの詳細はこちらにあるのでぜひ見てみてください。
https://github.com/takumaosada/customCellTutorial/tree/feature/decodable





