- 投稿日:2020-01-03T17:23:17+09:00
Swiftのモック生成ライブラリ「Mockolo」のセットアップ&操作方法
はじめに
Xcodeにはモック生成機能が搭載されておらず、手動で実装するのが大変だと感じてきたため、導入することにしました。
「Mockolo」とは?
Swift用のモック生成ライブラリです。
現在はプロトコルのモック生成のみ対応しており、クラスのモック生成は追加予定とのことです。
環境
- OS:macOS Catalina 10.15.2
- Swift:5.1.3
- Xcode:11.3 (11C29)
- Mockolo:1.1.1
セットアップ
Mockoloのインストール
Mintからインストールします。
Mintfile+ uber/mockolo@1.1.1$ mint bootstrap手動でインストールするには、公式ドキュメントをご参照ください。
https://github.com/uber/mockolo#build--installビルド時にモックを生成するようにする
Mockoloはモックの生成時間が速いため、ビルドするたびにモックを生成するようにします。
ビルド時間が気になる場合、手動でコマンドを実行してモックを生成してください。Xcodeでプロジェクトを開く
TARGETSで製品ターゲットを選択 > Build Phases > +をクリック > New Run Script Phase >
ドラッグ&ドロップで「Compile Sources」の直前に移動
スクリプトは「Generate Mocks with Mockolo」のようにわかりやすい名前を付けるといいです。
展開して以下のスクリプトを記述します。
if which mint >/dev/null; then mint run mockolo mockolo --sourcedirs $SRCROOT/{製品ターゲット名} --destination $SRCROOT/MockResults.swift else echo "warning: Mint not installed, download from https://github.com/yonaskolb/Mint" fiOutput Files > +をクリック
--destinationで指定しているファイルパスを記述します。$SRCROOT/MockResults.swift生成されるファイルを記述しないと、CI/CD時に以下のエラーが発生します。
error: Build input file cannot be found:以下の記事を参考にさせていただきました。
https://qiita.com/lovee/items/fa3ef5e60cfbf31996c0Mintを使っていない場合、
mint run mockoloを外し、if文の条件を変更してください。使っているオプションを説明します。
以下の2つは必須であり、必要に応じて値を変更してください。
オプション 説明 --sourcedirs 生成対象のフォルダパス
製品ターゲット名のフォルダを指定すれば、通常は全ファイルを対象にできる--destination モックの生成パス その他のオプションは公式ページまたは
mockolo --helpをご参照ください。プロジェクトをビルドし、「$SRCROOT(通常はプロジェクトのルートフォルダ)」に「MockResults.swift」が生成されたら、プロジェクトにドラッグ&ドロップします。
[Copy items if needed]チェックをOFFにし、[Finish]をクリックします。
バージョン管理から無視する
不要な競合を防ぐため、生成された「MockResults.swift」をバージョン管理の対象外にします。
Gitを使っている場合、以下を「.gitignore」に追加するのみでOKです。
.gitignoreMockResults.swift操作方法
モックを生成したいプロトコルに
@mockableのドキュメンテーションコメントを付けます。
タイプエイリアスがある場合、カッコ内に書きます。Foo.swift/// @mockable(typealias: T = AnyObject; U = StringProtocol) public protocol Foo { associatedtype T associatedtype U: Collection where U.Element == T associatedtype W var num: Int { get set } func bar(arg: Float) -> String }ビルドすると、モックが生成されます。
MockResults.swift// クラス名は `{プロトコル名}Mock` となる public class FooMock: Foo { typealias T = AnyObject typealias U = StringProtocol typealias W = Any // 指定しないと `Any` になる init() {} init(num: Int = 0) { self.num = num } var numSetCallCount = 0 var underlyingNum: Int = 0 var num: Int { get { return underlyingNum } set { underlyingNum = newValue numSetCallCount += 1 } } var barCallCount = 0 var barHandler: ((Float) -> (String))? func bar(arg: Float) -> String { barCallCount += 1 if let barHandler = barHandler { return barHandler(arg) } return "" } }自動生成されたコードのうち、テストで使うプロパティのみ説明します。
プロパティ 説明 {プロパティ名}SetCallCount セッターの呼び出し回数 {メソッド名}CallCount メソッドの呼び出し回数 {メソッド名}Handler メソッドの呼び出し時に実行されるクロージャ テスト時は以下のように使います。
FooTests.swiftfunc testMock() { // モックを生成する let mock = FooMock(num: 5) // 対象プロパティのセット回数を確認する XCTAssertEqual(mock.numSetCallCount, 1) // ハンドラは対象メソッドの呼び出し前に自分で代入する mock.barHandler = { arg in return String(arg) } // 対象メソッドの呼び出し回数を確認する XCTAssertEqual(mock.barCallCount, 0) }おわりに
とても簡単にモックを生成できました!
これでテスト時にVIPERのモックを手動で実装する手間が省けるぞ?もっと早く導入すればよかったと思いました笑
参考リンク
- 投稿日:2020-01-03T16:04:55+09:00
【Swift】三項演算子をswitch文に置き換えたら読みやすくなった
三項演算子はよく使いますが、条件が増えるとわかりづらくなるという難点があります。
そこで、switch文にしたら読みやすくなったという話です。もともとのコード
三項演算子let condA = true let v = condA ? 1 : 2条件が一つしかない場合は、三項演算子を使うとわかりやすいコードになります。
ところが、ここに新しい条件
CondBが増えると、条件が2つある場合let condA = true let condB = false let w = condA ? (condB ? 1 : 2) : 3このように入れ子になってしまいわかりづらくなります。
switch文を使って書き換えてみる
上のコードを
switch文で書き換えてみます。switch文を使った場合var x:Int switch(condA, condB) { case (true,true): x = 1 case (true,false): x = 2 case (false,true), (false,false): x = 3 }
condAとcondBの状態ごとにどのような値になるかが一目瞭然です。さらに改良してみる
上のコードをさらに改良して、代入式にまとめてみます。
改良をしたコードlet y = { condA, condB -> Int in switch(condA, condB) { case (true,true): return 1 case (true,false): return 2 case (false,true), (false,false): return 3 } }(condA, condB)このようにすることで、
CondAとCondBを入力としてyの値を定める、という全体の流れを表現することができるので読みやすくなります。まとめ
三項演算子を使っていて条件が増えた場合、switch文を使ったら条件が直感的に表現されたので読みやすいコードになりました。また、switch文は条件のすべてを列挙する必要があるため、複数の条件があったときに考慮もれの防止にも繋がります。
- 投稿日:2020-01-03T15:42:08+09:00
モンテカルロ法でルーレットに賭け続けた場合に本当に勝てるのか調べた話
初めに
モンテカルロ法というギャンブルの戦略があることを知り、実際にこれで勝負した場合の勝率がどの程度になるのかプログラムを書いて調べてみた。参考動画: https://www.youtube.com/watch?v=_suZM2y9wyA
モンテカルロ法とは
勝率1/2の勝負に、以下のルールでベットを行い続ける方法
まず
[1, 2, 3]の配列を作成する-- loop --
- 配列の要素数が2未満だったらループを抜ける
- 配列の先頭と末尾の要素を足した額をベットする (初期状態では
4)- 勝ったら → 配列の先頭と末尾の要素を削除し 1. に戻る。
負けたら → 配列の末尾に2.でベットした額と同じ値を追加し 1. に戻る-- loop end --
書いたコード
https://github.com/yamazaki-sensei/montecarlo
- ルーレットで赤or黒に賭けるのと同様の場合を想定。
- 0だと赤でも黒でも外れ。
- 予算は100000単位
- プログラムでは10000ループのモンテカルロ法を行う
import Foundation let roulette = (0...36) // 疑似ルーレット func spin() -> Bool { return roulette.randomElement()! >= 19 // 0から18の数字が出たら負け } let n = 100 var budget = 100_000 let initialState = [1, 2, 3] (1...100).forEach { _ in assert(budget > 0, "破産") (1...n).forEach { _ in var state = initialState while state.count >= 2 { if state.last! > 1000 { print(state) } let toBet = state.first! + state.last! if spin() { state.removeFirst() state.removeLast() budget += toBet } else { budget -= toBet state.append(toBet) } } } print("#################") print(budget) }所感
- 10000ループだと80%くらいの確率で勝つ気がする。
- 勝った時の儲けはだいたい10000単位くらい。
- 負けるときは大負けして最終的なbudgetが10000とかになる → このときの
stateを覗いてみると、state.last!が10000以上とかになっている。怖い。- コードを変えて100000ループ回すと、30%くらいの確率で破産する。怖い。
終わりに
実世界の場合一度破産したら死んでしまうので恐ろしくてなかなか出来ない。やはり必勝法などは無かった。
- 投稿日:2020-01-03T10:58:51+09:00
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターン ~Composite~
この記事シリーズは、iOS/Swiftエンジニアである執筆者個人が、
ごく普通のiOSアプリ開発でよくある状況や
Swiftのコアライブラリやフレームワークで使われているパターンに
着目してデザインパターンを学び直してみた記録です。関連記事一覧
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターンCompositeパターン概要
- ディレクトリとファイルのような、ツリー構造を扱うためのパターンです。
- ディレクトリとファイルのように、「名前」などの同じプロパティや、「削除」などの同じ操作を持つ場合、ディレクトリ(容器)とファイル(中身)を同じように扱うことができます。
- GoFのデザインパターンでは構造に関するパターンに分類されます。
使い所
- そのものズバリ、ツリー構造を扱う場合には、Compositeパターンを思い浮かべると設計に掛かる時間を短縮できる可能性が高いです。
- UIViewのView Hierarchyもツリー構造であり、Compositeパターンが使われています。
引用:Cocoa Design Patterns (Retired Document)サンプルコード
Swiftバージョンは 5.1 です。
protocol DirectoryEntry { var name: String { get } func remove() } final class File: DirectoryEntry { let name: String init(name: String) { self.name = name } func remove() { print("\(name)を削除しました") } } final class Directory: DirectoryEntry { let name: String private var entryList = [DirectoryEntry]() init(name: String) { self.name = name } func add(entry: DirectoryEntry) { entryList.append(entry) } func remove() { for entry in entryList { entry.remove() } print("\(name)を削除しました") } } // Usage let dir1 = Directory(name: "dir1") let file1 = File(name: "file1") dir1.add(entry: file1) // dir1 // ∟file1 let dir2 = Directory(name: "dir2") let file2 = File(name: "file2") let file3 = File(name: "file3") dir2.add(entry: file2) dir2.add(entry: file3) // dir2 // ∟file2 // ∟file3 dir1.add(entry: dir2) // dir1 // ∟file1 // ∟dir2 // ∟file2 // ∟file3 let file4 = File(name: "file4") dir1.add(entry: file4) // dir1 // ∟file1 // ∟dir2 // ∟file2 // ∟file3 // ∟file4 dir1.remove() // "file1を削除しました" // "file2を削除しました" // "file3を削除しました" // "dir2を削除しました" // "file4を削除しました" // "dir1を削除しました"
- 投稿日:2020-01-03T10:52:48+09:00
SwiftのTableViewCellを使ってTableViewを自由にカスタマイズ
概要
この記事は初心者の自分がRESTful なAPIとswiftでiPhone向けのクーポン配信サービスを開発した手順を順番に記事にしています。技術要素を1つずつ調べながら実装したため、とても遠回りな実装となっています。
前回の swiftで配列型のJSONから値を取り出す でクーポンの詳細情報や利用可能店舗名、有効期間の情報を取得できるようになりましたので、それらをアプリ画面に表示するようにTableViewを改造します。
参考
- やさしくはじめる iPhoneアプリ作りの教科書 森 巧尚 著 マイナビ
環境
Mac OS 10.15
Swift5
Xcode11.1手順
- Main.storyboardでTableViewCellをデザイン
- 配置したラベルにそれぞれの情報を表示できるようにViewControllerを改造
- 動作確認
Main.storyboardでTableViewCellをデザイン
現在は subtitle というTableViewの標準のデザインを使い、メイン情報を詳細情報の2つを表示する仕様になっています。3つ以上の情報をtableViewに表示したい場合は標準デザインが対応していないためオリジナル仕様のTableViewCellを作る必要があります。
まず既に配置済みのTableViewの上にTableViewCellを配置します。ObjectsのウィンドウはXcodeの右上の「+」ボタンで表示できます。
配置したTableViewCellのIdentifier にTableViewCellを特定するための名前をつけます。
右側の「show the size inspector」でTableViewCellの高さを200ptに設定し、TableViewCellの上に、クーポンの情報を表示するためのLabelを配置しました。ラベルの用途は下記の通りです。
- クーポン特典を表示するラベル
- クーポンのコメントを表示するラベル
- 利用可能店舗を表示するラベル
- 開始利用開始日と利用期限を繋げて利用可能期間を表示するラベル
それぞれのラベルをプログラムから識別するためにtag(通し番号)をつけます。
タグ番号の振り分けは下記の通りにしました。
- クーポン特典を表示するラベル
- クーポンのコメントを表示するラベル
- 利用可能店舗を表示するラベル
- 開始利用開始日と利用期限を繋げて利用可能期間を表示するラベル
クーポン特典とコメントは複数行で表示させたいので行数を2行に設定します。LabelのLinesで行数を設定できます。
以上でTableViewCellのデザインは完了です。
配置したラベルにそれぞれの情報を表示できるようにViewControllerを改造。
「10_swiftでクーポンを表示する画面を実装する」で作成したプログラムを改造していきます。
改造する箇所は関数func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)だけです。まず、セルオブジェクトを作るコードをMain.storyboard でデザインしたCell(couponCell)のセルオブジェクトを作る形式に変えます。
変更前
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "couponCell")変更後
withIdentifier:のところは 作成したTableViewCellのIdentifierに設定した名前(今回は couponCell)を指定します。let cell = tableView.dequeueReusableCell(withIdentifier: "couponCell", for: indexPath as IndexPath)次に、セルの高さを設定します。(Main.storyboardでも設定しましたが、ViewControllerでも設定しないと反映しませんでした。)
tableView.rowHeight = 200次に各Labelに表示する値を設定します。
ラベルオブジェクトを作るところで、cell.viewWithTag( )の「( )」に設定したtagの通し番号を入力します。ラベルに表示する文字列を設定する部分で、変数couponは[String: Any]型なので、String型にキャストします。コードは以下のようになります。//ラベルオブジェクトを作る let labelBenefit = cell.viewWithTag(1) as! UILabel //ラベルに表示する文字列を設定 labelBenefit.text = (coupon["coupon_benefits"] as! String)これを4つのラベル分、実装します。
出来上がったコードはこちらです。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let coupon = self.coupons[indexPath.row] //セルを作る let cell = tableView.dequeueReusableCell(withIdentifier: "couponCell", for: indexPath as IndexPath) tableView.rowHeight = 200 //各ラベルに値を設定する let labelBenefit = cell.viewWithTag(1) as! UILabel labelBenefit.text = (coupon["coupon_benefits"] as! String) let labelExplanation = cell.viewWithTag(2) as! UILabel labelExplanation.text = (coupon["coupon_explanation"] as! String) let labelStore = cell.viewWithTag(3) as! UILabel labelStore.text = (coupon["coupon_store"] as! String) let labelDay = cell.viewWithTag(4) as! UILabel labelDay.text = "有効期間: " + (coupon["coupon_start"] as! String) + " ~ " + (coupon["coupon_deadline"] as! String)動作確認
実行したところ、デザインした通りにクーポンが表示されています。
これで少しクーポンらしくなりました。
次回は、webAPI側をRESTfulなAPIに改造します。
- 投稿日:2020-01-03T10:48:08+09:00
swiftで配列型のJSONから値を取り出す
概要
この記事は初心者の自分がRESTfulなAPIとswiftでiPhone向けのクーポン配信サービスを開発した手順を順番に記事にしています。技術要素を1つずつ調べながら実装したため、とても遠回りな実装となっています。
前回のDBに格納したデータを返すwebAPIをDjangoとSQLiteで開発するでWebAPIの仕様を変えたので、新しいAPIの仕様に合わせて、swiftでwebAPIを呼び出してjsonデータを表示させる で作成したswiftのコードを改造します。大きな変更点は、レスポンスのjsonオブジェクトが配列になった点です。これにより複数のクーポン情報が取得出来るようになりました。
これまでのjson (例)
{“name”:”beer”,”price”:500}新しいjson (例)
[ {“name”:”beer”,”price”:500} {“name”:”vodka”,”price”:700} {“name”:”scotch”,”price”:800} ]jsonのデータを取り出してパースする処理を配列型に対応させる必要があります。
参考
- やさしくはじめる iPhoneアプリ作りの教科書 森 巧尚 著 マイナビ
- 【swift入門】apiを叩いてTableViewに表示させる
- JSON (JavaScript Object Notation)の書式、エンコーディング、MIME Type について
環境
Mac OS 10.15
Swift5
Xcode11.1手順
- レスポンスの内容をData型からjson形式に変換しコンソールで確認する
- jsonをAny型にキャストする
- 配列に格納されているjsonのオブジェクトを一つずつ String型にキャストする
レスポンスのjsonの形式を確認する
webAPI側はjsonをオブジェクトの配列で返す仕様になっていますが、念のためどの様なjsonが返ってくるか確認します。
まずはブラウザでAPIのURLにアクセスしてjsonの形式を確認します。
次にターミナルでも確認してみます。ターミナルの場合は curl コマンドで APIのURLにアクセスします。
curlコマンドを使う方が改行がされていて確認しやすいですね。もっと複雑なJSONの確認をする場合は専用のツールを使うの方法があるそうです。
想定した通り、配列型の下記の様な形式のjsonになっています。
[ {...} {...} {...} ]次にXcodeでData型をjsonに変換しただけのデータをコンソールに出力させてみます。swiftでwebAPIを呼び出してjsonデータを表示させる で作成したコードを使う場合は、
ViewController.swiftのviewDidLoadの一部を下記の通りコメントアウトや修正します。
- APIのURLを
http://127.0.0.1:8000/coupon/に変更JSONSerialization〜の後ろのas! [String: Any]は型違いでエラーになるので削除- TableViewとデータをやり取りするためのグルーバル変数に代入する処理をコメントアウト
修正後のviewDidLoadはこちらです。
ViewController.viewDidLoadoverride func viewDidLoad() { super.viewDidLoad() let url: URL = URL(string: "http://127.0.0.1:8000/coupon/")! // URLの変更 let task: URLSessionTask = URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in do { let couponData = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) print(couponData) /* ここはコメントアウト DispatchQueue.main.async() { () -> Void in self.couponBenefit = couponData["coupon_benefits"] as! String self.couponDeadline = couponData["coupon_deadline"] as! String } */ } catch { print(error) } }) task.resume() }この状態で実行してコンソールに出力されるjsonを確認します。すると、下記の様に出力されます。
ブラウザやcurlで表示されるjsonは外側のかっこが
[ ]ですが、アプリ上ではかっこが( )になっています。このままでは配列として扱えず、中のデータが取り出せません。そこで、Any型にキャストします。JSONをAny型にキャストする
JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments)の後ろにas! [Any]を付けるだけです。修正前のコードではas! [String: Any]が付いていたところです。do { let couponData = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as! [Any] //Any型にキャスト print(couponData) //...中略... } catch { print(error) }すると、コンソールに出力されたjsonの外側のかっこが
( )から[ ]に変わりました。これで配列として扱えるようになりました。
配列に格納されているjsonのオブジェクトを一つずつ String型にキャストする
jsonクラスのmapという関数を使い、jsonの配列のオブジェクトに対して[String: Any]型にキャストしたものを新しい配列
couponDataに格納するようにします。
JSONSErializationの処理の下に以下の処理を追加します。let couponData = couponDataArray.map { (couponData) -> [String: Any] in return couponData as! [String: Any] }ここまでの処理で個々のjsonオブジェクトの情報を取り出せるようになりました。情報を取り出す際、変数
couponDataはjsonオブジェクトと、オブジェクトに格納されているモデルフィールドの二重配列となっているので、
個々のデータは、couponData[オブジェクトの番号(0~)][“モデルフィールド名(jsonのキーと同じ)”]で指定できます。但しそのままだと[String: Any]型なので、取り出すモデルフィールドの型に合わせてキャストします。文字型の場合は
as! Stringでキャストします。下記のようなコードでデータを取り出していきます。couponData[オブジェクトの番号(0~)][“JSONのキー名”] as! [モデルフィールドの型]試しに以下のコードを追加し、クーポン2つ分のクーポン特典をコンソールに出力させてみます。
print(couponData[0]["coupon_benefits"] as! String) print(couponData[1]["coupon_benefits"] as! String)実行してコンソールを見ると、クーポン2つ分のクーポン特典が出力されているので処理が成功しています。
以上でオブジェクトの配列形式のjsonから個々の情報を取り出せるようになりました。
複数のクーポン情報をTableViewで表示するように改造する。
複数のクーポンの情報をレスポンス出来るようになったので、TableViewに複数のクーポンの情報を表示できるようにします。
まず、TableViewにデータを渡す方法を変え、jsonオブジェクトの配列を[String: Any]型にパースした 変数
couponDataをそのままTableViewに渡すようにします。そのためメンバー変数の定義部分を下記のように修正します。
- メンバー変数の型を[String: Any]型にし、couponDataをそのまま格納出来るようにする
- オブジェクトの項目毎のメンバ変数は不要なので削除
var coupons: [[String: Any]] = [] { //パースした[String: Any]型のクーポンデータを格納するメンバ変数 didSet{ tableView.reloadData() } }次にテーブルの行数を渡している部分を改造します。配列の
countメソッドを使ってデータを格納するメンバ変数(coupons)の配列の行数を渡すようにします。func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.coupons.count }次にテーブルに表示するデータを渡している部分を改造します。
辞書型変数の
couponを定義し、couponsの配列の1行(jsonの{...}で囲まれたオブジェクト)ごとにcouponに格納します。そして辞書型のcouponからモデルフィールドの名前(jsonのキー)でデータを取り出し、データをString型にキャストしてセルに渡します。func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "couponCell") //辞書型変数のcouponを定義 let coupon = self.coupons[indexPath.row] //モデルフィールドの名前でデータを取り出し、String型にキャストしてセルに渡す cell.textLabel?.text = (coupon["coupon_benefits"] as! String) cell.detailTextLabel?.text = "有効期限:" + (coupon["coupon_deadline"] as! String) return cell }最後に
viewDidLoadでjsonをパースしてメンバ変数に格納する部分を改造します。String型の変数にjsonオブジェクト内のデータを直接格納していましたが、
[String: Any]型のままTableViewに渡す仕様となったため、jsonをパースした[String: Any]型のデータをそのままメンバ変数couponsに渡すようにします。DispatchQueue.main.async() { () -> Void in self.coupons = couponData }アプリを起動すると、先にjsonで確認した3件つのクーポンがTableViewで表示されるようになりました。
修正後のViewControllerクラスは下記の通りです。
ViewController.swiftimport UIKit class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate { @IBOutlet weak var tableView: UITableView! var coupons: [[String: Any]] = [] { //パースした[String: Any]型のクーポンデータを格納するメンバ変数 didSet{ tableView.reloadData() } } override func viewDidLoad() { super.viewDidLoad() let url: URL = URL(string: "http://127.0.0.1:8000/coupon/")! let task: URLSessionTask = URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in do { let couponDataArray = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as! [Any] let couponData = couponDataArray.map { (couponData) -> [String: Any] in return couponData as! [String: Any] } DispatchQueue.main.async() { () -> Void in self.coupons = couponData } } catch { print(error) } }) task.resume() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.coupons.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "couponCell") //辞書型変数のcouponを定義 let coupon = self.coupons[indexPath.row] //モデルフィールドの名前でデータを取り出し、String型にキャストしてセルに渡す cell.textLabel?.text = (coupon["coupon_benefits"] as! String) cell.detailTextLabel?.text = "有効期限:" + (coupon["coupon_deadline"] as! String) return cell } }以上です。
- 投稿日:2020-01-03T10:13:42+09:00
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターン ~Singleton~
この記事シリーズは、iOS/Swiftエンジニアである執筆者個人が、
ごく普通のiOSアプリ開発でよくある状況や
Swiftのコアライブラリやフレームワークで使われているパターンに
着目してデザインパターンを学び直してみた記録です。関連記事一覧
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターンSingletonパターン概要
- インスタンスが1個しか生成されないことを保証するためのパターンです。
- GoFのデザインパターンでは生成に関するパターンに分類されます。
使い所
- iOSフレームワーク内で随所に使われています。典型例は
UIApplication.sharedです。- 実行時に依存性を変更しづらく、ユニットテストがしにくいというデメリットがあります。
- またグローバル変数と同様に、いつ・どこから・どのように変更されるのか理解が難しくなりますので、注意しながら利用する必要があります。
サンプルコード
Swiftバージョンは 5.1 です。
class SingletonClass { class var shared : SingletonClass { struct Static { static let instance : SingletonClass = SingletonClass() } return Static.instance } } // Usage let instance = SingletonClass.shared※少々回りくどい実装になっていますが、マルチスレッドでインスタンスが複数生成されてしまうことを回避するための実装例です。
- 投稿日:2020-01-03T08:20:49+09:00
「リファクタリング 第2版」Swiftでコーディング その15
21-22頁 第1章 ボリューム特典ポイント集計箇所の削除 totalAmount変数の削除
Swift版 main.swift
データ生成、結果表示付き。
import Foundation makeData() func playFor(aPerformance:Performance) -> Play { return plays[aPerformance.playID]! } func volumeCreditsFor(aPerformance:Performance) -> Int { var result = 0 result += max(aPerformance.audience - 30, 0) if "comedy" == playFor(aPerformance: aPerformance).type { result += Int(aPerformance.audience / 5) } return result } func usd(aNumber:Int) -> String { let format = NumberFormatter() format.numberStyle = .currency format.locale = Locale(identifier: "en_US") return format.string(from: NSNumber(value: aNumber / 100))! } func totalVolumeCredits(invoice:Invoice) -> Int { var result = 0 for perf in invoice.performances { result += volumeCreditsFor(aPerformance: perf) } return result } func totalAmount(invoice:Invoice) -> Int { var result = 0 for perf in invoice.performances { result += amountFor(aPerformance: perf) } return result } func statement(invoice:Invoice, plays:Dictionary<String, Play>) -> String { var result = "Statement for \(invoice.customer)\n" for perf in invoice.performances { result += " \(playFor(aPerformance: perf).name): " + usd(aNumber: amountFor(aPerformance: perf)) + " (\(perf.audience) seats)\n" } result += "Amount owed is " + usd(aNumber: totalAmount(invoice: invoice)) + "\n" result += "You earned \(totalVolumeCredits(invoice: invoice)) credits\n" return result } func amountFor(aPerformance:Performance) -> Int { var result = 0 switch playFor(aPerformance: aPerformance).type { case "tragedy": result = 40000 if aPerformance.audience > 30 { result += 1000 * (aPerformance.audience - 30) } case "comedy": result = 30000 if aPerformance.audience > 20 { result += 10000 + 500 * (aPerformance.audience - 20) } result += 300 * aPerformance.audience default: print("error") } return result } let result = statement(invoice: invoices[0], plays: plays) print(result)
- 投稿日:2020-01-03T08:19:13+09:00
「リファクタリング 第2版」Swiftでコーディング その14
18-20頁 第1章 ボリューム特典ポイント集計箇所の削除 「ループの分離(p.236)」「ステートメントのスライド(p.231)」「問い合わせによる一時変数の置き換え(p.185)」「関数の抽出(p.112)」
Swift版 main.swift
データ生成、結果表示付き。
import Foundation makeData() func playFor(aPerformance:Performance) -> Play { return plays[aPerformance.playID]! } func volumeCreditsFor(aPerformance:Performance) -> Int { var result = 0 result += max(aPerformance.audience - 30, 0) if "comedy" == playFor(aPerformance: aPerformance).type { result += Int(aPerformance.audience / 5) } return result } func usd(aNumber:Int) -> String { let format = NumberFormatter() format.numberStyle = .currency format.locale = Locale(identifier: "en_US") return format.string(from: NSNumber(value: aNumber) / 100)! } func totalVolumeCredits(invoice:Invoice) -> Int { var volumeCredits = 0 for perf in invoice.performances { volumeCredits += volumeCreditsFor(aPerformance: perf) } return volumeCredits } func statement(invoice:Invoice, plays:Dictionary<String, Play>) -> String { var totalAmount = 0 var result = "Statement for \(invoice.customer)\n" for perf in invoice.performances { result += " \(playFor(aPerformance: perf).name): " + usd(aNumber: amountFor(aPerformance: perf)) + " (\(perf.audience) seats)\n" totalAmount += amountFor(aPerformance: perf) } result += "Amount owed is " + usd(aNumber: totalAmount) + "\n" result += "You earned \(totalVolumeCredits(invoice: invoice)) credits\n" return result } func amountFor(aPerformance:Performance) -> Int { var result = 0 switch playFor(aPerformance: aPerformance).type { case "tragedy": result = 40000 if aPerformance.audience > 30 { result += 1000 * (aPerformance.audience - 30) } case "comedy": result = 30000 if aPerformance.audience > 20 { result += 10000 + 500 * (aPerformance.audience - 20) } result += 300 * aPerformance.audience default: print("error") } return result } let result = statement(invoice: invoices[0], plays: plays) print(result)















