設定
iPad 向けになってる。
iTunesConnect に申請してみる
iPad 向けにしてるのに iphone の storyboard がウンヌンって言われてる。
Info.plist 見てみる
邪魔そうなのがいる… 👀
消してみる
もう一回申請すると
エラーが消えました 🙌
Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)
良い本だった、書名になっているが初心者向けではなく実践入門なので、実践的な内容が多くためになる。
だいたい 16 時間くらいで読みおわった。
普段ゆるふわで書いているとクロージャやジェネリクスなど使いこなせていないし、非同期処理なども注意がおそろかになりがちだし、循環参照とかも起きてしまう。
そこらへんちゃんと理解してちゃんと書いていこうという内容であるし、この本を読みながら既存のアプリをリファクタリングしていきたい。
読み終わったら終わりな本ではなくて、手元に置いておいて Swift 書きながら読み返して体に染み込ませていくのがいいのかなと思いました。
石川さんから『Swift 実践入門』を頂いたので、見所などを綴ってみました。 にまともな書評がある。
ざっくり Swift こんな言語だよ〜って説明。
静的型付けで Optional 良いよねとかそんな話。
OSS にもなってるよ、コマンドラインとかからでも実行できるよ〜みたいな内容。
変数とか定数、基本的な型(String, Int, Optional など…)の話。
強制アンラップはやべーよなとか、キャストとかも書いてある、ほとんど知ってたけど Swift 書くなら一度読んでおいた方が良い内容。
command + control + ?
(英字keyの場合は command + control + shift + /
) で型情報見れる。
if, switch, guard などのはなし、深く実践的な内容が書かれている。
if case 1...10 = value
みたいに書く。この書き方いつも忘れる。switch case
の where
はあまり使ってない、というか存在を忘れていたので今後使っていこう。for-case in
という書きかた普通に知らなかった、便利だ。関数は他の言語と大体同じ。
クロージャは使いこなせておらず知らいことが多い、めっちゃ便利だがそれなりに難しく、後3回くらい読み直したい。
class, struct, enum で利用出来るプロパティやメソッドの説明。
今まで雰囲気で Swift 書いてきた人間はこの章で死ぬ。
class, struct, enum の説明。
class は参照型、struct は値型で、参照型は意図せぬ値の変更などが起きうるのでなるべく値型を使い、参照の共有が必要な時だけ参照型を使おう。
他の言語でいうインターフェースに近い。
100回読みたい。
100回読みたい。
配布可能なプログラム形式(import するやつ)、フレームワークとかを作ろうって話。
普通にプログラム書いてる分には Framework 作ることはないかなと思う。
クラスより構造体を使うべき。
ただしクラスの方が向いている処理もあるので、状況を見て判断すること。
UI のタップとかのイベントをどう取り扱うか。 デリゲート、クロージャ、オブザーバパターンなどある。
循環参照について書かれている、100回読みたい。
マルチスレッドのはなし。
スレッドを使うにはGCD、OperationQueue、 Thread のどれかを使う。
Thread を使うケースは稀で、簡単な非同期は GCD、複雑な非同期は OperationQueue を使うのが良さそう。
めちゃくちゃためになるというか、真面目に考えないといいアプリにならないがなかなか難しい。
スレッド書くときは読みながら書きたい。
エラーハンドリング。
Optional, Result<T, Error>, do-catch, try, fatalError, アサーションなどの使い方や使い分けなど。
最後にどういういう時にどのエラーを使うべきかという流れが書いてあって便利。
API Client を作っていく。
APIKit を作っていく感じ。
Protocol + Extension で抽象的なプログラムを書くの楽しい。
全部写経した。
starhoshi/GitHubSearchRepository: Swift 実践入門
Objective-C は実績のある言語だから、過去に書かれた ObjC を Swift で書き直していくにはどうすべきか?みたいな話。
Swift から ObjC の参照の仕方や、 ObjC もちゃんと書いてないと Swift から利用するのはむしろ逆に辛いかもしれないので、 ObjC もちゃんと書こうという話がされてる。
そもそも ObjC 全然詳しくないので勉強にはなるけど、正直 ObjC に努力値振る暇あったら Swift の知見深めたいところではある。
JetBrains のエディタは普通に使おうとするとお金がかかるのだが、 OSS を開発している人は無料でライセンスがもらえる。(もらえる条件はもうちょっと色々ある)
ライセンスをもらうには申請が必要なのだが、その申請が通りライセンスをもらうことができた。
ただし、このライセンスは OSS の開発にのみ使って良いライセンスのため、商用アプリケーションをしたい場合は別途ライセンス必要の支払いが必要になる。
https://www.jetbrains.com/shop/eform/opensource?product=ALL
ここに自分のつくっている OSS を入れていくだけ。
私はたいした OSS はつくってないけど、 starhoshi/pi-chan2 という esa.io の iOS クライアントアプリで申請したら通った。
入力フォームは特に考えるそれっぽくいい感じに埋めていっただけ。
star 2 しかないけど通ったので、興味ある人はやってみると良いと思う。
Google がやってるやつ。 Push 受けたり、クラッシュレポート取ったり、 Analytics 取れたりしてて、モバイルでは Google Analytics より Firebase Analytics 使ったほうが良い空気を Google から感じる。
また、Fabric + Crashlytics も買収して、もうモバイルアプリはこれに依存していく以外の選択肢が見つからない。
全面的に信頼するのはまだ早いので、 GA と併用しつつ Firebase でもデータをためてどう使っていくか検討していく。
デフォルト設定でここまで取れてすごい。無料である。
半年前は取得できなかった画面遷移が、現在は取れるらしい。と言うか、自動で取ってくれているらしい、すごい!
と思いきや、取得はされているようだが画面上では見れないらしい… 課金すれば見えるのかも?
画面遷移を見たい場合はとりあえず Google Analytics 使わないとダメ。
The screen parameters will accompany the events reported to the server but they currently are not displayed on Developer Console yet (at least in my developer console).
swift - Screen tracking data on Firebase Analytics - Stack Overflow
FIRAnalytics.logEventWithName(kFIREventSelectContent, parameters: [ kFIRParameterContentType:"cont", kFIRParameterItemID:"1" ])
イベント名とそれに対応するパラメータを送信できる。
デフォルトで用意されているイベントが20くらいあって、基本的にデフォルトイベントを使用した方が良さそう。
(デフォルトだと分析とか多分楽になるのだろう)
kFIREventSelectContent
がデフォルトイベント名。ここを login_button_tapped
みたいなカスタムイベントにするのも可能。
パラメータにもデフォルトのが用意されている。上記でいうと kFIRParameterContentType
が該当する。
カスタムパラメータも使える。ただし、カスタムパラメータを使う場合は Analytics レポートに乗ってこないらしい。
カスタム パラメータ: カスタム パラメータは Analytics レポートには記載されませんが、ユーザーリストの定義でフィルタとして使用でき、あらゆるレポートに適用できます。アプリが BigQuery プロジェクトとリンクされている場合、カスタム パラメータは BigQuery にエクスポートされるデータに含まれます。
イベントのログを記録する | Firebase
カスタムイベント + デフォルトパラメータだとどうなるのか?というのは不明。やってみるしかない。
ここら辺は BigQuery で分析することを前提とした空気を感じるので、とりあえずデータを蓄積してどうするかを考えていきたい。
入れておくだけでかなりいい感じに全体分析ができるので、絶対に入れたほうが良い。
ただし、画面遷移やイベントなどは実績のある GA で取りつつ、今後 Firebase だけで運用できないか知見をためていく!
heroku + Rails5でアプリケーションを開発し、リクエストを受ける前段に Cloudflare を利用している。
Cloudflare は DNS, Crypto で SSL, CDN の設定を利用している。
しかし、 Cloudflare を使い始めたからか IPアドレスが validation エラーになってしまった。
デバッグして中身を見ていると、IP アドレスが以下のようになっていた。(前半の IP は書き換えてる)
p ip_address = request.env['HTTP_X_FORWARDED_FOR'] || request.remote_ip # => "100.10.10.150, 103.22.200.116"
カンマの前半が実際のユーザの IP アドレスで、後半が Cloudflare の IP アドレスっぽい。
103.22.200.116 - Japan - Cloudflare - IP address location and data
ユーザの IP アドレスを取得したかったら ip_address.split(",")[0]
でいけると思うけど、だいぶ不安だ...。
tableView で この cell だけ separator 消したいよ〜
という時の方法。
ググると、全ての tableView の separator 消して追加したいやつだけ追加するコード書こうとか、全部の tableView の線を消す方法とか出るけど、特定の cell だけ separator 消す
というのをしたい。
import UIKit class HogeTableViewCell: UITableViewCell { static let height: CGFloat = 88 override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) } convenience init(_ separatorInsetLeft: CGFloat) { self.init(style: .default, reuseIdentifier: nil) separatorInset = UIEdgeInsets(top: 0, left: separatorInsetLeft, bottom: 0, right: 0) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
separatorInset の left を画面右端にすると、線が見えなくなる。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return FooterTableViewCell(view.bounds.width) }
これでいける、register nib している場合は init じゃダメだと思うので cell.separatorInset = ...
で書けばいけると思う。
RxSwift やるぞ!と思ったはいいものの、ちゃんと Observer パターンを学んだことがなかったので Swift でゼロから書いてみる。
観察者が何かを監視していて、その監視対象に更新があった際に観察者に通知が届く仕組み。
Subject
が監視対象で、 Observer
が観察者。
RxSwift でも PublishSubject とか出てくるし、 Observable とかも Subject に該当するのかな? (まだ RxSwift を理解できてない)
Observer パターン - デザインパターン入門 - IT専科 を参考に Swift にしていく。
観察者のインターフェースの protocol を作る。
id
は後述するが、 Observer を削除するときに必要になる key みたいなもの。
protocol Observer { var id: String { get } // Subject からの通知が届く func update(_ string: String) } extension Observer { func update(_ string: String) { print("\(type(of: self)) に届いた新しい値は \(string) です。") } }
observers
が mutating なため、 class を継承している。
protocol Subject: class { // 観察者リスト var observers: [Observer] { get set } // 観察者を追加する func add(_ observer: Observer) // 観察者を削除する func remove(_ observer: Observer) // 観察者に更新を通知する func notify(newString: String) } extension Subject { func add(_ observer: Observer) { observers.append(observer) } func remove(_ observer: Observer) { for (index, registerdObserver) in observers.enumerated() { // observer.id が登録済みだった場合削除する if registerdObserver.id == observer.id { self.observers.remove(at: index) } } } func notify(newString: String){ print("新しい値 \(newString) を observer に通知します。") observers.forEach { observer in observer.update(newString) } } }
Observer は id を UUID で作成して重複しないようにしている。
final class SubjectA: Subject { var observers: [Observer] = [] } final class ObserverA: Observer { var id = UUID().uuidString } final class ObserverB: Observer { var id = UUID().uuidString }
(本当はシステム的に UUID を id にするようにしたほうがよさそうだけどサボる)
observerA と observerB が subjectA を監視する。
途中で observerB が監視をやめる。
let subjectA = SubjectA() let observerA = ObserverA() let observerB = ObserverB() subjectA.add(observerA) subjectA.add(observerB) subjectA.notify(newString: "New!") // -> 新しい値 New! を observer に通知します。 // -> ObserverA に届いた新しい値は New! です。 // -> ObserverB に届いた新しい値は New! です。 subjectA.remove(observerB) subjectA.notify(newString: "New!!!!!!!!!!!!!!!") // -> 新しい値 New!!!!!!!!!!!!!!! を observer に通知します。 // -> ObserverA に届いた新しい値は New!!!!!!!!!!!!!!! です。
想定通りの動作になった。
Gist - Swift observer pattern.
実際に書いてみて、 Subject や Observer などの関係性などが理解できてよかった。
とはいえ Observer パターンはそんなに難しくないしゼロから書くほど価値はあまりないしちょっと時間の無駄だった感ある、KVO でやればこんな書かなくて良いしな!!!!!!!
何も考えずに Google Analytics と Firebase をアプリに導入しようとすると、それぞれ別で Project を作成して、GoogleService-Info.plist も 2 つ生まれてしまう。
2 つあったとしても読み込む plist はそれぞれ設定できるので問題ないといえばないが、管理などがややこしくなる。
それを 1 つで管理する方法があるので、その方が便利。
ここでは、 iOS アプリに GA と Firebase を組み込んでいきます。
iOS アプリにアナリティクスを追加する | iOS 向けアナリティクス | Google Developers
この手順の「設定ファイルを取得する」項に 設定ファイルを取得
ボタンがあるので押す。
そうすると、 「Create or choose an app」 画面が開く。
アプリ名、 bundle id など入れていく。
入れたら Choose and configure services
を押す。
Analytics を選択し、 Property を作成にして ENABLE ANALYTICS SERVICE
を押す。
Generate Configure Service は無視して画面を閉じて良い。
(Firebase に登録していない人は登録が必要。)
Firebase Console を開いて、中央右らへんにある GOOGLE プロジェクトをインポート
を押す。
追加ダイアログの選択肢で先ほど作成した sample
を選択して FIREBASE を追加
する。
追加すると Firebase Project に追加される。
左上の歯車から プロジェクトの設定
を選択し、 GoogleService-Info.plist
を DL する。
Firebase からダウンロードした設定ファイルでも、 GA の TRACKING_ID が挿入されている。🎉
...略... <key>TRACKING_ID</key> <string>UA-44007234-21</string> ...略...
あとは GA も Firebase も普通の手順でやれば OK!!!
AWS 全然使ったことなくて Lambda とか全くわからなかったけど1時間くらいで API 公開まで行けた。
このままだとやり方忘れちゃいそうなのでメモ。
まず AWS にログインして、 Lambda を選んで Get Started Now
する。
次に、 blank function を選択する。
そうすると Configure triggers という画面になるが、ここは気にせず Next
を押す。
Name に適当に名前入れる、とりあえず test
とか入れておけば良い。
Runtime は Node.js を選ぶ。
コードはこんな感じで書く。
exports.handler = function(event, context, callback) { console.log('"Hello":"World"'); const response = { statusCode: 200, headers: {}, body: JSON.stringify({ "message": "Hello World!" }) }; callback(null, response); };
API Gateway と連携させる場合、 statusCode や body などが必要。
参考: プロキシリソースのプロキシ統合を設定する - Amazon API Gateway
ロールはなければ作る。
Create new role from template(s)
を選んで、 Role name を適当に入れる。
適当だから admin
などにした。
他は無視して Next
する。
内容確認して Create function
する。
これで Lambda 関数が作られる、簡単!!!!!!!!!!
画面上の方に Test
があるので押してみる。
json の中身は変えないで Save and test
を押す。
以下の json が返って来れば成功!
{ "statusCode": 200, "headers": {}, "body": "{\"message\":\"Hello World!\"}" }
次は Rest API として叩けるように API Gateway と連携させる。
Get Started Now
を押すと サンプル API の作成
など出るが無視して OK
を押す。
画面上にある、 ◯ 新しい API
をクリック。
API 名 を適当に入れる。
適当だから api
にした。
API のエンドポイントを作る。
画面左上にある アクション
> リソースの作成
を選択。
リソース作成画面になるので、リソース名を test
とかで リソースの作成
を押す。
そうするとリソースが作られる。
画面には リソースにはメソッドは定義されていません。
などと出てくる。
アクション
> メソッドの作成
を選ぶと、 メソッドを選ぶ選択肢が出てくる。
GET
を選んで ✅ を押す。
統合タイプ: Lambda 関数
、 Lambda リージョン は適当に us-east-1
を選ぶ。
そうすると Lambda 関数の入力欄が出るので、そこで先ほど作成した test
を入力し、保存
を押す。
Lambda 関数に権限を追加する
は OK
を選択。
箱が 4 つ並んでる画面が出てくるが、画面左上の テスト
を押す。
メソッドテスト画面に遷移する。
画面下にある ⚡️テスト
ボタンを押す。
下記 json が返って来れば OK!
{ "statusCode": 200, "headers": {}, "body": "{\"message\":\"Hello World!\"}" }
最後にデプロイして外から叩けるようにする。
アクション
> API のデプロイ
を選択。
[新しいステージ]
test
(なんでもよい)で入力し、 deploy
する。
画面上に URL の呼び出し: https://hoge.execute-api.us-east-1.amazonaws.com/test
みたいなの書いてるので、これに先ほど決めたリソース名の /test
を追加しアクセスしてみる。
{ statusCode: 200, headers: { }, body: "{"message":"Hello World!"}" }
と返って来れば成功!!!!!
めっちゃ簡単に Lambda 使えて最高である、これだけ手軽に使えるとスマホアプリでちょっとサーバでやりたいこととか Lambda でできて良い。
しかも一ヶ月 1,000,000 件リクエストまで無料である、強い。
target = _blank かつ POST リクエストの際に画面が表示されないバグを出してしまったので気をつけないといけない。
WKWebViewでtarget="_blank"なリンクが開かない時の対処法 - Qiita のコメント欄で注意喚起がされている。
target="_blank" を強引にメインのwebViewにロードさせる場合は注意が必要です。
例えばイベントサイトのATNDで参加登録をした時、
メインフレームで参加登録リクエストが投げられ、別ウィンドウでTwitterへの投稿画面が開かれようとします。
このとき、別ウィンドウで開かれようとしているTwitterへの投稿画面を、
参加登録リクエストを送信するはずだったwebViewに読み込ませてしまうと、
肝心の参加登録処理が実行されないままになってしまいます。
WKWebView で target = _blank を開くことができないので、下記のように自力で load request を送る必要がある。
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { guard navigationAction.targetFrame else { webView.load(URLRequest(url: url)) decisionHandler(.cancel) return } decisionHandler(.allow) }
webView.load(URLRequest(url: url))
では POST のパラメータが消えてしまう。
実際に iOS Simulator + Safari Debug で確認した。
これをアプリ側でどうにかするためには、
つらい。
参考: WKWebViewでNSURLRequestをPOSTするとヘッダーが消える問題(解決) « TORQUES LABS
という対策をとる必要がある。
アプリで WebView を使うのやめようぜ!!!!!!!!!!!!!!!!!!!