- 投稿日:2019-09-09T20:18:18+09:00
RxSwiftでsubscribeをネストされると困る
はじめに
RxSwiftで
subscribe
をネストされると困るという話を最小限のコードで説明してみる。例えば次のようなコードがあるとする、これはやってることはシンプルだがこのような書き方を普段しているようなら、処理が複雑になっていくにしたがって読みづらいコードになる。
textField.rx.text .subscribe(onNext: { text in GitHubRepo.search(from: text).subscribe(onNext: { result in ... }) .disposed(by: disposeBag) }) .disposed(by: disposeBag)このコードをもう少しシンプルに再現しやすいサンプルでネストしないようにしてみる。
シンプルなネストの例とその解決案
Swift.Sequence
な1, 2, 3を文字列と結合する例を考えてみる。Observable.of(1, 2, 3) .subscribe(onNext: { let observable = Observable.of("\($0)A", "\($0)B", "\($0)C") observable.subscribe(onNext: { print($0) }) })出力は次の通り
1A 2A 3Aこれを解決するため、ネストさせたくなったらまずは
flatMap
系を思い出してほしい。これも出力は同じになる。Observable.of(1, 2, 3) .flatMap { Observable.just("\($0)A") } .subscribe(onNext: { print($0) })しかし、この例がそもそもシンプルすぎるからこんな簡単に置き換えられるわけだ。「1つのシーケンスをまた違う1つのシーケンスにしたいから
flatMap
で置き換えられているだけじゃないか」と思ったかもしれない。そのため例をもう少しだけ複雑にしてみる。ほんの少し複雑なネストの例とその解決案
subscribe
でログ出力を行わないといけないことを考えた例にしてみる。Observable.of(1, 2, 3) .subscribe(onNext: { Logger.log("log1:\($0)") let observable = Observable.just("\($0)A") observable.subscribe(onNext: { Logger.log("log2:\($0)") print($0) }) }) // Loggerは適当に...こんなニュアンスだとしてやってください struct Logger { static func log(_ message: String) { print(message) } }このような場合は
do
オペレータを使って副作用を分けてほしいObservable.of(1, 2, 3) .do(onNext: { Logger.log("log1:\($0)") }) .flatMap { Observable.just("\($0)A") } .do(onNext: { Logger.log("log2:\($0)") }) .subscribe(onNext: { print($0) })もちろんシーケンスを分岐して別々に処理を書いてもいい。この例ではわざわざ分岐する必要性はないが一応例を示す。
let observable = Observable.of(1, 2, 3) observable .do(onNext: { Logger.log("log1:\($0)") }) observable .flatMap { Observable.just("\($0)A") } .do(onNext: { Logger.log("log2:\($0)") }) .subscribe(onNext: { print($0) })分岐をする必要性はないが、アイデアの一つとして知っておくと良いと思う。
とにかくシーケンスの副作用の実行をまとめて一箇所の
subscribe
でやろうとするからコードが読みづらくなってしまうので、それを避けるために考えを巡らせたい。おわりに
上記の例はCold Observableを使ってとてもシンプルな例としたが、実際のアプリ開発ではこのようなシンプルなコードではないためコードの処理を追いづらい。他には
Subject
やRelay
に値をonNext
していたりしてキツイ。一つのシーケンスの処理を追っていたら複数イベントが発火し、それがそこだけの問題なのか判断ができないままコードを追いかけないといけない。そういうときにこそオペレータを駆使したりRxを調べたりして宣言的にコードを書くことを思い出してほしい。じゃないとRxを使ってるだけの読みづらいコードになってしまうわけですよね。
- 投稿日:2019-09-09T19:46:13+09:00
RxとMVVMの勉強ガイド(自分がやってきたこと)
この記事はRxをちょっと手を出したけどどう勉強すればいいかわからない人が、なんとなくコードを書けるようになるための記事です
やってるところでは当たり前になってるRxですが、まだまだ敷居が高くて手が出せないという方も多いのでは無いでしょうか?
そんな方たちに向けて、自分はこんなふうに勉強してどうにかコード書けるようになったよ!というのをまとめたいと思います
必ずしも上から順番にしっかり理解しなければいけないわけではないので、よくわからないものがあっても、とりあえず次に進んでください
記事中に簡単に説明を書きますが、だいぶ自分が直感的に理解したもので間違ってる(正しくない)可能性があります
参考記事を載せるので、正確な説明はそちらを見てくださいObservable(とその派生)
Rxを使っていると色々なプロパティと出会います。それぞれの性質や用途を把握しましょう。
Observable
すべての基本。こいつを購読(subscribe)するとonNext、onError、onCompletedの3つのイベントが流れてくる。これを使ってもいいが、用途別にいろいろな派生が存在する。他の派生が使えないときに使うイメージ。
参考:
https://qiita.com/k5n/items/17f845a75cce6b737d1e#observable
https://qiita.com/_ha1f/items/e16ddc6017c4ad807c3c#observable%E5%85%A5%E9%96%80(Publish|Behaviour)Subject
外から値を詰め込んでイベントを流すことができるObservable。元のObservableは生成時に指定した値でしかイベントが発生しない。
PublishとBehaviourの違いはキャッシュを持つかどうか。BehaviourSubjectは最後に流された値をキャッシュとして持っていて、購読した際にその値がすぐに流れてくる。PublishSubjectは購読後に発生したイベントのみ流れてくる。
任意のタイミングで変更される値をイベントとして扱いたいときに使うイメージ。ViewModelのプロパティはこれかRelayかDriverが多い気がする。
Subjectには他にも種類があるが自分は使わないので良く知らない。。。参考:
https://qiita.com/k5n/items/17f845a75cce6b737d1e#publishsubject
https://qiita.com/k5n/items/17f845a75cce6b737d1e#behaviorsubject
https://qiita.com/yyokii/items/81dc182dc4a6f1b9fd1f#subject(Publish|Behaviour)Relay
onNextのみ発生する(onErrorとonCompletedが発生しない)Subject。ある条件下で常に生き続けるストリームを作るために使える。
PublishとBehaviourの違いはSubjectと同じくキャッシュを持つかどうか。購読時の動作はSubjectと同じ。BehaviourRelayはvalueというプロパティで現在のキャッシュ値を参照できる。
ViewやViewModelのプロパティとしてよく使う。参考:
https://qiita.com/yyokii/items/81dc182dc4a6f1b9fd1f#relay
https://qiita.com/morishin/items/fbbb71d6b49f89d33b98#relay-%E3%81%A8%E3%81%AF
https://egg-is-world.com/2018/08/11/rxswift-behaviorrelay-publishrelay/
https://tech.mercari.com/entry/2017/12/04/103247Driver
メインスレッドで通知され、Hot変換され、onErrorを通知しないObservable。いきなりHotとか出てきたけど、UI部品の更新で使うとおぼえておけばOK。
ViewModelでViewの状態を表すプロパティとして使われる。それ以外にも上の性質を満たしたいときには使える。参考:
https://qiita.com/k5n/items/44ef2ab400f47fb66731
https://qiita.com/summer-hues/items/e8786cd75c292cdc3ec0
https://qiita.com/inamiy/items/d6fa90d0401fa0e83852Single
一度のみ値(onSuccess)かエラー(onError)が流れるObservable。
API通信など、1回で完結する処理に使える。参考:
https://qiita.com/yyokii/items/81dc182dc4a6f1b9fd1f#single
https://qiita.com/monoqlo/items/7bcec98432389b3b8909
https://qiita.com/shoheiyokoyama/items/40b9a2e9f8ae747c477a#singleCompletable
onCompletedかonErrorのみが流れるObservable。
返り値を使わないAPIなど、完了したことだけが知りたい場合に使える。参考:
https://qiita.com/yyokii/items/81dc182dc4a6f1b9fd1f#completable
https://qiita.com/monoqlo/items/7bcec98432389b3b8909
https://qiita.com/shoheiyokoyama/items/40b9a2e9f8ae747c477a#completableObservableの購読/購読解除
Observableを購読することで、イベントが流れるようになります。購読時にイベントが流れてきたときの処理を書きます。
基本的にはObservableに対してsubscribe()
を使います。派生によって流れてくるイベントが違うので、それに合わせてイベントが流れてきたときの処理を記述します。
bind(to:)
を使うと、あるObservableを別のObservableに接続させることができます。ViewControllerのサブビューで発生したイベントを、ViewControllerを通してViewModelに通知したい場合などに使用できます。
購読したいのがDriverの場合は、drive()
を使用します。
ずっと購読しているとメモリリークしてしまうので、必要がなくなったら購読を解除します。
それぞれのObservableに対してdispose()
を呼ぶことで購読を解除できます。
ただ、これは非常に面倒なので、DisposeBagを使用してまとめて購読を解除することもできます。画面が消えるときにその画面で購読したものを一斉に解除する、といった使い方をします。参考:
https://qiita.com/_ha1f/items/e16ddc6017c4ad807c3c#subscribe%E8%B3%BC%E8%AA%AD
https://qiita.com/_ha1f/items/e16ddc6017c4ad807c3c#dispose%E8%B3%BC%E8%AA%AD%E8%A7%A3%E9%99%A4
https://qiita.com/k5n/items/17f845a75cce6b737d1e#observableObservableの生成
様々な生成関数があります。
と言っても個人的にはcreate()
くらいしか使わないですが、知っておくといざというときにきっと役に立ちます。参考:
https://qiita.com/_ha1f/items/43b28792d27dbee7133d
https://qiita.com/moaible/items/de94c574b25ea4f0ef17イベントの加工と合成
イベントで流れていた値を加工して使いたい場合があります。例えばViewからViewModelに対してIndexPathが流れてきて、それを元にデータを取得する場合など。
その際に使用できるオペレータと呼ばれるものがたくさん用意されています。(オペレータ自体はとても広い概念で、例えばObservableの生成関数もオペレータの一種です)
加工するものとしてはfilter、map、flatMap、skipなどがあります。
また合成ではmerge、zip、concat、combineLatestなどがあります。
とにかくたくさんありますが、それぞれどのような動きをするのかを把握しましょう。参考:
https://qiita.com/_ha1f/items/db72471d0c9e82fab13d#_reference-dfb07cedc63c1f933f92
https://qiita.com/k5n/items/17f845a75cce6b737d1e#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%AE%E5%8A%A0%E5%B7%A5
https://qiita.com/k5n/items/17f845a75cce6b737d1e#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%AE%E5%90%88%E6%88%90
https://qiita.com/atizawa/items/92dfa5f49546b4711957
https://qiita.com/k5n/items/e80ab6bff4bbb170122dHotとCold
Rxをやっているとそのうちこの話題にたどり着きます。
ColdなObservableはsubscribeされて初めてイベントが流れ出し、subscribeされる度に別々のストリームが生成されます。
対してHotなObservableはsubscribeされてなくてもイベントが流れ、一つのストリームを複数のObserverがsubscribeできます。
最初はなんのこっちゃですが、ここを理解してないと効率の悪いコードを書いてしまったり、期待する結果にならなかったりするので、うまくいかないときは戻ってきて確認しましょう。参考:
https://qiita.com/morishin/items/99c0493de58079b722fe#cold--hot-
https://qiita.com/_ha1f/items/2d0fc50505ce3a1fcdab
https://qiita.com/toRisouP/items/f6088963037bfda658d3
https://qiita.com/kazu0620/items/bde4a65e82a10bd33f88サンプルを読む
ここまで来ればRxについてはなんとなく読めるはずなので、サンプルを読み漁って実際にどんな使われたかをしているか見てみましょう。
まずはRxSwiftについてるサンプルを読んでみるのが良いと思います。解説記事もあるので読みやすいかと思います。参考:
https://qiita.com/kzykbys/items/3bbb4fa24d30917afd05
https://qiita.com/yuzushioh/items/634a96c6fd69de4acdd0
https://qiita.com/fumiyasac@github/items/90d1ebaa0cd8c4558d96
https://qiita.com/kumapo/items/6207d74be19cedcf4b72
などなど便利なライブラリなど
RxViewController
UIViewControllerにrxを生やして、ライフサイクルのイベントをRx風に扱えるようにしてくれます。
NSObject-Rx
NSObjectのextensionでDisposeBagを追加することで、各クラスで毎回DisposeBagを書かなくて済むようにしてくれます。
Unio
MVVMのためのフレームワークです。MVの入出力を非常にわかりやすくしてくれますし、フレームワークを導入することで、個人による記述のブレも小さくすることができます。
おわりに
Rxの記事でコードが少しも出てこないというのはなかなか挑戦的だと思いましたが、勉強トピックのカタログというイメージで書きました
だいぶ粗い内容だし、リンクだらけですが、参考になれば幸いです
- 投稿日:2019-09-09T18:20:26+09:00
fastlaneで作成したios用証明書のパスワードを変更したい(ReactNative)
はじめに
fastlaneを使用してios用の証明書を取得する場合、privateのGitHubレポジトリを用意した上で下記のコマンドを打ち込むかと思います。
bundle exec fastlane match development もしくは bundle exec fastlane match appstoreこの際に
Passphrase for Git Repo:
という問いがあって、任意のパスワードを入力しますが、このパスワードを変更したい場合の手順を記載します。新しいレポジトリの用意
結論としては、パスワードを変更するのは不可能なので新しく証明書を取得する必要があります。そのため、証明書管理用の新しいGitHubレポジトリ(private)を用意します。
nukeコマンドの使用
現在使用している証明書を破棄するため、
アプリ名/ios
フォルダに移動し、下記コマンドを入力します。
参考:Nukebundle exec fastlane match nuke development もしくは bundle exec fastlane match nuke appstore
bundle exec fastlane match nuke appstore
で下記エラーが発生する場合、bundle exec fastlane match nuke distribution
を試してください。[!] Please run `fastlane match nuke [type], allowed values: development, distribution and enterprise. For the 'adhoc' type, please use 'distribution' instead.新規で証明書の発行
ios
ディレクトリ直下にあるMatchfile
を削除します。
そして再度下記コマンドを入力します。// 先程作成したGitHubレポジトリを登録 bundle exec fastlane match init // 証明書の再取得 bundle exec fastlane match development bundle exec fastlane match appstore参考
How to delete or reset the password of per repository for match,i forget the password #6297
- 投稿日:2019-09-09T16:46:29+09:00
iOSのメジャーアップデートに丁寧に対応する
前提知識
- Appleはここ数年毎年9月に新しいiPhoneを発表しています
- これに合わせてiOSのメジャーアップデートが実施されます
- 新しいiPhoneにはメジャーアップデートされたOSがインストールされています
- 2018年だとiPhone XSにiOS12が入っていました
- メジャーアップデートのiOSはBeta版が開発者に先行公開されます
- XcodeのBeta版の公開もほぼ同じタイミングで行われます
- Beta版でバージョンアップを重ね、リリース直前バージョンのGM(Golden Master)版がリリースの約一週間前に公開されます
- 大きな問題がない限りGM版はリリース版とほぼ同一と捉えて大丈夫です
メジャーアップデートの対応方針にいて
開発しているアプリの要求により大まかにと2つの対応方針が考えられます。
- 丁寧な対応
- Beta版のiOSを端末にインストールします
- アプリが正常に動くか動作確認します
- 想定した動作をしなかった場合にメジャーアップデート前にその不具合に対応する方法です
- 工数がかからない対応
- 新しいiOSのリリース後に動作確認し、問題があれば対応する方法です
本記事では
丁寧な方法
の手順を紹介します。丁寧な対応手順
- Beta版のiOSを端末にインストールします
- インストール前にバックアップ取得をオススメします
- その端末で動作確認を行います
- 動作確認で問題なければここで終わりです
- 想定した動作が行われなかった場合Beta版のXcodeをインストールし問題の箇所をデバッグします
- Beta版のXcodeを使用せずに今お使いのXcodeを使うことも可能です
- ただし、今お使いのXcodeではBeta版のiOSのデバッグはできず、当たりをつけて修正する必要があります。
- デバッグが必要な場合にBeta版のXcodeをお使いください
- ただし、Xcode更新にともないライブラリの更新が必要になる場合もあることにご注意ください
- 修正内容を特定し、その修正が今お使いのXcodeで行える場合は、今お使いのXcodeで修正・ビルドし、もう一度、Beta版のiOSが入った端末で動作確認を実施します
- もしBeta版のXcodeでしか修正できない場合はリリース後になるべく早く対応するしかありません
- 想定した動作が行われればiOSのメジャーアップデートを待たずに先にリリースします
- そうすることで、ユーザがiOSのメジャーアップデートをしても問題が発生しなくなります
この対応をいつすべきか
実施タイミングはアプリの要求次第になると思いますが、最低でもGM版で実施します。
ただし、GM版リリースは正式リリースの約1週間前のため致命的な問題が見つかった場合に対応の時間が限られるため注意が必要です。
- 投稿日:2019-09-09T15:14:45+09:00
WebRTCとWebViewの組み合わせについて
WebRTCとWebViewの組み合わせについて
Android/iOS共にWebViewでWebRTCの映像が受信できるか検証してみました。
WebRTCの配信基盤にはagora.ioを利用します。前提
今回の検証についてはWebView側からの映像送信は試しません。
getUserMedia()あたりが正常に動作しないとの記事をいくつか見かけている為です。
(もしかしたら動いているかもしれませんが)
agora.ioでの動作状況については以下にドキュメントがあります。
https://docs.agora.io/en/faqs/web_on_mobile環境
配信側ブラウザ:Chrome76.0.3809.132
視聴側デバイス:
・Huawei P20 lite/Android9.0
・Xperia Z5 Premium/Android5.1
・iPhone7/iOS12.4.1
SDK:agora.io WebSDK2.8.0WebRTCの実装
配信側、受信側共にagora.ioのWebSDKを利用しています。
サンプルコードはこちら
host.html:配信側
audience.html:受信側(WebView側で指定するページ)ポイントとしてはコーデックをVP8に指定しているところです。
廉価版のHUAWEI端末ではH264のデコードに対応していないもの(P20 lite等)がある為です。
尚、iOSのSafariは12.1からVP8対応しております。host.htmlclient = AgoraRTC.createClient({mode: 'live',codec:'vp8'});Androidの実装
Androidについての実装については特に難しい点はありませんでした。
Webアプリケーション側の修正をすぐに反映させる為にsetCacheMode(WebSettings.LOAD_NO_CACHE);にてキャッシュを読み込まないようにする程度かと思います。
サンプルコードはこちらMainActivity.java@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); WebView myWebView = (WebView)findViewById(R.id.webView1); myWebView.setWebViewClient(new WebViewClient()); myWebView.setWebChromeClient(new WebChromeClient()); myWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); myWebView.loadUrl("URL");//URLはaudience.htmlを指定 myWebView.getSettings().setJavaScriptEnabled(true); }結果画面
特に問題なく動作しています。
iOSの実装
サンプルコードはこちら
一番最初はWKWebViewを利用して試していました。
「iOS」「WebView」で検索するとよくヒットします。
結果としてはこのWKWebViewでは映像の視聴はできませんでした。
(やる方法はあるのかもしれませんが。。。)ViewController.swiftlet config = WKWebViewConfiguration() config.allowsInlineMediaPlayback = true let webView = WKWebView(frame: self.view.frame, configuration: config)一応、javascriptは動いているようですが、映像が映りません。またalert()等も動作せず、少しデバッグがしにくい状況でした。
そこで、SFSafariViewControllerというものを見つけたのでこれで動作確認をしてみました。ViewController.swiftoverride func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let url = URL(string:"URL") if let url = url{ let vc = SFSafariViewController(url: url) //URLはaudience.htmlを指定 present(vc, animated: true, completion: nil) } }このコードで無事に動作しました。
結果画面
「SFSafariViewControllerはiOS9.0以降使用可能でSafariの標準機能を備えたViewControllerを作成できます。」とのこと。
最後に
実際にプロダクトとして利用するには他の問題があるかもしれませんが、ひとまずWebViewでの映像再生は可能ということが分かりました。
agora.ioに関するお問い合わせはこちらから
- 投稿日:2019-09-09T12:24:35+09:00
iOS13とiPadOSに備える(フロントエンド)
もうじきiOS13、iPadOSが一般公開されますね。
みなさんは自身のサービスで動作確認済ませてますか?この記事では自分が観測した要注意事象と、それに対する対処を紹介します。
※ iOS13 beta8時点の情報です。要注意
プライベートモード判定の裏技が使えなくなった
プライベートモード判定しているサイトは要注意です!!
Apple Developer Forumsにもある内容ですが、iOS11とiOS12では下記のコードでWebSQLがエラーになる場合はプライベートモードと判定することが可能でした。
var isPrivate = false; try { window.openDatabase(null, null, null, null); } catch (_) { isPrivate = true; }しかし、Safari13ではWebSQLのサポートが終了し、プライベートモードを判定するすべがなくなりました。
ちなみに!
Safari13で上記コードがどのように動くかというと・・・
window.openDatabase
が存在しないため常にisPrivate = true
となります。
プレイベートモードユーザーのアクセスを制限しているようなサービスの場合、Safari13のユーザー全てがアクセス不可となり大惨事です。Safari13未満だけでもプライベートモード判定をしたい場合は、今のうちに次のように書き換えましょう。
var isPrivate = false; if (window.openDatabase) { try { window.openDatabase(null, null, null, null); } catch (_) { isPrivate = true; } }ちなみにChromeでも判定用の裏技がありましたが、最新バージョンのChrome76で既に潰されています。
Chromeのシークレットモード、Webサイト側での検出ができないように修正へ - Engadget 日本版iPadOSはデフォルトのUserAgentがmacOSと同じになる
UserAgentでiOS判定などを行なっているサイトは要注意です!!
iOS Safari13には「デスクトップ用WEBサイトを表示」というオプション機能が追加されていて、iPadOSはそれがデフォルトでONになっています。
その結果、SafariのUserAgentがmacOSと同じものになります。
実際のUserAgent
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15「デスクトップ用WEBサイトを表示」をOFFにすると、従来のiPadのUserAgentとなります。
Mozilla/5.0 (iPad; CPU OS 13_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/604.1ちなみにWebViewのUserAgentもmacOSに偽装されています。
iPhoneの「デスクトップ用WEBサイトを表示」は使えますが、デフォルトOFFなので意図的に設定されない限り問題ありません。
対処としては、タッチイベントに対応しているか否かで判定可能です。
PC用Safariはタッチイベント非対応ですが、iOS Safariは対応しています。
Can I use... Support tables for HTML5, CSS3, etciOS判定の実装例↓
if (Safari判定 && typeof document.ontouchstart !== 'undefined') { // Safari かつ タッチイベント対応 = iOS }IndexedDBの保存容量に上限ができた
ブラウザゲームなどIndexedDBを活用しているサービスは要注意です(・・・滅多なさそうですが)
一般的には「SafariのWebストレージ上限は50MB」という情報が出回っていると思いますが、実際のところはIndexedDBには保存上限がありませんでした。
やろうと思えばストレージを食いつぶすまでデータ保存可能でした(これはこれでヤバイ)Safari13ではついに上限が設定され、1GBあたりで容量エラーが返される事を確認しました。
1GBもあれば十分ですし、ユーザーにとっても安心ですね。ただし!リソース削除だけでは容量エラーが解消されないという怪しい挙動になっています。
リソース削除 & Safariのキャッシュ削除 & 端末再起動 まですればエラー解消できましたが・・・
ソフトウェア側の対処としては、しっかりエラーハンドリングしつつ、容量エラーになってもサービスが継続できるよう設計するしか無いですかね。おまけ
個人的にSafari13で期待している事も紹介したいと思います。
PWAのタスクキルが真っ当な動作になった
iOS12.1まで : PWAが非アクティブになるたびにセッションが切れてしまい、サービスによっては使い物にならない状態
iOS12.2から : 非アクティブでもセッションは維持できるが、タスクキルしてもセッションを切れないという予想外の挙動
iOS13から : 非アクティブでもセッションは維持できるし、タスクキルすればセッションを切れるネイティブアプリ同等の真っ当な挙動になったPWAのカメラ対応できそうな雰囲気だった
iOS13 beta1 PWAでもカメラが使えるようなりました
iOS13 beta2 重大なバグがあったとのことでリバートされました
iOS13 beta8 カメラはまだ使えません対応できそうな雰囲気だったのに!リリース版で奇跡の実装を期待!
Pointer events 対応
マウス、タッチスクリーン、ペンなどのさまざまな入力を統合するべく策定されたポインターイベントがついにSafariに実装です
さらっと確認した感じ微妙な統合具合でしたが・・・近々↓の記事を更新したいと思います。
PCとスマホの Pointer Events 挙動まとめ - Qiita
- 投稿日:2019-09-09T11:46:39+09:00
[iOS] UIWebViewがいよいよヤバいらしい
2019年9月初めぐらいから、TestFlightにビルドをアップロードした後、以下の自動メールを受信しました。
Dear Developer,
We identified one or more issues with a recent delivery for your app, "XXXXX" X.X.X (XXXX). Your delivery was successful, but you may wish to correct the following issues in your next delivery:
ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs . See https://developer.apple.com/documentation/uikit/uiwebview for more information.
After you've corrected the issues, you can use Xcode or Application Loader to upload a new binary to App Store Connect.
Best regards,
The App Store Team
Apple will stop accepting submissions of apps that use UIWebView APIs .
「AppleはUIWebView APIを使用するアプリの提出を受け付けなくなります。」いよいよ、UIWebViewを使っているとリジェクトされる日は間近なようです。
2019/9/9 追記:
「デッドラインがハッキリしない」というやりとり
https://forums.developer.apple.com/thread/122114「現時点での早急な対応は不要」とのご見解
https://qiita.com/YutoMizutani/items/2b5185c84c31edfdc2d4
- 投稿日:2019-09-09T11:46:39+09:00
[iOS] UIWebViewがいよいよヤバいらしい("ITMS-90809: Deprecated API Usage"メールが届いた件)
2019年9月初めぐらいから、TestFlightにビルドをアップロードした後、以下の自動メールを受信しました。
Dear Developer,
We identified one or more issues with a recent delivery for your app, "XXXXX" X.X.X (XXXX). Your delivery was successful, but you may wish to correct the following issues in your next delivery:
ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs . See https://developer.apple.com/documentation/uikit/uiwebview for more information.
After you've corrected the issues, you can use Xcode or Application Loader to upload a new binary to App Store Connect.
Best regards,
The App Store Team
Apple will stop accepting submissions of apps that use UIWebView APIs .
「AppleはUIWebView APIを使用するアプリの提出を受け付けなくなります。」いよいよ、UIWebViewを使っているとリジェクトされる日は間近なようです。
2019/9/9 追記:
「デッドラインがハッキリしない」というやりとり
https://forums.developer.apple.com/thread/122114「現時点での早急な対応は不要」とのご見解
https://qiita.com/YutoMizutani/items/2b5185c84c31edfdc2d42019/9/10 追記:
Ionicチームのブログ
https://ionicframework.com/blog/understanding-itms-90809-uiwebview-api-deprecation/以下、上記のブログより抜粋しGoogle翻訳したもの
- (iOSアプリをリリースできなくなりますか?)現在は、いいえ。これは単なる警告であり、エラーではないためです。警告によってApp Storeが拒否されることはありません。
- Appleに、UIWebView APIを含むアプリの提出の受け付けを停止する正式な日付をAppleに求めましたが、まだ返事はありません。 詳細がわかり次第、このブログを更新します。
- 投稿日:2019-09-09T09:44:23+09:00
【iOSDC Japan 2019 参加報告ブログ】ありがとう、ありがとう、ありがとう
iOSDC Japan 2019に参加してきました。
今年はコアスタッフ、そしてスピーカーとして参加し
色々と感じたことや思っていたことなどを書きたいと思います。コアスタッフとして
エコバッグ作成
今年はエコバックの作成を担当してました。
とは言ってもデザイナーの方はいらっしゃるので
僕は素材や色などを決めるだけなのですが。ただ、エコバッグは制作などに時間がかかるため
結構早い段階で発注をしなければいけず
結構ギリギリの時期に担当になったので
ちょっと焦りましたw今年のバッグはやはりダークモード取り入れられたことが一番印象深かったです。
バックの素材や種類を決めた時に
黒と白の2色を選べることができ
そこからデザイナーさんにデザインの候補を出してもらいました。その際に出てきたのが
黒地のバッグに青いデザイン
後々のSlackで
僕の心はずっとダークモードでした
とやや中二病的な発言をしてしまうほど気に入ってしまい
スタッフの皆様のご意見を伺いつつ
ダークモードの採用が決まりました。ここから今年のiOSDCはダークモード対応しようという機運が高まり
色々なものにダークモードが採用されるようになりました。ただ本当にダークモード気に入ってもらえるのかなという不安は
ずっと抱いていましたが
当日、ダークモードばかりなくなっていると裏で聞き「よしっ」
と内心思ってましたw
※
数に限りがあったのでもらえなかった方は本当にごめんなさい。
そして受付の方には「ダークモードがもうないぞ」という騒動を起こしてしまい本当申し訳なく思っています。個人の感想としては
皆様に気に入っていただけのではないかと自負しており
やって良かったなーという印象です。ドーナツ決死隊
朝に出すドーナツを用意する係です。
昨年は急遽ドーナツ購入が決まったのですが
好評だったので今年は事前に導入することが決まりました。「決死隊」ってなんだ?
と思われるかもしれませんが
これは時間の問題でiOSDCは9:30開場に対して
ミスドの開店時間は9:00なので
9:00に馬場のミスドに商品を取りにいき
そこから開場に向かって15分くらいで用意を完了させる。というミッションがありました。
なんとか間に合うように
取りに行く前にドーナツ以外の用意を済ませ
有志のスタッフの方と現地で合流して一緒に荷物を運んで頂き(300個/日)
なんとか間に合ったかな(?)と思っています。運用に関しては色々と課題がありましたが
当初の予想を裏切るスピードで売り切れたので
良かったなーと思っています。ただ、
朝ごはんだったので惣菜系のドーナツも入れていたのですが
その売れ行きがいまいちだったことはちょっと意外でした。何人かの方にご意見お伺いしましたが
ドーナツは甘いもの
のようですね。※
気がつかなかったという意見もあったので
それは私の配慮不足なだけかもしれません。
なんかご意見あったら欲しいです??ルーキーズLT司会
これは自分でやりたいと言ってやりました。
実は私自身が去年登壇者として参加して
それが私のiOSDCの原点だったので
個人的に思い入れのある企画でした。ルーキーズLT練習会に参加して
LTをやる仲間ができ
本番はすごい緊張しましたが
多くの方から感想を頂き
すごい嬉しかったのが今でも思い出されます。とは言っても実際大したことはしていないんですがw
当日の感想としては
これはルーキーではないw
です。
「iOSDCで初めて登壇する」なので
登壇自体は経験のある方もいらっしゃったのかもしれませんが
聞きやすく、スライドも見やすく、本当すごいなーって思いました。特に、LT経験者としてタイムマネジメントをしっかりされているのが印象深かったです。
やったことがある方ならわかると思いますが
5分で思っている以上に短いですよね。内容を考えるときにも
「何を話そうか」
よりも
「何を話さないか」
を考える時間のほうが多いくらいで
時間管理にとても苦労します。そんな中
僕の記憶では途中で終わる人もおらず
皆様きっちりまとめられていて事前準備しっかりしているなーと感じました。スピーカーとして
今回はレギュラートークに選んで頂き
30分枠で参加させて頂きました。内容に関してはスライドや補足資料をご参照頂けましたら幸いです。
スライド
https://speakerdeck.com/shiz/swift-clean-code補足資料
https://qiita.com/shiz/items/849e483338bd568cb6ab
https://qiita.com/shiz/items/5755a35887bcb7897464
https://qiita.com/shiz/items/7b3bda7c2d84c5a83c0f
https://qiita.com/shiz/items/79c7b39f94f32e548df3
https://qiita.com/shiz/items/da71d547b59c757cca94まず
お聞きに来て頂きました皆様
本当にありがとうございました!!直前にBitriseさんのランチセッションがあったので
なんか人は多いなーという印象だったのですが
最終的にこんな感じになるとは。。。本番は本当にドキドキしていましたが
やるだけのことはやったかなと思っていたので
「あとはどうにでもなれ!」ぐらいの感覚で話すことができたので
そこは良かったのかなーと思っています。※
「甘い!」とかありましたら叱ってくださいm(_ _)mただ、心残りなのがQ&Aです。
発表が終わったと同時に
やり切った感が出てしまい頭が真っ白になっていましたw質問していただいた方の質問を復唱することはしっかりやろうと思ったまでは良いのですが
そのあとは何も出てこず。。。そんな中、皆様温かい目で見守っていただいて
皆様の優しさにちょっと感動してしまいました。
(質問に答えろよ、とそのあと自分にすぐツッコミを入れましたが)とにかく
本当にありがとうございました!もしよろしければ、フィードバックで感想などを頂けますと嬉しいです!
また、何か質問などあれば、QiitaでもTwitterでもなんでも良いので聞いてください!
わかりづらかった点や間違っている箇所などのご指摘もぜひ教えてください??♂️最後に
もう完全に自己満なのですが
今回満員で部屋に入れなかった人用に「大入シール」というものがあり
自分の発表が終わったあとにこっそり拝借してこんな写真撮っちゃいましたw最後に
色々と書きましたが、
やっぱり参加して良かったという印象しかありません。こんな年齢になっても
子供の頃のように純粋に楽しめる機会はあまりないと思いますし
毎年一緒に楽しめる仲間が増えていくことはとても嬉しいです。参加していただいた皆様、本当にありがとうございました!
スタッフとして体力的にしんどい時間帯もありましたが皆様の笑顔がパワーになりました。トークを聞きに来てくださった皆様、最後まで温かく見守って頂きありがとうございました!
そして私に関わってくださった皆様、楽しい時間をありがとうございました!
見かけた際はまたお話しましょう!まだお話したことがない皆様、
勉強会などで今年の名札はずっと下げていると思うので
「なんかあのアイコン見かけたことがある」とか思ったらぜひ気軽に声をおかけください?そしてスタッフの皆様、
僕はなんかうろちょろしていただけで何の役に立っていたかはあまりわかっていませんがw
一緒にこの素敵なイベントを作っていくことができて幸せでした。
ただただ、ありがとうございました!これで僕のiOSDC Japan 2019を一旦終了とさせて頂きます!
- 投稿日:2019-09-09T03:15:44+09:00
iOSで課金のレシートをローカルで判定する方法
はじめに
最近 iOSでアプリ内課金で自動更新のサプスクリプションをの実装を行いました。その時に、Web でどうやってレシートの検証を行うかを検索しました。
• レシート検証 プログラミングガイド(PDF)
• レシート検証 プログラミングガイド(Web)
• In App Purchaseのレシートをローカルで検証できるようになった話(Qiita)
• iOSの月額課金レシート検証をサーバーサイドで行うときのTipsまとめ(Qiita)
• iOS In-App Purchase実装で必ず知っておきたい隠れた罠(Qiita)
• Apple App Store Receipt Validation with Swift and Go
• Local Receipt Validation for iOS in Swift From Start to Finish
• Validating in-app purchases in your iOS app
• レシートのverifyとSandbox
• 自動購読課金について【iOS編】いろいろありますが、ほとんどがサーバーでのレシート確認方法です。ローカルで検証する方法は本家のプログラミングガイドでもさらっと書かれているのみで、正直これだけ見て実装するのは骨が折れそうです。
今回の記事はレシートをローカルで検証する知見を紹介したいと思います。サーバーを介して検証する方法に関しての記述はしないつもりですし、うるさく言われているセキュリティの脆弱化については一部許容する方針とします。また、StoreKit の使い方やチップスについては触れません。
レシートの検証
では、レシートの検証
全体の流れ
- Appleのルート証明書(.cer)を取得する
- アプリがレシートを取得する
- アプリのレシートが Apple によって署名されているか確認する
- レシートから購入情報の列を取得する
- 購入情報の有効期限が切れていないか、キャンセルされていないか確認する
- 有効な購入情報があった場合、当該機能またはコンテンツをアンロックする
Appleのルート証明書(.cer)を取得する
以下のサイトから apple のルート証明書をダウンロードします。
https://www.apple.com/certificateauthority/と言われてもどれをダウンロードしていいかわからないので、これを使いました。
Apple Inc. Root Certificate
https://www.apple.com/appleca/AppleIncRootCertificate.cerこれをアプリに組み込みますが、リソースとして読み込む場合は、さすがにジェイルブレークなどでリソースを入れ替えた偽物をつかまされないように、ハッシュ値を確認しましょう。
$ shasum -a 256 AppleIncRootCertificate.cer b0b1730ecbc7ff4505142c49f1295e6eda6bcaed7e2c68c5be91b5a11001f024 AppleIncRootCertificate.cerこれを base64 でエンコードすると
sLFzDsvH/0UFFCxJ8Slebtpryu1+LGjFvpG1oRAB8CQ=
なので、こんな感じでAppleのルート証明書のバイナリを用意します。こんなバレバレの名前だとコードをクラックされるかもしれませんが、そこは横に置いておく事とします。var appleIncRootCertificate: Data { if let url = Bundle.main.url(forResource: "AppleIncRootCertificate", withExtension: "cer"), let data = try? Data(contentsOf: url) { // make sure the certificate is not fake one let sha256 = Data(base64Encoded: "sLFzDsvH/0UFFCxJ8Slebtpryu1+LGjFvpG1oRAB8CQ=") if data.sha256 == sha256 { return data } } fatalError("error: failed to read the certificate.") }レシートのデータは、ASN.1 のフォーマットという事ですが、多くのアプリはこれを信頼の置ける外部サーバーを経由し、 Apple に投げてそのレスポンスを処理してるようですが、ここではローカルで検証したいと思います。
ここで、アップルはコードのクラックを警戒してか、自分で
ASN1C
を使うか、openssl
を static にリンクしてなんとかしろというのですが、openssl
はともかくASN1C
から始めるにはあまりにもハードルが高いので、良さげなオープンソースのコードを探します。ASN1Decoder
https://github.com/filom/ASN1Decoder
How to use for AppStore receipt parse
と説明もありますが、レシートがAppleによって署名されているかどうかは書かれていません。import ASN1Decoder if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { do { let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) let pkcs7 = try PKCS7(data: receiptData) if let receiptInfo = pkcs7.receipt() { print(receiptInfo.originalApplicationVersion) } } catch { print(error) } }次にレシートから署名を全て取り出してその公開鍵を調べ、Apple のルート証明書の公開鍵と一つでも一致するか確認します。あとは、Bundle ID がレシート一致するかなどを調べます。
let appleX509cert = try X509Certificate(data: self.appleIncRootCertificate) guard let appleKey = appleX509cert.publicKey?.key else { fatalError("x509 public key not found.") } // check if one of these certificates is signed by apple let signedByApple: Bool = { print("certificates:") for certificate in pkcs7.certificates { if let signedKey = certificate.publicKey?.key { print(signedKey as NSData) if signedKey == appleKey { return true } } } return false }() guard signedByApple else { fatalError("the receipt is not signed by apple.") } guard let receipt = pkcs7.receipt() else { fatalError("receipt not found") } guard receipt.bundleIdentifier == Bundle.main.bundleIdentifier else { fatalError("bundle identifier do not match with certificate.") // 他必要な内容を確認そして、
ASN1Decoder
は購入情報のデコードもしてくれるので、以下のように購入情報を一つ一つ有効期限やキャンセルの有無を確認します。if let inAppPurchases = receipt.inAppPurchases { for purchase in inAppPurchases { guard let productIdentifier = purchase.productId else { continue } print("product identifier:", productIdentifier) print("purchase date:", purchase.purchaseDate ?? "n/a") print("original purchase date:", purchase.originalPurchaseDate ?? "n/a") print("cancellation date:", purchase.cancellationDate ?? "n/a") print("expires date:", purchase.expiresDate ?? "n/a") print("current date:", now) // 有効なレシートがないか確認 } }購入情報の確認方法自体は記事の対象外とさせていただきます。サーバーを介してAppleから戻ってくる JSON とは多少の違いがあるようですが、詳細は未確認です。
未解決事項
レシート検証 プログラミングガイドには、こんな記述があります。
Compute the Hash of the GUID
In macOS, use the method described in Get the GUID in macOS to fetch the computer’s GUID.
In iOS, use the value returned by the identifierForVendor property of UIDevice as the computer’s GUID.
To compute the hash, first concatenate the GUID value with the opaque value (the attribute of type 4) and the bundle identifier. Use the raw bytes from the receipt without performing any UTF-8 string interpretation or normalization. Then compute the SHA-1 hash of this concatenated series of bytes.
...snip...
5) Compute the hash of the GUID as described in Compute the Hash of the GUID.
If the result does not match the hash in the receipt, validation fails.しかし、レシート内には GUID が存在しないような気がします。
ASN1Decoder
に漏れがあるのか、こちらの見落としなのか、とにかく GUID が確認できないように思えます。環境
執筆時の環境に関しては以下の通りです。
Xcode Version 10.3 (10G8) Apple Swift version 5.0.1 (swiftlang-1001.0.82.4 clang-1001.0.46.5) Target: x86_64-apple-darwin18.7.0