20190415のiOSに関する記事は6件です。

【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にインストールすることができる。

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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-2429

2016年ごろからあるらしいですね。

対策

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") 

という暫定的な対策をとりました。

間違っていたり、原因が違っていた場合は教えてもらえるとありがたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Debugの.ipaを作成する方法

Xcodeで.ipaを作成するとき、初期設定だとRelease形式になっています。
Debug形式でExportする方法を記載します。

開発環境

Mac: 10.13.6 High Sierra
Xcode: 10.1

1. 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じゃないと使えなくてつらい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を設計する

https://medium.com/@DmytroGladkyi/cracking-the-coding-interview-in-flutter-design-the-call-center-45919a6a4fc9


Dmytro Gladkyiは、 Flutterを使用して典型的なコールセンターのインタビューテストを解決します。

WidgetGigs - Flutterジョブボード

https://widgetgigs.com/


Flutter関連の仕事のための新しい求人掲示板。履歴書をアップグレードしましょう。

Reduxを使ってFlutterでアニメーションを作成する方法

https://medium.com/flutter-community/how-to-create-animations-in-flutter-with-redux-e04ec70afbc9


Paulina Szklarskaが、アプリでReduxを使用しながらアニメーションを処理する方法を説明します。

Flutter :ゴールデンテスト - ウィジェットとスナップショットの比較

https://medium.com/flutter-community/flutter-golden-tests-compare-widgets-with-snapshots-27f83f266cea


ゴールデンテストは、 Flutterテストシステムではあまり知られていない機能の1つです。カタリーナシェレメットはそれらをマスターする方法を紹介します。

Flutter不変ビジネスロジック

https://medium.com/flutter-community/immutable-business-logic-in-flutter-5135333f39c4?sk=1d397e65802d5af810332837d0bf31ab


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での「依存性注入」

https://medium.com/flutter-community/dependency-injection-in-flutter-with-inheritedwidget-b48ca63e823


FlutterよるFlutter依存関係を注入するための賢い方法

Flutter派手な背景アニメーション

https://medium.com/@felixblaschke/fancy-background-animations-in-flutter-4163d50f5c37


Felix Blaschkeによる面白い背景アニメーション

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


MobX状態処理ライブラリの紹介

ビデオ&メディア

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を使って作られたシンプルなマルチレイヤ三目並べアプリケーション

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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方式より簡潔な記述はできないが、十分に便利なライブラリになっていると思います。いわゆるコールバック地獄から開放されより見通しやすい非同期処理が実現できるでしょう。アプリ開発の場面で、しばらく採用していきたいと思いました。

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftLintを導入してソースコードを整えたのでその知見を紹介します

久しぶりの投稿となります。
今月から現場が変わってメンテするプロジェクトが変わりました。
私のしばらくの業務は既存のソースコードに溜まっている技術的負債を返済するタスクになるそうです。

で、実際にソースコードを確認したのですが、これを自分がメンテするにはまずはSwiftLintを導入しないとダメだなと思い先週からSwiftLintの導入方法を調査していました。

というのも今までは既にSwiftLintを導入しているプロジェクトに参画していたので自分では導入したことはなかったのですね。

ということ今回はこのSwiftLintの調査と導入する過程で得られた知見を備忘録として残したいと思います。

SwiftLintについて

ではそもそもSwiftLintってなんぞやということから紹介します。

簡単にいうとSwiftのソースコードで独自ルールを設定してそれに沿って既存のソースコードを自動修正していくツールです。ツールですが、実際にはライブラリですので導入にはCocoapodsを使います。

SwiftLint(Githubリポジトリ)

Githubページに移れば分かりますがモバイルアプリのDBでお世話になるRealm社が提供しているライブラリになります。

このSwiftLintを既存コードに導入するとどのようなメリットがあるかというと

例えば、以下のソースコードがあったとします。

Sample.swift
import 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.swift
import 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の導入方法について

簡単にまとめると

  1. CocoapodsにSwiftlintを追加するimportする
  2. Run Scriptを追加してとあるコードを差し込む
  3. ルートディレクトリに.swiftlint.ymlを追加して最初のルールを簡単に記載する
  4. 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ライフが送れるようになると思います。

では以上で記事を終わります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む