20190218のSwiftに関する記事は6件です。

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もらえるかもしれませんよ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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) // clear

fuwamakiまとめ

  • 必要な単語を全て含んだ命名にしよう
  • 意味を示さない単語は省略して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.) Collection

capabilityを示すプロトコルには接尾に 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コードで書く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とか)の場合はテキスト分のサイズに。ただdistributionalignmentで拡大のルールは決められる。
  • Webでいうflexboxと考え方は一緒。

思ったよりAutoLayoutをコードで書くのがめんどくさくないので今後はコードで書いていきたい。isActive = trueはよく忘れる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者向け]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の名前です.

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/apps

2.My apps画面右上の"Create app"ボタンをクリック
DropBoxメイン画面.png

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 : 好きなアプリ名

DropBoxアプリ登録内容.png

4.App keyを保存(後ほど使います)
App key.png

#2 Xcodeプロジェクト作成

次のステップのcocoapodでライブラリをインストールするので作成、起動できるかテストを行う

#3 cocoapodでDropbox APIのライブラリのインストール

1.ターミナルで#2で作成したプロジェクトの階層まで移動
2.Podfileを生成

pod init

3.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>

参考画像
info.lists.png

以上で下準備は完了です!
次の記事で実装を行なっていきたいと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/decodable

Carthageから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.swift
import 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.swift
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)
      }
    }

今回で変わっているのはここだけです。Alamofireの使い方については今回は省略します。受け取ったデータをresDataと置き、その後forEach文で取ってきた値を1つ1つ代入しています。最後にそれをstationsとしてまとめることで他のファイルの記述に合わせています。最後にself.stationList.reloadData()を記述することを忘れないようにしましょう。

今回使用したコードの詳細はこちらにあります。
https://github.com/takumaosada/customCellTutorial/tree/feature/swifty_json

decodableを利用する方法

こちらが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.swift
import 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.swift
import 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.swift
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)
      }

SwiftyJSONの時と結構似てます。forEach文ではなくmapで一つ一つに代入してまたもやstationsとしてまとめている感じです。

このコードの詳細はこちらにあるのでぜひ見てみてください。
https://github.com/takumaosada/customCellTutorial/tree/feature/decodable

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む