20210306のiOSに関する記事は17件です。

Flutter pod installでエラー undefined method `each_child'

背景

FlutterとFirebaseを連携させてiOSシュミレーターでビルドすると、一向に成功しませんでした。。
flutter doctorで確認しても特に問題はなさそうで、、、

かなりの時間ハマってたので、ここに記録することにします:sweat_smile:

環境

MacOS

pubspec.yaml
version: 1.0.0+1

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.3
  firebase_core: ^1.0.0
  cloud_firestore: ^1.0.0

Rubyのバージョンが問題だった?

こちらをみると、どうやらRubyのバージョンが古いことによって発生するっぽい。。

「なんでRuby??」
と思うだろうが、iOS用のプロジェクトの依存関係マネージャーである「CocoaPods」は Rubyで開発されているため。

どうやってRubyのバージョンを上げるか?

ここが何気に苦労しました。。。

rbenvを使う

こちらを参考に、rbenvでRubyのバージョンを上げることにしました。

rbenv install -l
rbenv install 2.7.1
rbenv global 2.7.1

rbenvは、複数のRubyのバージョンを管理し、プロジェクトごとにRubyのバージョンを指定して使うことを可能としてくれるツールです。
https://mae.chab.in/archives/2612

rbenv install -lでインストールできるrubyのバージョンを確認します。

しかし!!

私の場合はrbenv install 2.7.1が、インストールできるrubyのバージョンに存在しなかったのです!

brew updateでもエラー発生

rbenvでrubyのバージョンを切り替える
:point_up:こちらを参考にbrew updateを実行

MacBook-Pro:~ gen$ brew update
Error: 
  homebrew-core is a shallow clone.
  homebrew-cask is a shallow clone.
To `brew update`, first run:
  git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow
  git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow
These commands may take a few minutes to run due to the large size of the repositories.
This restriction has been made on GitHub's request because updating shallow
clones is an extremely expensive operation due to the tree layout and traffic of
Homebrew/homebrew-core and Homebrew/homebrew-cask. We don't do this for you
automatically to avoid repeatedly performing an expensive unshallow operation in
CI systems (which should instead be fixed to not use shallow clones). Sorry for
the inconvenience!

謎のエラー。。。
ここで30分ぐらい道に迷ったのですが、、、しっかりとエラーメッセージを読むと答えがありました:sweat:

To `brew update`, first run:
  git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow
  git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow

git -Cから始まるコマンドをそのまま実行するだけでいけました。。

これでrbenv install -lを実行すると、2.7以上のバージョンがリストに表示されました。

rbenvでRubyのバージョンを上げる

rbenv install 2.7.1
rbenv global 2.7.1

cocoapodsを再度インストール

必要なのかはわかりませんが、、念の為再度インストール。
そしてFlutterプロジェクトのiosの階層でpod install

sudo gem install cocoapods

cd ios
pod install

Analyzing dependencies
Pre-downloading: `FirebaseFirestore` from `https://github.com/invertase/firestore-ios-sdk-frameworks.git`, tag `7.3.0`
cloud_firestore: Using Firebase SDK version '7.3.0' defined in 'firebase_core'
firebase_core: Using Firebase SDK version '7.3.0' defined in 'firebase_core'
Downloading dependencies
Installing Firebase (7.3.0)
Installing FirebaseCore (7.3.0)
Installing FirebaseCoreDiagnostics (7.4.0)
Installing FirebaseFirestore (7.3.0)
Installing Flutter (1.0.0)
Installing GoogleDataTransport (8.2.0)
Installing GoogleUtilities (7.2.0)
Installing PromisesObjC (1.2.12)
Installing cloud_firestore (1.0.0)
Installing firebase_core (1.0.0)
Installing nanopb (2.30907.0)
Generating Pods project
Integrating client project
Pod installation complete! There are 4 dependencies from the Podfile and 11 total pods installed.

[!] CocoaPods did not set the base configuration of your project because your project already has a custom config set. In order for CocoaPods integration to work at all, please either set the base configurations of the target `Runner` to `Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig` or include the `Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig` in your build configuration (`Flutter/Release.xcconfig`).

成功!!(多分)

最後に

色々と試行錯誤したので、所々記憶違いで間違えてるかもしれません。。。
発見したら指摘お願いします。:sunglasses:

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

【個人開発】リモートでビンゴゲームができるアプリを作成しました【iOS,Android】

はじめに

最近Androidアプリ開発の勉強会に参加しAndroid開発に関して学ぶ機会がありました。そこで学んだ知識をアウトプットしたいと思いリモートでビンゴができるアプリを作成しました。
(Androidアプリのみだと実用性がないためiOS版もリリースすることにしました)
この記事ではアプリの概要と使用した技術に関して記述しようと思います。

Android

iOS

サービス概要

フィーチャー画像remoteBingo.001.jpeg

オンライン・リモートで遊べるビンゴアプリです。
特徴としてはビンゴカードなしで遊ぶことができ、無料で遊べるアプリになっています。

使い方

images.002.jpeg
最初に表示される画面で主催者か参加者を選びます。

主催者編

images.003.jpeg
主催者としてルームを作成します。
ルーム名、説明(任意)、パスワードを入力します。上記の入力が完了したらルーム作成ボタンをタップしてください。
※パスワードをルームに参加する際に必要になりますので参加者全員に共有してください。

images.004.jpeg
参加者が集まったら「ビンゴを開始する」をタップしてください。
images.005.jpeg
準備完了です。抽選ボタンをタップしたらビンゴが開始されます。

参加者編

images.006.jpeg
参加したいルームをタップしてください。
images.007.jpeg
主催者が設定したパスワードを入力し参加するボタンをタップします。
これで参加完了です。
images.008.jpeg

使用した技術

サーバー側の実装はすべてFirebaseに任せました。
Cloud FirestoreやFunctionsの使用経験がほとんどなかったため苦戦しました。
「実践Firestore」という書籍で細かくFirestoreの使い方に関して記載されており非常に参考になりました。

  • Firebase

    • Authentication(匿名認証)
    • Cloud Firestore
    • Functions
    • Hosting (プライバシーポリシーをデプロイするため)
    • Crashlytics
    • Analytics
    • AdMob(インタースティシャル広告)
  • Android
    言語 Kotlin
    アーキテクチャー AAC
    ライブラリー material-dialogs,lottie等

  • iOS
    言語 Swift
    アーキテクチャー MVVM
    ライブラリー RxSwift,RxCocoa,RxDataSources,lottie-ios等

その他

プライバシーポリシー
下記サイトで自動でプライバシーポリシーのHTMLを生成しました。そのHTMLをFirebaseのhostingを用いてデプロイしました。
https://app-privacy-policy-generator.firebaseapp.com/

問合せページ
アプリを公開するには問合せ先のwebページを作成します。
1から作成するのはかなり手間がかかるので「ペライチ」というサービスを用いてお問い合わせページを作成しました。無料で使えるかつ15分ぐらいで問合せフォームが作れるので非常に便利です。
https://peraichi.com/landing_pages/view/remotebingo

おわりに

Androidアプリ開発をしてみるとiOSとの共通点が多くあり簡単なアプリを作れるレベルには成長できた気がします。(コードは汚いので時間があるタイミングで綺麗にしたい泣)
また実際にアプリを作って公開しようと思うとプライバシーポリシーの作成やストア用の画像の準備、証明書関係の整理等思ったよりやるべき事が多く苦労しました。
しかしながら自分の作りたいアプリを好きな時間で作れるのが個人開発のいい点だと思います。
今後も時間を見つけて個人開発をしていきたいです。

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

CareKitのコードから学ぶCore Dataの活用方法

はじめに

CareKitはアップルが開発しているオープンソースのフレームワークです。iOSとMacで動作します。
また、内部の永続化、検索処理にCore Dataを活用しているという事で、昔CoreDataを使いまくっていた人間としては活用方法に非常に興味があります。ただ、Core Data自体を使う事は今はあまりお勧めしません。

ソースコード

CareKitのソースコードはここから入手可能です。

理解する上で参考となる文献

CareKitで使っている各種設計パターンについてはマーチンファウラーの有名な文献が参考になります。

エンタープライズアプリケーションアーキテクチャパターン

基本的な設計思想

前述の通り、Core Dataを活用しています。ただし、利用者には隠蔽しています。理由はCore Dataを剥き出しにする設計にすると、利用者はCore Dataを学習する必要があるからです。これは他のフレームワーク、EventKit, ContactsKit, HealthKitも大体同じ考えです。

OCKStore周りのクラス構成

OCKStoreの構造.png
OCKStoreクラスは所謂リポジトリクラスです。プロトコル指向で実装しており、図以外にもプロトコルを継承しています。この辺についてはこの記事では深堀しません。
OCKStoreクラスは内部にCoreDataフレームワークのインスタンスを内部に持っています。

OCKCarePlan周りのクラス構成

CarePlanクラスの構成.png
ごちゃごちゃしていますが、利用者に見せるOCKCarePlanクラスとCoreDataとのやりとりに使うOCKCDCarePlanクラスに分かれています。
OCKStoreから検索、保存等を行う際に相互に変換します。所謂データマッパーパターンです。NSManagedObjectから派生している方のクラスは所謂DTOとして使っています。どうやって変換しているかについては現段階では深堀しません。

OCKCarePlanQuery周りのクラス構成

OCKCareQueryクラスの構成.png
OCKCarePlanQueryクラスは所謂Queryオブジェクトです。検索する際に、OCKStoreがOCKCarePlanQueryのメソッドを使ってNSPredicateやSortDescriptorクラスに変換します。このクラスがあるおかげで利用者はNSPredicateの文法やクエリーの詳細を覚えなくてすみます。

検索処理の流れ

フェッチのシーケンス図.png
検索処理の流れです。わかりやすくするためにOCKCarePlanStoreとOCKStoreと分けて書いています。OCKStoreクラスが内部でCore Dataとのやりとりに必要なデータに変換、Core Dataに渡しています。Core Dataから帰ってきたデータを利用者に公開しているOCKCarePlanオブジェクトに変換しています。意外な事にNSManagedObjectContextを複数使って処理負荷を下げる事はしていませんでした。

追加、更新、削除処理の流れ

WIP

まとめ

CareKitの永続化周りのコードを追うことによって、参考文献に載っている様々な設計パターンが活用されている事がわかりました。ここでは説明していませんが、これまでの説明以外の様々なパターンが活用されています。
なお、開発メンバー全員がCore Dataに精通している場合はこのような複雑な設計を採用する必要はないかもしれません。

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

[Flutter] Flutter 2.0 Migration方法

Migration

  • 方法
> dart migrate
  • Process
C:/Users/itsme/Documents/GitHub/flutter_migration> dart migrate
Migrating C:/Users/itsme/Documents/GitHub/flutter_migration

See https://dart.dev/go/null-safety-migration for a migration guide.

Analyzing project...
[------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\]
No analysis issues found.

Generating migration suggestions...
[-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------]

Compiling instrumentation information...
[-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------]

View the migration suggestions by visiting:

  http://127.0.0.1:59295/C:/Users/itsme/Documents/GitHub/flutter_migration?authToken=SLX1RWDeq6Y%3D

Use this interactive web view to review, improve, or apply the results.
When finished with the preview, hit ctrl-c to terminate this process.

If you make edits outside of the web view (in your IDE), use the 'Rerun from
sources' action.

migrateをすると、Null safety Previewのリンクかでます。

Proposed null safety changes

  • リンクはプロジェクトによって、違います。
http://127.0.0.1:59295/C:/Users/itsme/Documents/GitHub/flutter_migration?authToken=SLX1RWDeq6Y%3D

image.png

Apply Migration

image.png

  • マイグレーションにすると、environmentの変えます。

image.png

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

【初心者】Swift UIを勉強する その② ーーーSymbolsとList

はじめに

今回のはシンボルとリストの作成方法を学習していきます。

目次

  1. SF Symbols
  2. リスト作成
  3. まとめ
  4. 参考文献

SF Symbols

・appleさんはシンボルアプリを提供していますので、SF Symbolsをダウンロードしましょう。

截屏2021-02-23 15.19.40.png
・Swift UIファイルを新規作成し、SF Symbolsを使ってみましょう。
 SF Symbolsアプリにてシンボルの名称をImageとして作成します。
 また、デフォルトのシンボルはかなり小さいので、imageScale()を使って大きさを決めましょう。
 だたし、こうやるとシンボルのみサイズが変わるために、シンボルとセットになっているパーツを揃ってデザインを変えるのがおすすめです。

CourseRow.swift
struct CourseRow: View {
    var body: some View {
        HStack(alignment: .top) {
            Image(systemName: "paperplane.circle.fill")
                .renderingMode(.template)
                .frame(width: 48.0, height: 48.0)
                .imageScale(.medium)
                .foregroundColor(.white)
            }
        }
    }
}

・シンボルのフレームを48x48に、背景色とclipShape(Circle())で円形に切り取りで完成です。

CourseRow.swift
struct CourseRow: View {
    var body: some View {
        HStack(alignment: .top) {
            Image(systemName: "paperplane.circle.fill")
                .renderingMode(.template)
                .frame(width: 48.0, height: 48.0)
                .imageScale(.medium)
                .background()
                .clipShape(Circle())
                .foregroundColor(.white)
            VStack(alignment: .leading, spacing: 4.0) {
                Text("SwiftUI")
                    .font(.subheadline)
                    .bold()
                Text("Description")
                    .font(.subheadline)
                    .bold()
            }
            Spacer()
        }
    }

リスト作成

・新規Swift UIファイルを作成し、先ほど作ったCourseRow()を呼び出します。
List{}に詰め込めば完成しました。(UIKitのようにscrollViewがなくでもスクロールできて感動しました)
截屏2021-02-23 16.54.11.png

・Listのスタイルは何かあるのかをみたい時に、optionを押しながらlistStyleのドキュメントからチェックできます。
截屏2021-02-23 16.57.38.png

まとめ

・UIKitの半分のコードでlistを作れました。再びSwift UIの強さを感じました。

ソースコードはGithub

参考文献

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

iOSで電話番号がリンクになった時の対処法

備忘録として残します。

iOSで確認したところ、aタグを使っていないのにも関わらず電話番号がリンクとして表示されていたので調べてみました。

なぜリンクになるのか

iOSは、ユーザが電話番号をタップやクリックすると電話を掛けられるように
自動的にaタグを挿入する仕様になっているそう。

対処法

以下のコードをheadタグ内に記述します。

<meta name="format-detection" content="telephone=no">

CSSでは効かないのか?

以下の方法で試しましたが、やり方が悪いのか私の場合は効きませんでした。

a {
  text-decoration: none;
  color: #色;
}

参考:

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

【Swift】UISearchBarを利用してTableViewの表示を絞る

UISearchBarでTableViewの表示を絞る方法

スクリーンショット 2021-03-06 17.12.18.png

最初にstoryboardでUISearchBarを配置し、ViewControllerと接続します。

@IBOutlet weak var searchBar: UISearchBar!

次にViewControllerにUISearchBarDelegateプロコトルを適用させます。

class ViewController: UIViewController, UISearchBarDelegate{

最後にsearchBarの処理をコードで実装します。

// searchButtonを押した時に実行されるメソッド
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
     // tableViewに表示するデータリストをsearchBarに入力されたテキストで絞り込む
        members = members?.filter("name CONTAINS[cd] %@", searchBar.text!).sorted(byKeyPath: "name", ascending: true)
        tableView.reloadData()
    }

    // searchBarの入力が無くなったら絞り込みを解除する
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if searchBar.text?.count == 0 {
            loadMembers()
            DispatchQueue.main.async {
                searchBar.resignFirstResponder()
            }
        }
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UISearchBarを利用してTableViewの表示を絞る

UISearchBarでTableViewの表示を絞る方法

スクリーンショット 2021-03-06 17.12.18.png

最初にstoryboardでUISearchBarを配置し、ViewControllerと接続します。

@IBOutlet weak var searchBar: UISearchBar!

ViewControllerにUISearchBarDelegateプロコトルを適用させます。

class ViewController: UIViewController, UISearchBarDelegate{

  
searchBarの処理をコードで実装します。

// searchButtonを押した時に実行されるメソッド
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
     // tableViewに表示するデータリストをsearchBarに
入力されたテキストで絞り込む
        members = members?.filter("name CONTAINS[cd] %@", searchBar.text!).sorted(byKeyPath: "name", ascending: true)
        tableView.reloadData()
    }

    // searchBarの入力が無くなったら絞り込みを解除する
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if searchBar.text?.count == 0 {
            loadMembers()
            DispatchQueue.main.async {
                searchBar.resignFirstResponder()
            }
        }
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Realmでproblem 'Index is out of bounds (must be less than 1)とエラーが出たときの対処法

RealmでTableViewにデータを表示しようとした時、タイトルのエラーが起きたので対応方法を記します。
このエラーはRealmのデータ更新時、Realmに保存されているモデルの件数と表示されているTableViewの件数(TableViewの認識している件数が以前のままで)の整合性がとれなくエラーになるようです。

var participants: Results<Participant>?

// cellにデータを表示
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CELL_ID) as! AttendanceTableViewCell
    cell.delegate = self
    cell.index = indexPath
    cell.textLabel?.text = participants?[save:indexPath.row].name
 }

 // Resultsを拡張
extension Results {
subscript (save index: Int) -> Element? {
    if index < self.count {
        return self[index]
    } else {
        return nil
    }
}

}

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

Realmでproblem 'Index 1 is out of bounds (must be less than 1)とエラーが出たときの対処法

RealmでTableViewにデータを表示しようとした時、タイトルのエラーが起きたので対応方法を記します。
このエラーはRealmのデータ更新時、Realmに保存されているモデルの件数と表示されているTableViewの件数(TableViewの認識している件数が以前のままで)の整合性がとれなくエラーになるようです。

var participants: Results<Participant>?

// cellにデータを表示
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CELL_ID) as! AttendanceTableViewCell
    cell.delegate = self
    cell.index = indexPath
    cell.textLabel?.text = participants?[save:indexPath.row].name
 }

 // Resultsを拡張
extension Results {
subscript (save index: Int) -> Element? {
    if index < self.count {
        return self[index]
    } else {
        return nil
    }
}

}

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

[SwiftUI]Widgetでサイズごとに値を変える

iOSのWidgetでサイズごとに文字サイズなどを変えるためのメモ。

実装

View内でWidgetFamilyの値を取得

    @Environment(\.widgetFamily) var widgetFamily

WidgetFamilyごとにFontサイズを変える

    var font: Font {
        switch self.widgetFamily {
        case .systemSmall: return .system(size: 10)
        case .systemMedium: return .system(size: 20)
        case .systemLarge: return .system(size: 30)
        @unknown default: return .system(size: 10)
        }
    }

    var body: some View {
        Text(entry.date, style: .time)
            .font(self.font)
    }

結果

WidgetFamilyごとに文字サイズを変えることができました。

参考

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

Initialization Closureとglobal定数を組み合わせて、computedライクなglobal定数を作る

Initialization Closureとは?

Initialization closureは、以下の特徴を持ちます

  • closure内でpropertyに操作を加えられる
  • stored propertyである(値が割り当てられるタイミングはclassからinstanceが生成された時)

If a stored property’s default value requires some customization or setup, you can >use a closure or global function to provide a customized default value for that >property. Whenever a new instance of the type that the property belongs to is >initialized, the closure or function is called, and its return value is assigned >as the property’s default value.

公式ドキュメント

主にUIをコードで記述する際に用いられます。
参考記事

Globalな定数(変数)の特徴

Globalな定数(変数)は以下の特徴を持つ

  • lazyである(割り当てられるタイミングは値が呼ばれた時)

Global constants and variables are always computed lazily, in a similar manner to >Lazy Stored Properties. Unlike lazy stored properties, global constants and >variables don’t need to be marked with the lazy modifier.
Local constants and variables are never computed lazily.

それぞれの特徴を組み合わせると、computedライクなglobal定数を作ることができます。

ViewController.swift
let somePattern: FetchedValue = {
   let defalutValue = "hoge"
   return isFetchedCompleted ? fetchedValue : defaultValue
}()

これは、外部サービスを使って、複数画面でABテストを行いたい/アプリの挙動を変えたい場合に以下の性質を持つので便利です。

  • lazyなので、アプリ起動後にfetchが間に合わない場合でも、対象画面に遷移するまで時間を稼げる(=テストパターンに割り振られるユーザー数を増やせる)
  • globalなので、各画面でfetchを行わなくて良い
  • 各画面でfetchを行わないので、画面毎に挙動が異なることがない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UITableViewの高さをcell数に応じて可変にしたい

課題事例

  • 元々、画面全体がUIScrollViewになっていて、真ん中にUITableViewを差し込みたい(※新規画面なら全体をUITableViewにすれば済む話ではあります)。
  • 画面全体をスクロールさせる点に変わりはないので、UITableViewはスクロールさせない。
  • UITableViewはcell数が可変、かつcellごとの高さも違っていて、それに応じてUITableViewの高さを変えたい(そうしないと全てのcellが表示されなかったり、余白ができてしまったりするので)。

解決法

(1) 高さのconstraintを変数に格納。

private weak var tableViewHeightConstraint: NSLayoutConstraint?

(2) KVO(Key Value Observing)でUITableViewのcontentSize.heightを監視。

NSKeyValueObservationを格納するための変数を宣言し、UITableViewのcontentSize.heightをobserve。
heightが変わった時に上の制約のconstantをセットする処理を定義する。

private var observation: NSKeyValueObservation?

override func viewDidLoad() {
// 略
    observation = tableView.observe(\.contentSize, options: [.new]) { [weak self] (_, _) in
        guard let self = self else { return }
        self.tableViewHeightConstraint?.constant = self.tableView.contentSize.height
    }
}

(3) 後始末

deinit {
    observation?.invalidate()
    observation = nil
}

私は以上のような解決法で課題をクリアできましたが、もっとシンプルな解決法をご存知でしたら、ぜひ教えてください。:bow_tone1:

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

[WIP]WKHTTPCookieStoreコード解析

はじめに

中々開発者泣かせなWebKitのクッキー管理、色々はまりどころがあり、とうとうAppleもWKWebViewの移行期限を無期限に伸ばしました。と言うわけで中がどうなっているか解析していきます。

WebKitのソースコード

ソースコードはこちら

WebKitのプロセス構成

セキュリティ対策や重たいコンテンツを読み込んでもアプリが落ちないように、WebKitはここここここにある通り、UIProcess, WebProcess, NetworkProcess, StorageProcess(WorkerProcess)と4つのプロセスに分かれています。それぞれのプロセスはIPCでやりとりしています。

Cookieの取得

Cookieの取得は以下のメソッドを通じて行います。

class WKHTTPCookieStore 
func getAllCookies(_ completionHandler: @escaping ([HTTPCookie]) -> Void)

UIプロセス側のコールスタック

取得完了ハンドラーに復帰した時のUIスレッド(プロセス)のコールスタックは以下になります。イベントループ(RunLoop)から
WebKit〜IPC受信〜WebKit APIと呼び出しているのが分かります。

#1  0x000000018e6b5250 in WTF::Detail::CallableWrapper<WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>, void, WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&>::call(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&) ()
#2  0x000000018ea2b6c4 in WTF::Detail::CallableWrapper<API::HTTPCookieStore::filterAppBoundCookies(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)>&&)::$_0, void, WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&>::call(WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&) ()
#3  0x000000018ea0fa68 in WTF::Detail::CallableWrapper<WebKit::WebsiteDataStore::getAppBoundDomains(WTF::CompletionHandler<void (WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&)>&&) const::$_12, void, WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&>::call(WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&) ()
#4  0x000000018ea0c028 in WTF::CompletionHandler<void (WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&)>::operator()(WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&) ()
#5  0x000000018ea0c1a0 in WebKit::WebsiteDataStore::getAppBoundDomains(WTF::CompletionHandler<void (WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&)>&&) const ()
#6  0x000000018ea28ab0 in API::HTTPCookieStore::filterAppBoundCookies(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)>&&) ()
#7  0x000000018ea2bfc4 in WTF::Detail::CallableWrapper<API::HTTPCookieStore::cookies(WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>&&)::$_2, void, WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&>::call(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&) ()
#8  0x000000018ec0e3c8 in Messages::WebCookieManager::GetAllCookies::callReply(IPC::Decoder&, WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)>&&) ()
#9  0x000000018e92a8f4 in WTF::Detail::CallableWrapper<WebKit::AuxiliaryProcessProxy::sendMessage(std::__1::unique_ptr<IPC::Encoder, std::__1::default_delete<IPC::Encoder> >, WTF::OptionSet<IPC::SendOption>, WTF::Optional<std::__1::pair<WTF::CompletionHandler<void (IPC::Decoder*)>, unsigned long long> >&&, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::$_0, void, IPC::Decoder*>::call(IPC::Decoder*) ()
#10 0x000000018e683dd8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) ()
#11 0x000000018e6866c8 in WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_7, void>::call() ()
#12 0x000000018c3ac5e0 in WTF::RunLoop::performWork() ()
#13 0x000000018c3ad2c8 in WTF::RunLoop::performWork(void*) ()
#14 0x000000018203d76c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#15 0x000000018203d668 in __CFRunLoopDoSource0 ()
#16 0x000000018203c960 in __CFRunLoopDoSources0 ()
#17 0x0000000182036a8c in __CFRunLoopRun ()
#18 0x000000018203621c in CFRunLoopRunSpecific ()
#19 0x0000000199c02784 in GSEventRunModal ()
#20 0x0000000184a76ee8 in -[UIApplication _run] ()
#21 0x0000000184a7c75c in UIApplicationMain ()
#22 0x0000000104b308a0 in main at /app/main.m:22
#23 0x0000000181cf66b0 in start ()

Webプロセス側の解析

前述のコールスタックにある

WTF::Detail::CallableWrapper<API::HTTPCookieStore::cookies(WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>&&)::$_2, void, WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, 

より、APIHTTPCookieStore.mを見ると、

    auto* cookieManager = pool->supplement<WebKit::WebCookieManagerProxy>();
    cookieManager->getAllCookies(m_owningDataStore->sessionID(), [this, protectedThis = makeRef(*this), pool = WTFMove(pool), completionHandler = WTFMove(completionHandler)] (const Vector<WebCore::Cookie>& cookies) mutable {
        filterAppBoundCookies(cookies, WTFMove(completionHandler));
    });

WebCookieManagerProxyと言う記載があります。コールスタックにもありますね。〜Proxyと言うのはProxyパターンの事を指します。Proxyを外して
WebCookieManagerで検索すると、WebCookieManager.h、WebCookieManager.cppと言うファイルがでてきました。ここからはNetworkProcess側の解析になります。

コールスタックをさらに解析するとgetAllCookiesと言う記載があります。多分↓のメソッドを呼び出しているのでしょう。

void WebCookieManager::getAllCookies(PAL::SessionID sessionID, CompletionHandler<void(Vector<WebCore::Cookie>&&)>&& completionHandler)
{
    Vector<Cookie> cookies;
    if (auto* storageSession = m_process.storageSession(sessionID))
        cookies = storageSession->getAllCookies();
    completionHandler(WTFMove(cookies));
}

storageSession->getAllCookies()でクッキを取り出しています。と言うわけでm_process、storageSessionを見てみます。
m_processを調べるとNetworkProcessクラスのインスタンス変数です。NetworkProcessクラスのstorageSessionメソッドを見てみます。

WebCore::NetworkStorageSession* storageSession(const PAL::SessionID&) const;

NetworkStorageSessionと言うクラスを返すようになっています。クラス名、NetworkStorageSessionで定義しているメソッドを見るとCookieやキャッシュのに永続化に関するいろんな仕事をやるようです。
後、NetworkStorageSessionCocoa.mmと言うファイル名から、プラットホーム依存のクラスらしいです。
で、↑よりgetAllCookiesメソッドを調査すると

Vector<Cookie> NetworkStorageSession::getAllCookies()
{
    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
    return nsCookiesToCookieVector(nsCookieStorage().cookies);
}

↑よりさらにnsCookieStorage()を調べると

  WEBCORE_EXPORT NSHTTPCookieStorage *nsCookieStorage() const;

と言う宣言がありました。実装は見当たりませんでした。よく知っているNSHTTPCookieStorageクラスです。

まとめ

Cookieの取り出しを見ると、WebProcessにあるNSHTTPCookieStorageからCookieを取り出してUIProcessに返しているだけでした。

Cookieの設定

Cookieの設定は以下のメソッドを通じて行います。

class WKHTTPCookieStore 
func setCookie(_ cookie: HTTPCookie, 
completionHandler: (() -> Void)? = nil)

UIプロセス側のコールスタック

設定完了ハンドラーに復帰した時のUIスレッド(プロセス)のコールスタックは以下になります。

#1  0x000000018ea2c3f8 in WTF::Detail::CallableWrapper<auto API::HTTPCookieStore::setCookies(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::CompletionHandler<void ()>&&)::$_4::operator()<WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> >(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)::'lambda'(), void>::call() ()
#2  0x000000018e9821a8 in WTF::Detail::CallableWrapper<void WebKit::AuxiliaryProcessProxy::sendWithAsyncReply<Messages::WebCookieManager::SetCookie, WTF::CompletionHandler<void ()> >(Messages::WebCookieManager::SetCookie&&, WTF::CompletionHandler<void ()>&&, unsigned long long, WTF::OptionSet<IPC::SendOption>, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::'lambda'(IPC::Decoder*), void, IPC::Decoder*>::call(IPC::Decoder*) ()
#3  0x000000018e92a8f4 in WTF::Detail::CallableWrapper<WebKit::AuxiliaryProcessProxy::sendMessage(std::__1::unique_ptr<IPC::Encoder, std::__1::default_delete<IPC::Encoder> >, WTF::OptionSet<IPC::SendOption>, WTF::Optional<std::__1::pair<WTF::CompletionHandler<void (IPC::Decoder*)>, unsigned long long> >&&, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::$_0, void, IPC::Decoder*>::call(IPC::Decoder*) ()
#4  0x000000018e683dd8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) ()
#5  0x000000018e6866c8 in WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_7, void>::call() ()
#6  0x000000018c3ac5e0 in WTF::RunLoop::performWork() ()
#7  0x000000018c3ad2c8 in WTF::RunLoop::performWork(void*) ()
#8  0x000000018203d76c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#9  0x000000018203d668 in __CFRunLoopDoSource0 ()
#10 0x000000018203c960 in __CFRunLoopDoSources0 ()
#11 0x0000000182036a8c in __CFRunLoopRun ()
#12 0x000000018203621c in CFRunLoopRunSpecific ()
#13 0x0000000199c02784 in GSEventRunModal ()
#14 0x0000000184a76ee8 in -[UIApplication _run] ()
#15 0x0000000184a7c75c in UIApplicationMain ()
#16 0x0000000104b308a0 in main at /app/main.m:22
#17 0x0000000181cf66b0 in start ()

Webプロセス側の解析

WIP

Cookieの削除

Cookieの削除は以下のメソッドを通じて行います。

class WKHTTPCookieStore 
func delete(_ cookie: HTTPCookie, 
completionHandler: (() -> Void)? = nil)

UIプロセス側のコールスタック

削除完了ハンドラーに復帰した時のUIスレッド(プロセス)のコールスタックは以下になります。

#1  0x000000018ea2c638 in WTF::Detail::CallableWrapper<API::HTTPCookieStore::deleteCookie(WebCore::Cookie const&, WTF::CompletionHandler<void ()>&&)::$_6, void>::call() ()
#2  0x000000018e981ebc in WTF::Detail::CallableWrapper<void WebKit::AuxiliaryProcessProxy::sendWithAsyncReply<Messages::WebCookieManager::DeleteCookie, WebKit::WebCookieManagerProxy::deleteCookie(PAL::SessionID, WebCore::Cookie const&, WTF::CompletionHandler<void ()>&&)::$_0>(Messages::WebCookieManager::DeleteCookie&&, WebKit::WebCookieManagerProxy::deleteCookie(PAL::SessionID, WebCore::Cookie const&, WTF::CompletionHandler<void ()>&&)::$_0&&, unsigned long long, WTF::OptionSet<IPC::SendOption>, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::'lambda'(IPC::Decoder*), void, IPC::Decoder*>::call(IPC::Decoder*) ()
#3  0x000000018e92a8f4 in WTF::Detail::CallableWrapper<WebKit::AuxiliaryProcessProxy::sendMessage(std::__1::unique_ptr<IPC::Encoder, std::__1::default_delete<IPC::Encoder> >, WTF::OptionSet<IPC::SendOption>, WTF::Optional<std::__1::pair<WTF::CompletionHandler<void (IPC::Decoder*)>, unsigned long long> >&&, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::$_0, void, IPC::Decoder*>::call(IPC::Decoder*) ()
#4  0x000000018e683dd8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) ()
#5  0x000000018e6866c8 in WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_7, void>::call() ()
#6  0x000000018c3ac5e0 in WTF::RunLoop::performWork() ()
#7  0x000000018c3ad2c8 in WTF::RunLoop::performWork(void*) ()
#8  0x000000018203d76c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#9  0x000000018203d668 in __CFRunLoopDoSource0 ()
#10 0x000000018203c960 in __CFRunLoopDoSources0 ()
#11 0x0000000182036a8c in __CFRunLoopRun ()
#12 0x000000018203621c in CFRunLoopRunSpecific ()
#13 0x0000000199c02784 in GSEventRunModal ()
#14 0x0000000184a76ee8 in -[UIApplication _run] ()
#15 0x0000000184a7c75c in UIApplicationMain ()
#16 0x0000000104b308a0 in main at /app/main.m:22
#17 0x0000000181cf66b0 in start ()

Webプロセス側の解析

WIP

疑問

ソースを読むとよくでてくるWTFってなんの略? WebKit Transport Frameworkの略?

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

WKHTTPCookieStoreクラスコード解析

はじめに

中々開発者泣かせなWebKitのクッキー管理、色々はまりどころがあり、とうとうAppleもWKWebViewの移行期限を無期限に伸ばしました。と言うわけでソースは公開されているので中がどうなっているか解析していきます。

WebKitのソースコード

ソースコードはこちら

WebKitのプロセス構成

セキュリティ対策や重たいコンテンツを読み込んでもアプリが落ちないように、WebKitはここここここにある通り、UIProcess, WebProcess, NetworkProcess, StorageProcess(WorkerProcess)と4つのプロセスに分かれています。それぞれのプロセスはIPCでやりとりしています。

Cookieの取得

Cookieの取得は以下のメソッドを通じて行います。

class WKHTTPCookieStore 
func getAllCookies(_ completionHandler: @escaping ([HTTPCookie]) -> Void)

UIプロセス側のコールスタック

取得完了ハンドラーに復帰した時のUIスレッド(プロセス)のコールスタックは以下になります。イベントループ(RunLoop)から
WebKit〜IPC受信〜WebKit APIと呼び出しているのが分かります。

#1  0x000000018e6b5250 in WTF::Detail::CallableWrapper<WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>, void, WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&>::call(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&) ()
#2  0x000000018ea2b6c4 in WTF::Detail::CallableWrapper<API::HTTPCookieStore::filterAppBoundCookies(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)>&&)::$_0, void, WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&>::call(WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&) ()
#3  0x000000018ea0fa68 in WTF::Detail::CallableWrapper<WebKit::WebsiteDataStore::getAppBoundDomains(WTF::CompletionHandler<void (WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&)>&&) const::$_12, void, WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&>::call(WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&) ()
#4  0x000000018ea0c028 in WTF::CompletionHandler<void (WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&)>::operator()(WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&) ()
#5  0x000000018ea0c1a0 in WebKit::WebsiteDataStore::getAppBoundDomains(WTF::CompletionHandler<void (WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&)>&&) const ()
#6  0x000000018ea28ab0 in API::HTTPCookieStore::filterAppBoundCookies(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)>&&) ()
#7  0x000000018ea2bfc4 in WTF::Detail::CallableWrapper<API::HTTPCookieStore::cookies(WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>&&)::$_2, void, WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&>::call(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&) ()
#8  0x000000018ec0e3c8 in Messages::WebCookieManager::GetAllCookies::callReply(IPC::Decoder&, WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)>&&) ()
#9  0x000000018e92a8f4 in WTF::Detail::CallableWrapper<WebKit::AuxiliaryProcessProxy::sendMessage(std::__1::unique_ptr<IPC::Encoder, std::__1::default_delete<IPC::Encoder> >, WTF::OptionSet<IPC::SendOption>, WTF::Optional<std::__1::pair<WTF::CompletionHandler<void (IPC::Decoder*)>, unsigned long long> >&&, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::$_0, void, IPC::Decoder*>::call(IPC::Decoder*) ()
#10 0x000000018e683dd8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) ()
#11 0x000000018e6866c8 in WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_7, void>::call() ()
#12 0x000000018c3ac5e0 in WTF::RunLoop::performWork() ()
#13 0x000000018c3ad2c8 in WTF::RunLoop::performWork(void*) ()
#14 0x000000018203d76c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#15 0x000000018203d668 in __CFRunLoopDoSource0 ()
#16 0x000000018203c960 in __CFRunLoopDoSources0 ()
#17 0x0000000182036a8c in __CFRunLoopRun ()
#18 0x000000018203621c in CFRunLoopRunSpecific ()
#19 0x0000000199c02784 in GSEventRunModal ()
#20 0x0000000184a76ee8 in -[UIApplication _run] ()
#21 0x0000000184a7c75c in UIApplicationMain ()
#22 0x0000000104b308a0 in main at /app/main.m:22
#23 0x0000000181cf66b0 in start ()

Webプロセス側の解析

前述のコールスタックにある

WTF::Detail::CallableWrapper<API::HTTPCookieStore::cookies(WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>&&)::$_2, void, WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, 

より、APIHTTPCookieStore.mを見ると、

    auto* cookieManager = pool->supplement<WebKit::WebCookieManagerProxy>();
    cookieManager->getAllCookies(m_owningDataStore->sessionID(), [this, protectedThis = makeRef(*this), pool = WTFMove(pool), completionHandler = WTFMove(completionHandler)] (const Vector<WebCore::Cookie>& cookies) mutable {
        filterAppBoundCookies(cookies, WTFMove(completionHandler));
    });

WebCookieManagerProxyと言う記載があります。コールスタックにもありますね。〜Proxyと言うのはProxyパターンの事を指します。実態クラスがあるはずなんで、Proxyを外して
WebCookieManagerで検索すると、WebCookieManager.h、WebCookieManager.cppと言うファイルがでてきました。ここからはNetworkProcess側の解析になります。

コールスタックをさらに解析するとgetAllCookiesと言う記載があります。多分↓のメソッドを呼び出しているのでしょう。

void WebCookieManager::getAllCookies(PAL::SessionID sessionID, CompletionHandler<void(Vector<WebCore::Cookie>&&)>&& completionHandler)
{
    Vector<Cookie> cookies;
    if (auto* storageSession = m_process.storageSession(sessionID))
        cookies = storageSession->getAllCookies();
    completionHandler(WTFMove(cookies));
}

storageSession->getAllCookies()でクッキを取り出しています。と言うわけでm_process、storageSessionを見てみます。
m_processを調べるとNetworkProcessクラスのインスタンス変数です。NetworkProcessクラスのstorageSessionメソッドを見てみます。

WebCore::NetworkStorageSession* storageSession(const PAL::SessionID&) const;

NetworkStorageSessionと言うクラスを返すようになっています。クラス名、NetworkStorageSessionで定義しているメソッドを見るとCookieやキャッシュのに永続化に関するいろんな仕事をやるようです。
後、NetworkStorageSessionCocoa.mmと言うファイル名から、プラットホーム依存のクラスらしいです。
で、↑よりgetAllCookiesメソッドを調査すると

Vector<Cookie> NetworkStorageSession::getAllCookies()
{
    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
    return nsCookiesToCookieVector(nsCookieStorage().cookies);
}

↑よりさらにnsCookieStorage()を調べると

  WEBCORE_EXPORT NSHTTPCookieStorage *nsCookieStorage() const;

と言う宣言がありました。よく知っているNSHTTPCookieStorageクラスです。実装は見当たりませんでした。

まとめ

Cookieの取り出しを見ると、WebProcessにあるNSHTTPCookieStorageからCookieを取り出してUIProcessに返しているだけでした。

Cookieの設定

Cookieの設定は以下のメソッドを通じて行います。

class WKHTTPCookieStore 
func setCookie(_ cookie: HTTPCookie, 
completionHandler: (() -> Void)? = nil)

UIプロセス側のコールスタック

設定完了ハンドラーに復帰した時のUIスレッド(プロセス)のコールスタックは以下になります。

#1  0x000000018ea2c3f8 in WTF::Detail::CallableWrapper<auto API::HTTPCookieStore::setCookies(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::CompletionHandler<void ()>&&)::$_4::operator()<WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> >(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)::'lambda'(), void>::call() ()
#2  0x000000018e9821a8 in WTF::Detail::CallableWrapper<void WebKit::AuxiliaryProcessProxy::sendWithAsyncReply<Messages::WebCookieManager::SetCookie, WTF::CompletionHandler<void ()> >(Messages::WebCookieManager::SetCookie&&, WTF::CompletionHandler<void ()>&&, unsigned long long, WTF::OptionSet<IPC::SendOption>, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::'lambda'(IPC::Decoder*), void, IPC::Decoder*>::call(IPC::Decoder*) ()
#3  0x000000018e92a8f4 in WTF::Detail::CallableWrapper<WebKit::AuxiliaryProcessProxy::sendMessage(std::__1::unique_ptr<IPC::Encoder, std::__1::default_delete<IPC::Encoder> >, WTF::OptionSet<IPC::SendOption>, WTF::Optional<std::__1::pair<WTF::CompletionHandler<void (IPC::Decoder*)>, unsigned long long> >&&, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::$_0, void, IPC::Decoder*>::call(IPC::Decoder*) ()
#4  0x000000018e683dd8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) ()
#5  0x000000018e6866c8 in WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_7, void>::call() ()
#6  0x000000018c3ac5e0 in WTF::RunLoop::performWork() ()
#7  0x000000018c3ad2c8 in WTF::RunLoop::performWork(void*) ()
#8  0x000000018203d76c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#9  0x000000018203d668 in __CFRunLoopDoSource0 ()
#10 0x000000018203c960 in __CFRunLoopDoSources0 ()
#11 0x0000000182036a8c in __CFRunLoopRun ()
#12 0x000000018203621c in CFRunLoopRunSpecific ()
#13 0x0000000199c02784 in GSEventRunModal ()
#14 0x0000000184a76ee8 in -[UIApplication _run] ()
#15 0x0000000184a7c75c in UIApplicationMain ()
#16 0x0000000104b308a0 in main at /app/main.m:22
#17 0x0000000181cf66b0 in start ()

Webプロセス側の解析

取り出しとほぼ同じ機構のはず。

Cookieの削除

Cookieの削除は以下のメソッドを通じて行います。

class WKHTTPCookieStore 
func delete(_ cookie: HTTPCookie, 
completionHandler: (() -> Void)? = nil)

UIプロセス側のコールスタック

削除完了ハンドラーに復帰した時のUIスレッド(プロセス)のコールスタックは以下になります。

#1  0x000000018ea2c638 in WTF::Detail::CallableWrapper<API::HTTPCookieStore::deleteCookie(WebCore::Cookie const&, WTF::CompletionHandler<void ()>&&)::$_6, void>::call() ()
#2  0x000000018e981ebc in WTF::Detail::CallableWrapper<void WebKit::AuxiliaryProcessProxy::sendWithAsyncReply<Messages::WebCookieManager::DeleteCookie, WebKit::WebCookieManagerProxy::deleteCookie(PAL::SessionID, WebCore::Cookie const&, WTF::CompletionHandler<void ()>&&)::$_0>(Messages::WebCookieManager::DeleteCookie&&, WebKit::WebCookieManagerProxy::deleteCookie(PAL::SessionID, WebCore::Cookie const&, WTF::CompletionHandler<void ()>&&)::$_0&&, unsigned long long, WTF::OptionSet<IPC::SendOption>, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::'lambda'(IPC::Decoder*), void, IPC::Decoder*>::call(IPC::Decoder*) ()
#3  0x000000018e92a8f4 in WTF::Detail::CallableWrapper<WebKit::AuxiliaryProcessProxy::sendMessage(std::__1::unique_ptr<IPC::Encoder, std::__1::default_delete<IPC::Encoder> >, WTF::OptionSet<IPC::SendOption>, WTF::Optional<std::__1::pair<WTF::CompletionHandler<void (IPC::Decoder*)>, unsigned long long> >&&, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::$_0, void, IPC::Decoder*>::call(IPC::Decoder*) ()
#4  0x000000018e683dd8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) ()
#5  0x000000018e6866c8 in WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_7, void>::call() ()
#6  0x000000018c3ac5e0 in WTF::RunLoop::performWork() ()
#7  0x000000018c3ad2c8 in WTF::RunLoop::performWork(void*) ()
#8  0x000000018203d76c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#9  0x000000018203d668 in __CFRunLoopDoSource0 ()
#10 0x000000018203c960 in __CFRunLoopDoSources0 ()
#11 0x0000000182036a8c in __CFRunLoopRun ()
#12 0x000000018203621c in CFRunLoopRunSpecific ()
#13 0x0000000199c02784 in GSEventRunModal ()
#14 0x0000000184a76ee8 in -[UIApplication _run] ()
#15 0x0000000184a7c75c in UIApplicationMain ()
#16 0x0000000104b308a0 in main at /app/main.m:22
#17 0x0000000181cf66b0 in start ()

Webプロセス側の解析

取り出しとほぼ同じ機構のはず。

疑問

  • ソースを読むとよくでてくるWTFってなんの略? WebKit Transport Framework?
  • Cookie管理について。Androidは同期呼び出しで、iOSは非同期呼び出しなのはなんで? どっちもプロセス間通信が走っているけど。
  • iOSの非公開APIにアクセスしまくっているけど、これはアップル主導のプロジェクトからなのか。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WKHTTPCookieStoreクラスのコード解析

はじめに

中々開発者泣かせなWebKitのクッキー管理。色々はまりどころがあり、とうとうAppleもWKWebViewの移行期限を無期限に伸ばしました。と言うわけでソースは公開されているので中がどうなっているか解析していきます。

Androidについては以前にこの記事で解説しています。

WebKitのソースコード

ソースコードはこちら

WebKitのプロセス構成

セキュリティ対策や重たいコンテンツを読み込んでもアプリが落ちないように、WebKitはここここここにある通り、UIProcess, WebProcess, NetworkProcess, StorageProcess(WorkerProcess)と4つのプロセスに分かれています。それぞれのプロセスはIPCでやりとりしています。

Cookieの取得

Cookieの取得は以下のメソッドを通じて行います。

class WKHTTPCookieStore 
func getAllCookies(_ completionHandler: @escaping ([HTTPCookie]) -> Void)

UIプロセス側のコールスタック

取得完了ハンドラーに復帰した時のUIスレッド(プロセス)のコールスタックは以下になります。イベントループ(RunLoop)から
WebKit〜IPC受信〜WebKit APIと呼び出しているのが分かります。

#1  0x000000018e6b5250 in WTF::Detail::CallableWrapper<WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>, void, WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&>::call(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&) ()
#2  0x000000018ea2b6c4 in WTF::Detail::CallableWrapper<API::HTTPCookieStore::filterAppBoundCookies(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)>&&)::$_0, void, WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&>::call(WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&) ()
#3  0x000000018ea0fa68 in WTF::Detail::CallableWrapper<WebKit::WebsiteDataStore::getAppBoundDomains(WTF::CompletionHandler<void (WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&)>&&) const::$_12, void, WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&>::call(WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&) ()
#4  0x000000018ea0c028 in WTF::CompletionHandler<void (WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&)>::operator()(WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&, WTF::HashSet<WTF::String, WTF::DefaultHash<WTF::String>, WTF::HashTraits<WTF::String> > const&) ()
#5  0x000000018ea0c1a0 in WebKit::WebsiteDataStore::getAppBoundDomains(WTF::CompletionHandler<void (WTF::HashSet<WebCore::RegistrableDomain, WTF::DefaultHash<WebCore::RegistrableDomain>, WTF::HashTraits<WebCore::RegistrableDomain> > const&)>&&) const ()
#6  0x000000018ea28ab0 in API::HTTPCookieStore::filterAppBoundCookies(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)>&&) ()
#7  0x000000018ea2bfc4 in WTF::Detail::CallableWrapper<API::HTTPCookieStore::cookies(WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>&&)::$_2, void, WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&>::call(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&) ()
#8  0x000000018ec0e3c8 in Messages::WebCookieManager::GetAllCookies::callReply(IPC::Decoder&, WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)>&&) ()
#9  0x000000018e92a8f4 in WTF::Detail::CallableWrapper<WebKit::AuxiliaryProcessProxy::sendMessage(std::__1::unique_ptr<IPC::Encoder, std::__1::default_delete<IPC::Encoder> >, WTF::OptionSet<IPC::SendOption>, WTF::Optional<std::__1::pair<WTF::CompletionHandler<void (IPC::Decoder*)>, unsigned long long> >&&, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::$_0, void, IPC::Decoder*>::call(IPC::Decoder*) ()
#10 0x000000018e683dd8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) ()
#11 0x000000018e6866c8 in WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_7, void>::call() ()
#12 0x000000018c3ac5e0 in WTF::RunLoop::performWork() ()
#13 0x000000018c3ad2c8 in WTF::RunLoop::performWork(void*) ()
#14 0x000000018203d76c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#15 0x000000018203d668 in __CFRunLoopDoSource0 ()
#16 0x000000018203c960 in __CFRunLoopDoSources0 ()
#17 0x0000000182036a8c in __CFRunLoopRun ()
#18 0x000000018203621c in CFRunLoopRunSpecific ()
#19 0x0000000199c02784 in GSEventRunModal ()
#20 0x0000000184a76ee8 in -[UIApplication _run] ()
#21 0x0000000184a7c75c in UIApplicationMain ()
#22 0x0000000104b308a0 in main at /app/main.m:22
#23 0x0000000181cf66b0 in start ()

Webプロセス側の解析

前述のコールスタックにある

WTF::Detail::CallableWrapper<API::HTTPCookieStore::cookies(WTF::CompletionHandler<void (WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&)>&&)::$_2, void, WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, 

より、APIHTTPCookieStore.mを見ると、

    auto* cookieManager = pool->supplement<WebKit::WebCookieManagerProxy>();
    cookieManager->getAllCookies(m_owningDataStore->sessionID(), [this, protectedThis = makeRef(*this), pool = WTFMove(pool), completionHandler = WTFMove(completionHandler)] (const Vector<WebCore::Cookie>& cookies) mutable {
        filterAppBoundCookies(cookies, WTFMove(completionHandler));
    });

WebCookieManagerProxyと言う記載があります。コールスタックにもありますね。〜Proxyと言うのはProxyパターンの事を指します。実態クラスがあるはずなんで、Proxyを外して
WebCookieManagerで検索すると、WebCookieManager.h、WebCookieManager.cppと言うファイルがでてきました。ここからはNetworkProcess側の解析になります。

コールスタックをさらに解析するとgetAllCookiesと言う記載があります。多分↓のメソッドを呼び出しているのでしょう。

void WebCookieManager::getAllCookies(PAL::SessionID sessionID, CompletionHandler<void(Vector<WebCore::Cookie>&&)>&& completionHandler)
{
    Vector<Cookie> cookies;
    if (auto* storageSession = m_process.storageSession(sessionID))
        cookies = storageSession->getAllCookies();
    completionHandler(WTFMove(cookies));
}

storageSession->getAllCookies()でクッキを取り出しています。と言うわけでm_process、storageSessionを見てみます。
m_processを調べるとNetworkProcessクラスのインスタンス変数です。NetworkProcessクラスのstorageSessionメソッドを見てみます。

WebCore::NetworkStorageSession* storageSession(const PAL::SessionID&) const;

NetworkStorageSessionと言うクラスを返すようになっています。クラス名、NetworkStorageSessionで定義しているメソッドを見るとCookieやキャッシュのに永続化に関するいろんな仕事をやるようです。
後、NetworkStorageSessionCocoa.mmと言うファイル名から、プラットホーム依存のクラスらしいです。
で、↑よりgetAllCookiesメソッドを調査すると

Vector<Cookie> NetworkStorageSession::getAllCookies()
{
    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
    return nsCookiesToCookieVector(nsCookieStorage().cookies);
}

↑よりさらにnsCookieStorage()を調べると

  WEBCORE_EXPORT NSHTTPCookieStorage *nsCookieStorage() const;

と言う宣言がありました。よく知っているNSHTTPCookieStorageクラスです。実装は見当たりませんでした。

まとめ

Cookieの取り出しを見ると、WebProcessにあるNSHTTPCookieStorageからCookieを取り出してUIProcessに返しているだけでした。

Cookieの設定

Cookieの設定は以下のメソッドを通じて行います。

class WKHTTPCookieStore 
func setCookie(_ cookie: HTTPCookie, 
completionHandler: (() -> Void)? = nil)

UIプロセス側のコールスタック

設定完了ハンドラーに復帰した時のUIスレッド(プロセス)のコールスタックは以下になります。

#1  0x000000018ea2c3f8 in WTF::Detail::CallableWrapper<auto API::HTTPCookieStore::setCookies(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&, WTF::CompletionHandler<void ()>&&)::$_4::operator()<WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> >(WTF::Vector<WebCore::Cookie, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc>&&)::'lambda'(), void>::call() ()
#2  0x000000018e9821a8 in WTF::Detail::CallableWrapper<void WebKit::AuxiliaryProcessProxy::sendWithAsyncReply<Messages::WebCookieManager::SetCookie, WTF::CompletionHandler<void ()> >(Messages::WebCookieManager::SetCookie&&, WTF::CompletionHandler<void ()>&&, unsigned long long, WTF::OptionSet<IPC::SendOption>, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::'lambda'(IPC::Decoder*), void, IPC::Decoder*>::call(IPC::Decoder*) ()
#3  0x000000018e92a8f4 in WTF::Detail::CallableWrapper<WebKit::AuxiliaryProcessProxy::sendMessage(std::__1::unique_ptr<IPC::Encoder, std::__1::default_delete<IPC::Encoder> >, WTF::OptionSet<IPC::SendOption>, WTF::Optional<std::__1::pair<WTF::CompletionHandler<void (IPC::Decoder*)>, unsigned long long> >&&, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::$_0, void, IPC::Decoder*>::call(IPC::Decoder*) ()
#4  0x000000018e683dd8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) ()
#5  0x000000018e6866c8 in WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_7, void>::call() ()
#6  0x000000018c3ac5e0 in WTF::RunLoop::performWork() ()
#7  0x000000018c3ad2c8 in WTF::RunLoop::performWork(void*) ()
#8  0x000000018203d76c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#9  0x000000018203d668 in __CFRunLoopDoSource0 ()
#10 0x000000018203c960 in __CFRunLoopDoSources0 ()
#11 0x0000000182036a8c in __CFRunLoopRun ()
#12 0x000000018203621c in CFRunLoopRunSpecific ()
#13 0x0000000199c02784 in GSEventRunModal ()
#14 0x0000000184a76ee8 in -[UIApplication _run] ()
#15 0x0000000184a7c75c in UIApplicationMain ()
#16 0x0000000104b308a0 in main at /app/main.m:22
#17 0x0000000181cf66b0 in start ()

Webプロセス側の解析

取り出しとほぼ同じ機構のはず。

Cookieの削除

Cookieの削除は以下のメソッドを通じて行います。

class WKHTTPCookieStore 
func delete(_ cookie: HTTPCookie, 
completionHandler: (() -> Void)? = nil)

UIプロセス側のコールスタック

削除完了ハンドラーに復帰した時のUIスレッド(プロセス)のコールスタックは以下になります。

#1  0x000000018ea2c638 in WTF::Detail::CallableWrapper<API::HTTPCookieStore::deleteCookie(WebCore::Cookie const&, WTF::CompletionHandler<void ()>&&)::$_6, void>::call() ()
#2  0x000000018e981ebc in WTF::Detail::CallableWrapper<void WebKit::AuxiliaryProcessProxy::sendWithAsyncReply<Messages::WebCookieManager::DeleteCookie, WebKit::WebCookieManagerProxy::deleteCookie(PAL::SessionID, WebCore::Cookie const&, WTF::CompletionHandler<void ()>&&)::$_0>(Messages::WebCookieManager::DeleteCookie&&, WebKit::WebCookieManagerProxy::deleteCookie(PAL::SessionID, WebCore::Cookie const&, WTF::CompletionHandler<void ()>&&)::$_0&&, unsigned long long, WTF::OptionSet<IPC::SendOption>, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::'lambda'(IPC::Decoder*), void, IPC::Decoder*>::call(IPC::Decoder*) ()
#3  0x000000018e92a8f4 in WTF::Detail::CallableWrapper<WebKit::AuxiliaryProcessProxy::sendMessage(std::__1::unique_ptr<IPC::Encoder, std::__1::default_delete<IPC::Encoder> >, WTF::OptionSet<IPC::SendOption>, WTF::Optional<std::__1::pair<WTF::CompletionHandler<void (IPC::Decoder*)>, unsigned long long> >&&, WebKit::AuxiliaryProcessProxy::ShouldStartProcessThrottlerActivity)::$_0, void, IPC::Decoder*>::call(IPC::Decoder*) ()
#4  0x000000018e683dd8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) ()
#5  0x000000018e6866c8 in WTF::Detail::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_7, void>::call() ()
#6  0x000000018c3ac5e0 in WTF::RunLoop::performWork() ()
#7  0x000000018c3ad2c8 in WTF::RunLoop::performWork(void*) ()
#8  0x000000018203d76c in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#9  0x000000018203d668 in __CFRunLoopDoSource0 ()
#10 0x000000018203c960 in __CFRunLoopDoSources0 ()
#11 0x0000000182036a8c in __CFRunLoopRun ()
#12 0x000000018203621c in CFRunLoopRunSpecific ()
#13 0x0000000199c02784 in GSEventRunModal ()
#14 0x0000000184a76ee8 in -[UIApplication _run] ()
#15 0x0000000184a7c75c in UIApplicationMain ()
#16 0x0000000104b308a0 in main at /app/main.m:22
#17 0x0000000181cf66b0 in start ()

Webプロセス側の解析

取り出しとほぼ同じ機構のはず。

疑問

  • ソースを読むとよくでてくるWTFってなんの略? WebKit Transport Framework?
  • Cookie管理について。Androidは同期呼び出しで、iOSは非同期呼び出しなのはなんで? どっちもプロセス間通信が走っているけど。
  • iOSの非公開APIにアクセスしまくっているけど、これはアップル主導のプロジェクトからなのか。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[iOS] ダークモードを考慮したAsset Catalogの管理

本記事では、Swiftの画像の命名規則とAsset Catalogのフォルダ構成について、個人で最適だと考える方法について説明します。

画像の命名規則について

注意点

本記事では,画像のパターンが2種類あります。
例)sampleという画像の名前の場合
Asset Catalog内の画像
→ sample

Asset Catalogに追加する前の画像
→ sample.png

命名規則

画像の命名は以下で統一

スネークケースを使用

  • ic_画像名_修飾.png
    • 小さめなアイコンの画像
    • 例: ic_star_fill.png
  • img_画像名_修飾.png
    • 大きめなillust, logoなどの画像
    • 例: img_logo.png

修飾について以下の例があります。
- fill
- off/on

ダークモード用の画像の場合

語尾にdarkを付ける
- ic_画像名_修飾_dark.png
- img_画像名_修飾_dark.png

ダークモード用の画像分けて命名する理由
iOSでは,同じ命名で各モードの画像をAsset Catalog内に追加すると,〇〇-1.pngのように重複によって番号が付与されてしまう.
内部的に不都合が生じるため,ダークモード用の画像の命名が必要である

Asset Catalogのフォルダ

現在のAsset Catalog内のフォルダ構成は以下の通りです。

Assets

  • iconフォルダ
    • ic_画像名
  • imageフォルダ
    • img_画像名
  • colorフォルダ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む