Swiftを初めて10日ほど経ちました。
今回はSwiftで、AlamofireとSwiftyJSONというライブラリを用いて、APIを叩いてそのデータをもとにModelオブジェクトを生成。そのModelオブジェクトをもとにCellを生成してTableViewに描画する
という課題をやりました。
新しい学びが多かったので、まとめてみたいと思います。
目次
- そもそもAPIて何?
- ライブラリとは?
- 実装概要
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のセットの羅列)
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で公開されている。
$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)
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によるので適宜変えてください。
② viewDidLoad()内でAPIを呼び出し、①で作成したprofileModelに従いオブジェクトを作成
Alamofireのreadmeを参考に書きました。
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を描画し直している。
③ 作成したオブジェクトが要素となる配列を作成
profileCellsという配列を宣言して、②で作成したobjectを末尾に追加していく
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)
}
}
}
...
}
④ セクション内の行数を、②で作成したオブジェクトの数に設定する
...
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return profileCells.count
}
...
⑤ UITableViewに描画するCellオブジェクトの初期化をする関数を作成
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
}
}
⑥ ③で作成した配列においてUITableViewで描画しているCellの行数番目の要素をもとに、Cellを初期化していく
...
func tableView(table: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ProfileCell") as! ProfileListItemTableViewCell
cell.setCell(profileCells[indexPath.row], atIndexPath: indexPath)
return cell
}
以上です。