- 投稿日:2019-04-15T22:50:40+09:00
【iOS】アプリ申請/プロビジョニングプロファイル周りの用語解説
いろんなサイトを見る時に、単語の意味がわからなくて困ったのでまとめました。
本当に初心者の人は、太字の解説だけでも知っていれば、役に立つと思います。
もっと詳しいことを知りたい方は、下にリンクを貼っておきますので、参考にしていただければ。注意
- iOS開発経験の浅い人向けです
- 手順については解説していません
- 参考サイトからの引用多めです(リンク切れが発生した時のことを考えて書き写しています)
- 情報が古い/間違っている場合、コメントをお願いします
プロビジョニングプロファイル
「どこどこの誰々さん」は「どのアプリケーション」を作っていて、「どのデバイス」を使っているかという情報が書いてあるファイル。
開発中(やAdHoc用)アプリを、iPhone/iPadの実機にインストールするために必要。
アプリケーションID、デバイス識別子、正規開発者証明書の関係をまとめたファイル。Profile UUID
プロビジョニングプロファイル固有の識別子。作成時に自動的に生成される。
UUID = Universally UniqueIDentifier (全世界で2つ以上のアイテムが同じ値を持つことがない一意な識別子のこと)正規開発者証明書(.p12ファイル)
キーチェーンに保存されているやつ。
Certificatesから出力できるファイル。暗号化された秘密鍵と証明書(≒公開鍵)のペアなので、扱いには気をつける必要がある。
「どこどこの誰々さん」の部分。
以下の種類がある。
- iOS Development:開発用
- iOS Distribution (AppStore):App Store配布用
- iOS Distribution (Ad Hoc):AdHoc配布用
アプリケーションID(App ID)
アプリケーションを識別するID。
「どのアプリケーション」部分。App ID Prefix
デフォルトではTeam ID となる
App ID Suffix
以下の二種類がある。
- Explicit App ID:それぞれのアプリ固有。Bundle IDと同じ。配布用はこっち。
- Wildcard App ID:複数のアプリケーションに使用できる
Team
プロビジョニングプロファイルを作成したAppIDのPrefix。
Appleから自動で採番される、一意のID。Bundle Identifier(Bundle ID)
App IDからPrefixを除いたもの。
App IDが xxxxx.jp.domainname.appnameの場合はBundle Identifierはjp.domainname.appnameデバイス識別子(UDID)
開発中(やAdHoc用)の、アプリを動かしたいデバイスの識別子。
App Store申請用には不要。
「どのデバイス」部分。CSRファイル(証明書署名要求)
証明書の申請時に認証局に提出するファイル。 CertSigningRequestファイル。
「自分はどこどこの誰々さんなんです」ってAppleに提出するファイル。
「公開鍵」とその所有者情報、及び申請者が対応する秘密鍵を持っていることを示すために申請者の署名が記載されている。
このファイルに含まれる情報を元に証明書が発行される。CERファイル(Certificateファイル/証明書ファイル)
公開鍵が誰の公開鍵であるかを証明している もの。暗号化された証明書。
「どこどこの誰々さんである」ってことを証明するもの。身分証明書。DevelopmentとProductionに分かれており、以下のような種類がある。
- Development
- 『iOS AppDevelopment』:iOSアプリ開発用
- 『Apple Push Notificationservice SSL (Sandbox)』:テストでプッシュ通知を行う際に必要
- Production
- 『App Store and Ad Hoc』:アプリの『App Store』への提出や、AdHoc配布用
- 『Apple Push Notificationservice SSL (Production)』:製品版でプッシュ通知を行う際に必要
CodeSigning
コードサイニング証明書は、ソフトウェア(プログラム)にデジタル署名を行う電子署名用の証明書。
ソフトウェアの配布元を認証し、なりすましや内容の改ざんなどがされていないことを保証する。
「このアプリは、どこどこの誰々さんが作ったもの」ってことを証明するもの。
Code Signing ErrorなどのエラーはProvisioningProfile(Certificates)に登録されていないアカウントでアプリをビルドしようとすることが原因。アプリの配布の種類
評価用配布(AdHoc)
AppStoreを介さない、最大100台のデバイスまでの限定配布用。
インストールを許可するデバイスのUDIDをProvisioning Profileに登録する必要がある。
デバイス追加の都度、UDID登録、Provisioning Profile更新、リビルド等の作業が必要。
Provisioning Profileの有効期限(1年)が切れるとアプリが起動できなくなる。
アプリの署名に使った証明書が無効になるとアプリが起動できなくなる。
アプリ配布は開発用端末にデバイスを1台ずつUSB接続して行う。組織内配布(In-House)
社内向けのAppを社員に配布する用。
AppStoreを介さずに、デバイス無制限でアプリ配布可能。
AdHocで必要となる煩雑な作業(UDID管理等)が一切不要。
Provisioning Profileの有効期限(1年)が切れるとアプリが起動できなくなる。
アプリの署名に使った証明書が無効になるとアプリが起動できなくなる。
意図しない関係者(ライセンス契約企業ではない人)でもアプリを実行出来てしまうリスクはあり、In-House版アプリが関係者以外にインストールされないよう厳重管理が必要。
In-House版アプリの利用者はADEP(Apple Developer Enterprise Program)契約主体法人の従業員に限る。アプリを配布するのに必要なライセンスの種類
Apple Developer Program
個人や法人がアプリをリリースするために必要なライセンス。
1年毎に更新が必要で、登録料は1回あたり11,800円(2019/01/22現在)。
AppStoreで公開する場合、こっち。Apple Developer Enterprise
台数無制限で組織内にアプリを配布することが可能なライセンス。
こちらも1年毎に更新が必要で、登録料は1回あたり37,800円(2019/01/22現在)。法人に限る。
AppStoreに公開せず、社内配布などの場合、こっち。その他
知らなくても良いが、知っていると良いかもしれないもの
ipaファイル
コンパイル済みのアプリケーションの情報が入ったzipファイル。
プロビジョニングプロファイルの情報+アプリの情報(名前、バージョン、ビルド番号、バンドルID、対象デバイス)が記録されている。
ipaファイルを直接iPhoneにインストールすることができる。参考サイト
- 投稿日:2019-04-15T21:53:25+09:00
URLResposne.allHeaderFieldsでレスポンスヘッダーの値を取得しようとしたらつまずいた
環境
xcode10.1
iOS12.1
Swift4.2まえがき
商品の検索結果をTableViewで表示する際にページングを行いたく、レスポンスヘッダーに含まれる
X-Total-Pages
の値を取得していろいろ使おうとしていた。
https://developer.apple.com/documentation/foundation/httpurlresponse/1417930-allheaderfields
でレスポンスヘッダーが取得できるらしい。api.swift(APIKit使用)func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response { // 検索結果がない場合は0が入るため、forced unwrappingにしていた let totalPages = urlResponse.allHeaderFields["X-Total-Pages"] as! String }といった実装をしていて、しばらく問題がなかったが、ある日リリースビルドしたアプリをTestFlight上で確認したところ、検索を行った際にクラッシュしていて、原因を調べてみたらこの箇所が
nil
になっていた。レスポンスを確認してみても0またはそれ以上の値が入っているし、サーバー担当に確認してみても、ここが空になることはないという。
原因1: サーバーのレスポンスヘッダーに含まれているキーが小文字になっていた
リリースビルドの方
x-total-pages 64
開発の方
X-Total-Pages 64
そのため
api.swift(APIKit使用)func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response { // "X-Total-Pages"というキーがないためリリースビルドではnilになっていた let totalPages = urlResponse.allHeaderFields["X-Total-Pages"] as! String }自分が調べたところによると、ネットワークによっては小文字を返す場合がある(らしい)、サーバーのバージョン違い、アプリのバージョン違いなど考えられるそうです。
調べた分だとバージョン違いに関してはなさそうでした。
なぜ小文字になったのか、原因が特定できていないため、ご存知の方がいたら教えていただけるとありがたいです。原因2: Swiftのバグ (仕様?)
大文字小文字の違いで値が取得できずnilになったとは言いましたが、
前提として、HTTPHeaderは大文字小文字を区別しないはずです。これはドキュメントの
https://developer.apple.com/documentation/foundation/httpurlresponse/1417930-allheaderfields
にも書かれています。(google翻訳したやつ)この辞書のキーは、サーバーから受け取ったヘッダーフィールド名です。一般的に使用されているHTTPヘッダーフィールドのリストについては、RFC 2616を参照してください。
HTTPヘッダーは大文字と小文字を区別しません。コードを単純化するために、特定のヘッダーフィールド名は標準形式に正規化されています。たとえば、サーバーがcontent-lengthヘッダーを送信すると、自動的にに調整されますContent-Length。とドキュメントにも書かれていますが、実際allheaderfieldsは大文字小文字を区別してしまっています。
これはSwiftのバグ(仕様?)で、ドキュメントも更新されずそのままのようです。
https://bugs.swift.org/browse/SR-24292016年ごろからあるらしいですね。
対策
URLResponseにすべて小文字として取得できるものをはやしました。
// https://stackoverflow.com/questions/40152483/httpurlresponse-allheaderfields-swift-3-capitalisation/40152676#40152676 extension HTTPURLResponse { func find(header: String) -> String? { let keyValues = allHeaderFields.map { (String(describing: $0.key).lowercased(), String(describing: $0.value)) } if let headerValue = keyValues.filter({ $0.0 == header.lowercased() }).first { return headerValue.1 } return nil } } // usage 存在しない場合nilが入るのでよしなに処理する let total = urlResponse.find(header: "X-Total-Pages")という暫定的な対策をとりました。
間違っていたり、原因が違っていた場合は教えてもらえるとありがたいです。
- 投稿日:2019-04-15T16:30:03+09:00
Debugの.ipaを作成する方法
Xcodeで.ipaを作成するとき、初期設定だとRelease形式になっています。
Debug形式でExportする方法を記載します。開発環境
Mac: 10.13.6 High Sierra
Xcode: 10.11. Archive時のBuild Configuration変更
これが今回一番大事な作業。
Command + Shift + ,
などでEdit Schemeを開きます。
ArchiveのBuild ConfigurationをReleaseからDebugに変更します。2. flagの設定
swift flagで環境を切り替えている人は、変更をしておきましょう。
今回作るのはDebug
が対象となります。3. Archive実行
メニューバーのProductにある
Archive
を選択、実行します。4. Distribute App
Archiveが成功したら、Organizerが立ち上がります。
※余談ですが、Organizerってなんか前とUI変わりましたね。
Distribute App
を選択します。Developmentを選択してNext。
App Thinningはスルーで大丈夫です、NoneのままNext。
signingをよしなに選択します。
この後結構時間かかります、おそらく。
ロードが完了したら、Exportしましょう。5. Exportしたファイルを開く
Exportしたファイルを任意のところに置いて、開きます。
xxx.ipa
ファイルが作成できました。最後に
Xcode10.2って、mojaveじゃないと使えなくてつらい。
- 投稿日:2019-04-15T14:58:54+09:00
Flutterウィークリー #54
Flutterウィークリーとは?
FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/この記事は#54の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-54※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。
読み物&チュートリアル
2019年のFlutterの最初のユーザー調査からの洞察
https://medium.com/flutter-io/insights-from-flutters-first-user-survey-of-2019-3659b02303a5
Flutter UXリサーチチームによるQ1ユーザー調査に関するFlutter使用に関する新しいレポート。なぜそしてどのように私はFlutterを学んでいますか?
https://medium.com/flutter-community/why-and-how-am-i-learning-flutter-2652c15c8113
Wilton RibeiroはFlutterに関する彼の学びの道と彼がそれを学び始めた理由を共有します。Flutterコーディングインタビューをクラックする:Call Centerを設計する
Dmytro Gladkyiは、 Flutterを使用して典型的なコールセンターのインタビューテストを解決します。WidgetGigs - Flutterジョブボード
Flutter関連の仕事のための新しい求人掲示板。履歴書をアップグレードしましょう。Reduxを使ってFlutterでアニメーションを作成する方法
https://medium.com/flutter-community/how-to-create-animations-in-flutter-with-redux-e04ec70afbc9
Paulina Szklarskaが、アプリでReduxを使用しながらアニメーションを処理する方法を説明します。Flutter :ゴールデンテスト - ウィジェットとスナップショットの比較
ゴールデンテストは、 Flutterテストシステムではあまり知られていない機能の1つです。カタリーナシェレメットはそれらをマスターする方法を紹介します。Flutter不変ビジネスロジック
David Leybovichが、 Reduxビジネスロジックにおける不変性について説明します。Flutter :クイズゲームの作り方
https://medium.com/flutter-community/flutter-how-to-build-a-quiz-game-596d0f369575
Flutterクイズゲームを作成するためのFrancesco Mineoによる完全なチュートリアル。FlutterアニメーションI:背景色の変化
https://medium.com/flutter-community/flutter-animation-i-background-color-transition-39dcbada7335
Divyanshu Bhargavaが、トゥイーンアニメーションを使用してアプリの背景色を変更する方法を説明します。Flutterエラーメッセージが改善されました
https://medium.com/@eibaan_54644/better-errors-messages-for-flutter-a21c53982a99
Amigaシステムを覚えていますか? Stefan Matthias AustがFlutterの古い "達人瞑想"エラーメッセージを再現します。Flutter UIをフェードインする
https://medium.com/@felixblaschke/fade-in-your-uis-in-flutter-c81b1c345f70
Felix Blaschkeは、自分のライブラリsimple_animationsを使用して画面内の要素をフェードインさせます。Flutterレイアウトの基本
https://medium.com/@seenickcode/the-basics-of-flutter-layout-88339a4d2cd2
基本に立ち返って。 Nick ManningがFlutterレイアウトを紹介します。InheritedWidgetを使用したFlutterでの「依存性注入」
FlutterよるFlutter依存関係を注入するための賢い方法Flutter派手な背景アニメーション
https://medium.com/@felixblaschke/fancy-background-animations-in-flutter-4163d50f5c37
Flutter :ネットワーク要求(HTTP)
https://medium.com/@lawrey/flutter-network-request-http-6ae5667fff73
Lawrence TanがFlutter httpリクエストを行うための完全な紹介をします。Flutterビデオプレーヤー - Chewie Tutorial
https://resocoder.com/2019/04/13/flutter-video-player-chewie-tutorial/
より良いビデオ処理のためのChewieライブラリでビデオプレーヤーを作成することに関するチュートリアル。Flutter - MobXによる状態管理
https://diveintoflutter.blogspot.com/2019/04/flutter-state-management-with-mobx.html
ビデオ&メディア
6分でFlutterナビゲーション - ナビゲータのみを使用
https://www.youtube.com/watch?v=DlArCl8jvlo&feature=youtu.be
この短いフラッターチュートリアルでは、ナビゲーションを含む現実世界のシナリオについて説明します。Flutterチュートリアル-とFirestore Flutter
https://www.youtube.com/watch?v=R12ks4yDpMM
FlutterアプリケーションにFirebaseを統合する方法についてのビデオ。Flutterテキスト、フォントおよびThemeData
https://www.youtube.com/watch?v=ePLhVrT4au0
TextTheme、カスタムフォント、およびTextStyleを使った基本的なマテリアルデザインテーマを使用してテキストをスタイルする方法を学びます。Flutter :sqlite(sqflite)| CRUD操作
https://www.youtube.com/watch?v=9D1VX6uGylU&feature=youtu.be
Flutterデータベースを処理するためのsqfliteライブラリの使用法の紹介。FlutterとGoogle Mapsを使ってモバイルアプリを構築する(Cloud Next '19)
https://www.youtube.com/watch?v=RpQLFAFqMlw
Brett MorganとMatthew Sullivanによる、Cloud Next '19イベントのFlutter Mapsの使用に関する講演。フラッターアニメーションを使用してボタンを上下にアニメートする
https://www.youtube.com/watch?v=d-yx0F2FoaA&feature=youtu.be
このチュートリアルでは、クリックしたときにボタンを上下に移動させるためのアニメーションの作成方法を説明します。ライブラリ&コード
flutter_movie_ui
https://github.com/sergiandreplace/flutter_movie_ui
先週の土曜日、私は上にあったFlutterでUIの作成について話チューリッヒミートFlutter 。ここにはソースコード(便利なステップバイステップのコミット)とプレゼンテーションへのリンクがあります。
ジデグル/素材について
https://github.com/JideGuru/material-about
モバイルアプリで使用する画面について。
olexale / arkit_flutter_plugin
https://github.com/olexale/arkit_flutter_plugin
ARKit Flutterプラグイン
luigi-rosso / sequenced_loader
https://github.com/luigi-rosso/sequenced_loader
Flareコントローラを使用してFlutterローディングアニメーションを駆動する方法の例。
nonybrighto / flutter_firebase_tic_tac_toe
https://github.com/nonybrighto/flutter_firebase_tic_tac_toe
バックエンドとしてflutterとfirebaseを使って作られたシンプルなマルチレイヤ三目並べアプリケーション
- 投稿日:2019-04-15T12:30:46+09:00
Promisesを使って、Swiftの非同期処理をスマートにする
Promisesの紹介
実際に使ってみて、評価した結果
あるモバイルアプリ開発プロジェクトで実際にこのPromisesを導入してみたところ、なかなか良好な使い勝手とパフォーマンスでした。今後は有力な選択肢になるかと思い、その基本機能について原典を元に参考資料としてまとめておくことにしました。
そもそもPromise(プロミス)とは
Promise(プロミス)とは、並列プログラミング言語における同期処理実行を構成するための概念の一つです。
一般的には、非同期タスクのイベント結果を返し、もしそのタスクが失敗した時はエラー原因を返すといった機構を表現するものです。類似した概念として、Futuresといったものもあります。
先行事例としては、既にJavascriptなどで実装されている例が多いと思います。Javascriptの世界においては、ECMAScript 2015(ES6)でpromise機能が導入されました。Promisesフレームワーク
Promisesは、SwiftとObjective-Cで、同期処理構造を実現するモダンなフレームワークです。
本稿では、Swiftに絞って、基本的な使い方を確認していきます。特徴
ざっくり言うと、RxSwiftなどの人気フレームワークに比較して、容量や処理性能が優れているのだそうです。ベンチマークを計測し、2倍以上バイナリサイズが小さい、かつ直列キュー、並列キュー上の処理性能が優れているなど、実測データが紹介されています。例えば、列キューに連結された処理ブロックに到達する平均時間は2.5倍程速いなど、色々良いと言われています。その他、注目すべき特徴は下記の通りです。
- シンプル: 既存コード上にも展開しやすい、直感的なAPIを持っている
- 互換性: Objective-CとSwiftの両方をサポートしている
- 軽量: GCDやコールバックとほぼ同等レベルのパフォーマンスを達成する最小のオーバーヘッド
- 柔軟: どのスレッドやキュー上でも発動するオブザーバー処理ブロックを使用できる
- 安全: 全ての処理ブロックは、GCDによって管理される
- テスト済み: 100%のコードカバレッジでテストされている
導入
今回はCocoaPodsでの導入方法を紹介します。
CocoaPodsで導入
podがインストール済み、Xcodeプロジェクト作成済みであることを前提として、Podfileに下記を追記します。
# targetは環境に応じて変更 target 'PromisesExample' do use_frameworks! pod 'PromisesSwift', '~> 1.2.7' endプロジェクトディレクトリで
pod install
を実行し、open PromisesExample.xcworkspace
でXcodeを起動します。Swiftコード上では、モジュールのインポートを行うと使えます。
一度ビルドを通してから追記するのが良いかもしれません。import Promisesその他の方法
Bazel, Swift Package Manager, Carthageで行う方法もありますが、これは割愛します。
基本的な使い方
プロミスの作成
非同期処理をするには、所定の作業ブロックを指定します。そしてfulfill()をパラメータ付きで発動し、成功処理、reject()でエラーオブジェクトを引数にとって失敗処理を行います。successはBool型のフラグ、someErrorは何らかのErrorオブジェクトとして次のような構文で記述します。
非同期処理の基本
Swift:
// 保留状態の処理を変数に格納 let promise = Promise<String>(on: .main) { fulfill, reject in // 指定のディスパッチキュー上で非同期処理される if success { // 成功処理 fulfill("Hello world.") } else { // エラー処理 reject(someError) } }非同期処理の省略形
Promisesフレームワークは、デフォルトでメインディスパッチキューを使用します。なので、 下記のコードも同様の処理となります。
Swift:
// 保留状態の処理を変数に格納 let promise = Promise<String> { fulfill, reject in // デフォルトキュー上で非同期処理される if success { // 成功した場合 fulfill("Hello world.") } else { // 失敗、エラーになった場合 reject(someError) } }doブロック 非同期処理の最小構文
さらに、doブロックでもっとも簡潔な記述をすると次のようにできます。
Swift:
let promise = Promise { () -> String in // デフォルトキュー上で非同期処理される guard success else { throw someError } return "Hello world" }保留する
非同期処理ブロックを指定しないでプロミスオブジェクトを保留するには、pending()関数を用います。
そして後段の処理で結果を返すことができます。Swift:
let promise = Promise<String>.pending() // ... if success { promise.fulfill("Hello world") } else { promise.reject(someError) }解決済みプロミス生成
既に結果を解決済みのプロミスを生成できると便利な場合があります。その時は、初期値またはエラーをプロミスのコンストラクタに渡してください。例えば、次のようになります。
Swift:
func data(at url: URL) -> Promise<Data?> { // 妥当性チェックなどの場面で、即座にnilを返すことができる if url.absoluteString.isEmpty { return Promise(nil) } return load(url) }成功処理の監視
プロミスからの結果通知を受け取るには、
then
演算子を使います。
様々な方法で保留プロミスの結果を処理することができます。
- プロミス上で
fulfill
メソッドを実行- 非同期処理ブロック内で
fulfill
メソッドを実行then
ブロックから戻り値を返却- エラーなしで、解決済みのプロミスを返却
Then
then演算子は引数に一つの処理ブロックを取ります。処理ブロックは、前段の処理結果を引数で受け取り、必要な処理を経由して、他のプロミスオブジェクトや値、エラーを戻すようにします。
Swift:
let numberPromise = Promise(42) // 別のプロミスを返す let chainedStringPromise = numberPromise.then { number in return self.string(from: number) } // 値を返す let chainedStringPromise = numberPromise.then { number in return String(number) } // エラーを返す let chainedStringPromise = numberPromise.then { number in throw NSError(domain: "", code: 0, userInfo: nil) } // Voidを返す let chainedStringPromise = numberPromise.then { number in print(number) // Implicit 'return number' here. }注意: chainedStringPromiseは、Voidを返す一つの例です。
thenの処理ブロックをメインスレッドでなく、別のキューで実行させたい場合は、次のように記述します。
numberPromise.then(on: backgroundQueue) { number in return String(number) }Then のパイプライン
そして、これが最も重要な機能です。非同期処理のプロミスをパイプラインでつないで、まとめて同期処理を行うところです。次のようなサンプルで実行します。
Swift:
// 処理1、文字列を返す非同期Promise func work1(_ string: String) -> Promise<String> { return Promise { return string } } // 処理2、数値を返す非同期Promise func work2(_ string: String) -> Promise<Int> { return Promise { return Int(string) ?? 0 } } // 処理3、数値を返す関数 func work3(_ number: Int) -> Int { return number * number } // 処理1が実行されたら、 work1("10").then { string in // 処理2を実行し、 return work2(string) }.then { number in // 処理3を実行して、 return work3(number) }.then { number in // 結果を出力して終了する print(number) // 100 } // Swiftでは上記の例をもっとシンプルに記述することができます。 work1("10").then(work2).then(work3).then { number in print(number) // 100 }失敗処理の監視
プロミスからのエラー、失敗通知を受け取るには、
catch
演算子を使います。
様々な方法でプロミスの失敗を処理することができます。
- プロミス上で
reject
メソッドを実行- 非同期処理ブロック(asyncやdoブロック)内で
reject
メソッドを実行then
ブロックからエラーを返却またはthrow- エラーありで、解決済みのプロミスを返却
Catch
catch演算子は一つの処理ブロックを取ります。処理ブロックは、プロミスの処理で失敗した時のエラーを受け取ります。
Swift:
number(from: "abc").catch { error in print("Cannot convert string to number: \(error)") }Catch のパイプライン
従来のコールバックのネストは、コードが複雑化してしまうなど、開発が困難になることもあります。予期せぬバグの温床となったり、メンテが大変になるなど、問題化しやすい方法と言えます。
本Promisesにおいては、エラーが発生した場合、自動的にパイプラインに伝播され、残りの処理ブロックは無視されて、エラーが通知される仕組みを持っています。catch
演算子は、処理連鎖のどこに配置されてもよく、柔軟にエラーハンドリングができるものです。Swift:
// エラーの定義 struct CustomError: Error {} // 処理1、文字列を返す非同期Promise func work1(_ string: String) -> Promise<String> { return Promise { return string } } // 処理2、数値を返す非同期Promise func work2(_ string: String) -> Promise<Int> { return Promise { () -> Int in guard let number = Int(string), number > 0 else { throw CustomError() } return number } } // 処理3、数値を返す関数 func work3(_ number: Int) -> Int { return number * number } // 処理1が実行されたら、 work1("abc").then { string in // 処理2を実行し、文字列を変換しようとしてエラーが発生 return work2(string) }.then { number in // 前段の処理でエラーになるので到達しない return work3(number) }.then { number in // 前段の処理でエラーになるので到達しない print(number) }.catch { error in // エラーが捕捉され、ログ出力して終了する print("Cannot convert string to number: \(error)") }拡張機能
このフレームワークで、async, do, then, catchといった基本の演算子を使っていれば、一般的な非同期処理には十分に実装することができます。
それでもやはり、より一層ハイレベルなパターンにも対応した拡張機能が多数提供されています。
各機能を列挙すると次のようになりますが、詳しくは原典をあたりましょう。通常は、基本パターンで間に合うはずです。All, Always, Any, Await, Delay, Race, Recover, Reduce, Retry, Timeout, Validate, Wrap
アンチパターン
やってはいけない、誤った使用方法があります。ごく簡単ですが、紹介しておきましょう。
破損したサイクル
正しいパイプラインとなっていないケース。
Swift:
func asyncCall() -> Promise<Data> { let promise = doSomethingAsync() promise.then(processData) return promise }プロミスのネスト
入れ子構造にするのはやめた方が良いとのこと。
Swift:
loadSomething().then { something in self.loadAnother().then { another in self.doSomething(with: something, and: another) } }まとめ
ということで、最後にまとめておきます。
async/await方式より簡潔な記述はできないが、十分に便利なライブラリになっていると思います。いわゆるコールバック地獄から開放されより見通しやすい非同期処理が実現できるでしょう。アプリ開発の場面で、しばらく採用していきたいと思いました。参考資料
- 投稿日:2019-04-15T09:31:47+09:00
SwiftLintを導入してソースコードを整えたのでその知見を紹介します
久しぶりの投稿となります。
今月から現場が変わってメンテするプロジェクトが変わりました。
私のしばらくの業務は既存のソースコードに溜まっている技術的負債を返済するタスクになるそうです。で、実際にソースコードを確認したのですが、これを自分がメンテするにはまずは
SwiftLint
を導入しないとダメだなと思い先週からSwiftLint
の導入方法を調査していました。というのも今までは既に
SwiftLint
を導入しているプロジェクトに参画していたので自分では導入したことはなかったのですね。ということ今回はこの
SwiftLint
の調査と導入する過程で得られた知見を備忘録として残したいと思います。SwiftLintについて
ではそもそも
SwiftLint
ってなんぞやということから紹介します。簡単にいうとSwiftのソースコードで独自ルールを設定してそれに沿って既存のソースコードを自動修正していくツールです。ツールですが、実際にはライブラリですので導入にはCocoapodsを使います。
Githubページに移れば分かりますがモバイルアプリのDBでお世話になる
Realm
社が提供しているライブラリになります。このSwiftLintを既存コードに導入するとどのようなメリットがあるかというと
例えば、以下のソースコードがあったとします。
Sample.swiftimport UIKit import UIKit // 重複import です class MainViewController:UIViewController { @IBOutlet var button:UIButton! override func viewDidLoad(){ super.viewDidLoad() } func sampleMethod()->Int{ var count:Int = 1 count += 1 return count } }パッと見あんまり見たくないコードですよね。できればコードフォーマッタを使って
型
のスペース部分とかを修正したいかと思います。その気持ちがあれば十分だと思います。こういったクラスが2,3個だけならいいのですが、数百個レベルになると個人的には辛くなってくると思います。
そんなあなたにとってはSwiftLint
は大きな味方です。SwiftLintに既にあるデフォルトのルールを適用させるだけでも上記のコードを綺麗に整えることができます。
例えば、上記のコードは正確には
Sample.swiftimport UIKit class MainViewController: UIViewController { @IBOutlet var button: UIButton! override func viewDidLoad() { super.viewDidLoad() } func sampleMethod() -> Int { var count: Int = 1 count += 1 return count } }このように修正されます。
これがSwiftLintのパワーです。あなたがメンテしているコードで上記の部分が該当している場合にはチームリーダーに相談してSwiftLintを導入して見ましょう。
そんなSwiftLintの導入の仕方を紹介します。
SwiftLintの導入方法について
簡単にまとめると
- Cocoapodsに
Swiftlint
を追加するimportする- Run Scriptを追加してとあるコードを差し込む
- ルートディレクトリに
.swiftlint.yml
を追加して最初のルールを簡単に記載する- Terminalで
swiftlint autocorrect
を叩くたったこれだけの作業でSwiftLintを導入して既存コードを修正していくことができます。
ではそれぞれ説明していきます。
Cocoapodsに
SwiftLint
を追加してimportするgithubページの解説に従ってpodsを使ってライブラリをimportしましょう。
まずはMacに
Homebrew
がない場合はHomebrew
をインストールします。https://brew.sh/ のトップページにある下記のコマンドを叩いて
Homebrew
をインストールしましょう。/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Homebrew
が無事にインストールできたら次のコマンドを叩きます。brew install swiftlint
これでMacにswiftlintがインポートされます。
それが完了したら
CocoaPods
を使ってSwiftLintをインポートします。pod 'SwiftLint'そしてterminalで
pod install
を叩くことで無事にSwiftLintがプロジェクトファイルに入ります。
Run Scriptを追加してとあるコードを差し込む
次にSwiftLintを正常に動かすためにプロジェクトファイルの
Targets
のところでBuild Phases
の部分で
Run Script
を追加します。そのRun Scriptを追加してシェル上に次のコードを差し込みます。
if which "${PODS_ROOT}/SwiftLint/swiftlint" >/dev/null; then ${PODS_ROOT}/SwiftLint/swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fiこれで基本的な設定が完了しました。
[注記]
@uhooi さんのコメントを参照ですが、if which "${PODS_ROOT}/SwiftLint/swiftlint" >/dev/null; then swiftlint autocorrect --format swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fiと記載するとコミット毎に自動修正が入るようになります。
こちらの方が運用上楽かと思います。ルートディレクトリに
.swiftlint.yml
を追加して最初のルールを簡単に記載するさてこれからソースコードに適用させるlintのルールを設定していきます。
このルールの把握に時間が取られました。流石にここはQiitaの次の記事を参考にして設定していきました。
非常にお世話になりましたのでこの場をお借りして感謝のお礼を申し上げます。
そして、続きの説明に入りますが、プロジェクトファイルのルートディレクトリ(簡単に言えばPodfileなどが入ってい階層のこと)に
.swiftlint.yml
ファイルを追加します。ターミナルでルートディレクトに移動したらviコマンドでファイルを追加します。
vi .swiftlint.ymlこれでvimが使えるようになるのでルールの詳細がわからない導入期は下記のコードをコピーペーストしましょう。
swiftlint.yml# 無効にするルール disabled_rules: # xcodeでviolation warningが表示されたり違反でXcodeがビルドできないルールを差し込んでいきます。 # デフォルトルール以外でopt-inから適用させるルールの設定 opt_in_rules: # ここに採用したいルールを追加していく # # SwiftLintの適用から外すディレクトリ # (UnitTestやUITestもある場合は修正されますので入れます) excluded: # cocoapodsを使っている場合# - Pods/ # Carthageを使っている場合# - Carthage/ # 1行あたりの文字数制限を300に変更 line_length: 300 # etcこれをペースとして
:wq
で保存します。
これでSwiftLintに適用させたいルールの設定が終わります。Terminalで
swiftlint autocorrect
を叩くここまでの設定が終わればあとはTerminalで次のコマンドを叩くだけです。
swiftlint autocorrectこれでちょっと汚いと思われていたコードの箇所が適用クラスに限り自動で修正されます。
自動修正自体はものの5秒程度で完了しますので本当にすぐです。これでXcodeでソースコードを見ていきましょう。
そしてXcode上で⌘ + B
でビルドして見ましょう。大体は感動の代わりに落胆になるかと思います。
少なくとも私は落胆しました。Swiftの標準的な書き方を踏襲していたらそんな違反になることはないと思いましたが私がやって見た限りは
ビルドエラーになってしまいました。はい。デフォルトルールに違反しているコードが多く存在していました。
例えば、メソッドの命名だったり定数・変数の命名で違反しているところが多かったです。既存のコードでviolationや違反があってビルドが失敗する場合の対処方法
そこでデフォルトのSwiftLintのルールに違反していてビルドが成功しなかった場合の対処方法を紹介します。
定数・変数レベルでビルドが失敗していた場合には
.swiftlint.yml
でルールを緩和していきます。
ただ、.swiftlint.yml
ファイルは通常は非表示ファイルなのでFinder上では見つけられません。どうするかというと、
ターミナル上で次のコマンドを叩きます。
defaults write com.apple.finder AppleShowAllFiles TRUEそして、次のコマンドでFinderを一度切ります。
killall Finder再度、Finderを開くと非表示ファイルが見えるようになります。
これで
.swiftlint.yml
をVScodeとかでひらけます。そして
disabled_rules
に次のようなルールを入れてそのルールを無効
にしていきますdisabled_rules: # warningが走るため無効にする # ビルドが失敗するルールを無効にする - variable_name - file_length - function_body_length - line_length - type_body_length - variable_name_max_length - variable_name_min_length - force_cast - force_try - shorthand_operator - empty_count - cyclomatic_complexity - type_name - implicit_getterこのあたりのルールを無効化したらSwiftLintが許してくれるようになりました(笑)。
結構、規約に無視したコードでも割とビルドに成功すると思います。全体的なファイルをもう一度記載すると
swiftlint.yml# 無効にするルール disabled_rules: # warningが走るため無効にする # ビルドが失敗するルールを無効にする - variable_name - file_length - function_body_length - line_length - type_body_length - variable_name_max_length - variable_name_min_length - force_cast - force_try - shorthand_operator - empty_count - cyclomatic_complexity - type_name - implicit_getter # デフォルトルール以外でopt-inから適用させるルールの設定 opt_in_rules: # ここに採用したいルールを追加していく # # SwiftLintの適用から外すディレクトリ # (UnitTestやUITestもある場合は修正されますので入れます) excluded: # cocoapodsを使っている場合# - Pods/ # Carthageを使っている場合# - Carthage/ # 1行あたりの文字数制限を300に変更 line_length: 300 # etcこんな感じにして再度
swiftlint autocorrect
を叩いてXcode上で⌘ + B
して見てください。
これよりひどい場合にはやっぱりビルドは失敗すると思いますが私の場合はこれでなんとかビルドが成功するようになりました。あとはコミットのたびに
swiftlint autocorrect
していけばSwiftLintを適用した快適なSwiftライフが送れるようになると思います。では以上で記事を終わります。