Alamofire,SwiftyJSON,ModelでCellをカスタマイズしてTableViewを描画する


Swiftを初めて10日ほど経ちました。<br>

今回はSwiftで、AlamofireとSwiftyJSONというライブラリを用いて、APIを叩いてそのデータをもとにModelオブジェクトを生成。そのModelオブジェクトをもとにCellを生成してTableViewに描画する
という課題をやりました。
新しい学びが多かったので、まとめてみたいと思います。

##目次

1. そもそもAPIて何？<br>
2. ライブラリとは？<br>
3. 実装概要

***

#1.そもそもAPIて何？

###前提
####webアプリとmobileアプリの違い

・web
ブラウザからサーバーにget requestをして、サーバーにある適当なデータを取ってきて、サーバーからブラウザに情報をhtml形式でpostをする。
ブラウザ上で、htmlを描画する。

・mobile
ブラウザからサーバーにget requestをして、サーバーにある適当なデータを取ってきて、サーバーからブラウザに情報をjson形式でpostをする。
ブラウザ上で、appで対応したhtml,cssに当てはめる。

-> webでは **html形式** で、mobileでは **json形式** でデータを取得している

####なぜWebとmobileで違うのか?
mobileはHDDやメモリがPCと比べると劣る。
-> **mobileは通信が弱い**
-> 通信において、 _最小限の情報量_ で、必要な情報が欲しい
-> _html形式だと余分な情報が含まれている_ ので、mobileではcrashしてしまう恐れがあるので、 _json形式で最小限の情報量_ で取得する(appの中に、html,cssのような見た目の装飾部分についての枠は用意しておく)


###WHAT is "API"
サーバーからjson形式でデータを取得したい時に、それらデータの集合をAPIという。サーバーに収められている、必要な情報の住所みたいなもの。

APIを叩くと、サーバーの中の必要な情報が、json形式で取得できる

そうして得られたjson形式の情報を、
プログラム上の変数に代入することで、
app上で適切に情報が表現される。

この代入する作業が、一番面倒で難しい(らしい)


(jsonはデータのフォーマットで、keyとvalueのセットの羅列)
<br>



#2.ライブラリとは？

汎用性の高い複数のプログラムを、再利用可能な形でひとまとまりにしたもの。

##特徴
・iOSで開発するCocoa touchフレームワークにより、プログラマはアプリ固有のロジックの実装に注力することができる

・iOS開発コミュニティでは多くのライブラリが公開されていて、これを使うことで、より効率よく、高品質なアプリを開発することができる。
***

よく使われるものとして、Alamofire、SwiftyJSON、Alamofire-SwiftyJSONというものがあるらしい。



###Alamofire

HTTP通信を簡単に、そして便利に行うためのライブラリ。
GitHubで公開されている。

###SwiftyJSON

JSONファイルをパースし、Swiftで取り扱うためのライブラリ。
Cocoa TouchフレームワークにもJSONを扱うフレームワークは含まれているが、よりシンプルにJSONを取り扱うことができる。
こちらもGitHubに公開されている。

###Alamofire-SwiftyJSON

Alamofireを拡張しSwiftyJSONと組み合わせて使うためのライブラリ。
Alamofire, SwiftyJSONと合わせて利用することでよりプログラムをシンプルに書くことができる。
Alamofire-SwiftyJSONもGitHubで公開されている。
<br>


```ライブラリの取り込み方
$git clone a_repository
$cd a_repository
$sudo gem install cocoapods
$pod setup
$git checkout -b your_branch_name
$pod install
```

#3.実装概要

API通信とModel作成以外の部分はできている前提で書きます。(余力あればそちらも書きます)

自分が作ったアプリは写真、名前、メッセージを含むCellをUITableViewに描画するapplicationです

**< 概要 >**
***
① profileModelの作成
② viewDidLoad()内でAPIを呼び出し、①で作成したprofileModelに従いオブジェクトを作成
③ 作成したオブジェクトが要素となる配列を作成
④ セクション内の行数を、②で作成したオブジェクトの数に設定する
⑤ UITableViewに描画するCellオブジェクトの初期化をする関数を作成
⑥ ③で作成した配列においてUITableViewで描画しているCellの行数番目の要素をもとに、Cellを初期化していく
***

####① profileModelの作成
File > New File > FileでiOS SourceのSwift Fileを選択して適当に命名する(ProfileListItemTableViewCellModel.swift)


```ProfileListItemTableViewCellModel.swift
import Foundation
import SwiftyJSON

final class ProfileListItemTableViewCellModel: NSObject {
    var imageUrl: String = ""
    var message: String = ""
    var name: String = ""
    
    init? (object: JSON) {
        self.imageUrl = object["image"]["thumb"]["url"].stringValue
        self.message = object["message"].stringValue
        self.name = object["name"].stringValue
    }    
}
```

init?はイニシャライザ。
`ProfileListItemTableViewCellModel(object)`
で、JSONのobjectを引数にしてModelをもとにobjectを初期化できる。

object[][]...はAPIによるので適宜変えてください。

<br>

####② viewDidLoad()内でAPIを呼び出し、①で作成したprofileModelに従いオブジェクトを作成

Alamofireのreadmeを参考に書きました。

```ProfileListViewController.swift

import UIKit
import Alamofire
import SwiftyJSON

class ProfileListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

 override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        // call the notification api
        let apiUrl = "https://hoge/notifications"
        let params = ["access_token": "hogehoge"]
        
        Alamofire.request(.GET, apiUrl, parameters: params)
            .response { (request, response, data, error) -> Void in

                if error == nil {
                    // success handling
                    let jsonData = JSON(data: data! as NSData)
                    for json in jsonData["notifications"].arrayValue {
                        // make a viewcellmodel object
                        let profileCell = ProfileListItemTableViewCellModel(object: json)
                    }
                    self.tableView.reloadData()
                } else {
                    // error handling
                    // user alertとか
                    print(error)
                }
                
        }
    }
    
    ...
    
 }

```

self.tableView.reloadData()はライフサイクルや内部のコンパイルがどうなっているかの話に関連しているみたい。余力があれば追記します。
要は、APIへ通信してとってきたデータをapplicationに反映するためにデータをすべて取りきれたらもう一度cellを描画し直している。
<br>

####③ 作成したオブジェクトが要素となる配列を作成

profileCellsという配列を宣言して、②で作成したobjectを末尾に追加していく

```ProfileListViewController.swift

class ProfileListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
 
 // 新たに追加
 var profileCells: [FFAProfileListItemTableViewCellModel] = [FFAProfileListItemTableViewCellModel]()
 
...

 override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        // call the notification api
        let apiUrl = "https://hoge/notifications"
        let params = ["access_token": "hogehoge"]
        
        Alamofire.request(.GET, apiUrl, parameters: params)
            .response { (request, response, data, error) -> Void in

                if error == nil {
                    // success handling
                    let jsonData = JSON(data: data! as NSData)
                    for json in jsonData["notifications"].arrayValue {
                        // make a viewcellmodel object
                        let profileCell = ProfileListItemTableViewCellModel(object: json)
                        
                        //ここを新たに追加
                        // make an array that includes some viewcellmodel objects
                        self.profileCells.append(profileCell!)
                    }
                    self.tableView.reloadData()
                } else {
                    // error handling
                    // user alertとか
                    print(error)
                }
                
        }
    }
    
    ...
    
 }

```

<br>

####④ セクション内の行数を、②で作成したオブジェクトの数に設定する
```ProfileListViewController.swift

...

 func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return profileCells.count
 }
 
...


```

<br>

####⑤ UITableViewに描画するCellオブジェクトの初期化をする関数を作成

```ProfileListItemTableViewCell.swift
import UIKit

class ProfileListItemTableViewCell: UITableViewCell {

    @IBOutlet weak var profileImage: UIImageView!
    @IBOutlet weak var profileName: UILabel!
    @IBOutlet weak var profileIntro: UILabel!
        
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        // Configure the view for the selected state
    }
    
    // initialize the cell object made from ProfileListItemTableViewCellModel
    func setCell(cellobject: ProfileListItemTableViewCellModel, atIndexPath indexPath: NSIndexPath) {
        let imageView = profileImage as UIImageView
        imageView.setUserAvatar(cellobject.imageUrl)
        profileName.text = cellobject.name
        profileIntro.text = cellobject.intro
    }
    
}
```

<br>

####⑥ ③で作成した配列においてUITableViewで描画しているCellの行数番目の要素をもとに、Cellを初期化していく

```ProfileListViewController.swift

...

 func tableView(table: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {        
     let cell = tableView.dequeueReusableCellWithIdentifier("ProfileCell") as! ProfileListItemTableViewCell
     cell.setCell(profileCells[indexPath.row], atIndexPath: indexPath)
     return cell     
  }

```

<br>

以上です。