Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Yappliにおけるパーミッション要求の課題と改善 / Challenges and Impr...

Nao-RandD
October 17, 2023

Yappliにおけるパーミッション要求の課題と改善 / Challenges and Improvements of Permission Requests in Yappli

Yappli Tech Conference 2023 における発表内容です。
https://yappli.connpass.com/event/295001/

Nao-RandD

October 17, 2023
Tweet

More Decks by Nao-RandD

Other Decks in Programming

Transcript

  1. 0 1 iOSにおけるパーミッション要求 iOSにおけるパーミッション要求 パーミッション要求とは? • アプリがデバイスの特定機能やデータに 
 アクセスするために必要となる 


    例:位置情報、カメラ、プッシュ通知など 
 • ユーザーがアプリにどのような特定機能‧ 
 アクセスを許可するかを明⽰的にコントロー ルできる
  2. 0 1 iOSにおけるパーミッション要求 iOSにおけるパーミッション要求 開発者に期待されること • 必要最低限のパーミッションのみを 
 要求すること 


    • プライバシーの側⾯でデータや 
 リソース利⽤の透明性を担保する https://developer.apple.com/design/human-interface-guidelines/privacy
  3. 0 1 iOSにおけるパーミッション要求 iOSにおけるパーミッション要求 パーミッション要求のダイアログ • ダイアログ表⽰処理はアプリで呼び出し 
 システムによって実⾏される 


    • 同時に複数呼び出すとダイアログが重複する 
 動作となりユーザー体験を損なう 
 • ホーム画⾯など要求処理が集中する場所では 
 呼び出しに配慮が必要になる
  4. パーミッション要求はシステム実⾏であり、 
 順次実⾏のためにコールバックで実装している コールバックの実⾏は開発者の責務となり、 
 呼ばないことによるコンパイルエラーは出ない 1 基盤改善前の課題 03 基盤改善における課題と今後実現したいこと

    可読性‧保守性の低さ func hoge() { requestPermissionA { // ॏཁͳޙଓॲཧ } } func requestPermissionA( completion: @escaping () -> Void) { // PermissionAͷύʔϛογϣϯཁٻॲཧ // ... if isAuthorize { // ޙଓͷॲཧ͕࣮ߦ͞ΕΔ completion() } else { // Կ΋͠ͳ͔ͬͨ } } パーミッションに関わる処理はストア審査リジェクト、 機能提供できないなど⼤きな問題になる可能性が⾼い
  5. パーミッションの種類によって 
 要求処理に必要な実装は異なる 機能単位で要求処理を別々に実装しており 処理として共通化されていない 1 基盤改善前の課題 03 基盤改善における課題と今後実現したいこと 要求処理のボイラープレート化

    struct ModuleA { typealias Callback = (Bool) -> Void func requestLocationPermission( _ completion: @escaping Callback) { // Ґஔ৘ใͷύʔϛογϣϯཁٻॲཧ } func requestATTPermission(_ completion: @escaping Callback) { // ATT(AppTrackingTransparancy)ͷύʔϛογϣϯཁٻॲཧ } } struct ModuleB { typealias Callback = (Bool) -> Void func requestLocationPermission( _ completion: @escaping Callback) { // Ґஔ৘ใͷύʔϛογϣϯཁٻॲཧ } } 同じパーミッションの実装が 必要な機能ごとに増えていく
  6. ホーム画⾯など同時に複数のパーミッション を組み合わせて要求したい場⾯がある 1 基盤改善前の課題 03 基盤改善における課題と今後実現したいこと 同時に複数の要求を扱うのが難しい func requestMultiPermission( _

    completion: @escaping () -> Void) { requestLocationPermission { _ in requestATTPermission { _ in requestBluetoothPermission { _ in // ޙଓॲཧ } } } } 要求処理が増えるたびに ネストが深くなってしまう
  7. STEP 2 STEP 1 STEP 3 Swift Concurrency導⼊ インターフェースを共通化 04

    改善までの道のり 要求処理のワークフロー化
  8. 1 2 Swift Concurrencyのメリット 導⼊における考慮 • Swift Concurrencyについて • async/awaitによる可読性向上

    • パーミッションごとの要求処理の差分 • Delegateパターンの吸収 04 改善までの道のり- STEP 1 Swift Concurrency導⼊ 課題 今後実現したいこと ⬜︎ 可読性‧保守性の低さ ⬜︎ 要求処理のボイラープレート化 ⬜︎ 同時に複数の要求を扱うのが難しい ⬜︎ 事前説明画⾯の機能追加 ✅ 改善におけるチェックリスト
  9. Swift 5 . 5 から⾔語機能として登場した ⾮同期処理‧並列処理のコードを簡潔かつ安 全に記述できる async/awaitを使⽤することができる 1 Swift

    Concurrencyのメリット 04 改善までの道のり- STEP 1 Swift Concurrency導⼊ Swift Concurrencyについて https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/
  10. async/awaitを⽤いることで⾮同期処理を 
 同期処理と同じような書き⽅で実装できる コールバックのネストが深い処理などを 
 可読性⾼く実装する助けになる 1 Swift Concurrencyのメリット 04

    改善までの道のり- STEP 1 Swift Concurrency導⼊ async/awaitによる可読性向上 // ίʔϧόοΫͰͷ࣮ߦ foo { x in bar(x) { y in baz(y) { z in // ޙଓͷॲཧ } } } // async/awaitͰͷ࣮ߦ let x = await foo() let y = await bar(x) let z = await baz(y) パーミッション要求処理を async/awaitに落とし込んでいく
  11. async/awaitが対応したものがAPIが 
 提供されていれば素直に実装する プッシュ通知のUNUserNoti fi cationなどは async/awaitで呼び出せるAPIがある requestAuthorization(for: ) 2

    導⼊における考慮 04 改善までの道のり- STEP 1 Swift Concurrency導⼊ async/await の I/F が⽤意されている ⭕ func request() async -> Bool { do { let nc = UNUserNotificationCenter.current() return try await nc.requestAuthorization( options: [.badge, .sound, .alert]) } catch { // ΤϥʔϋϯυϦϯά } } https://developer.apple.com/documentation/usernoti fi cations/unusernoti fi cationcenter/1649527-requestauthorization
  12. 代表的なケース: Delegateでパーミッションのステータス変化を 
 検知する必要がある 例:Bluetoothや位置情報 そのままasync/awaitで置き換えられない 2 導⼊における考慮 04 改善までの道のり-

    STEP 1 Swift Concurrency導⼊ async/await の I/F が⽤意されていない ❌ class BluetoothPermission: NSObject, CBCentralManagerDelegate { private var centralManager: CBCentralManager? typealias Status = CBManagerAuthorization func request() { // ύʔϛογϣϯεςʔλεʹԠͯ͡ૣظϦλʔϯ // ... // CBCentralManagerΛΠϯελϯεԽ͢ΔͱμΠΞϩάදࣔ centralManager = CBCentralManager(delegate: self, queue: nil) } /// εςʔλε͕มߋ͞ΕΔͱݺ͹ΕΔDelegateϝιου func centralManagerDidUpdateState( _ central: CBCentralManager) { let status = CBManager.authorization // ύʔϛογϣϯεςʔλε͝ͱͷॲཧ } }
  13. 代表的なケース: Delegateでパーミッションのステータス変化を 
 検知する必要がある 例:Bluetoothや位置情報 そのままasync/awaitで置き換えられない 2 導⼊における考慮 04 改善までの道のり-

    STEP 1 Swift Concurrency導⼊ async/await の I/F が⽤意されていない ❌ https://developer.apple.com/documentation/swift/withcheckedcontinuation(function:_:) func request() async -> Status { // εςʔλεʹԠͯ͡ૣظϦλʔϯ // ... // CBCentralManagerΛΠϯελϯεԽ͢ΔͱμΠΞϩάදࣔ centralManager = CBCentralManager(delegate: self, queue: nil) return await withCheckedContinuation { [weak self] (continuation: Continuation) in guard let self = self else { return } self.continuation = continuation } } func centralManagerDidUpdateState( _ central: CBCentralManager) { let status = CBManager.authorization if status == .notDetermined { return } // resumeʹεςʔλεΛ౉͢ continuation?.resume(returning: status) continuation = nil } withCheckedContinuation を利⽤する
  14. 1 2 Swift Concurrencyのメリット 導⼊における考慮 • Swift Concurrencyについて • async/awaitによる可読性向上

    • パーミッションごとの要求処理の差分 • Delegateパターンの吸収 04 改善までの道のり- STEP 1 Swift Concurrency導⼊ 課題 今後実現したいこと ✅ 可読性‧保守性の低さ ⬜︎ 要求処理のボイラープレート化 ⬜︎ 同時に複数の要求を扱うのが難しい ⬜︎ 事前説明画⾯の機能追加 ✅ 改善におけるチェックリスト
  15. 1 2 共通化において実現したいこと 実装 • 異なるパーミッション要求処理の共通化 • Managerクラスを定義してI/Fも共通化 • 全体図

    • コードに落とし込む 04 改善までの道のり- STEP 2 インターフェースを共通化 課題 今後実現したいこと ✅ 可読性‧保守性の低さ ⬜︎ 要求処理のボイラープレート化 ⬜︎ 同時に複数の要求を扱うのが難しい ⬜︎ 事前説明画⾯の機能追加 ✅ 改善におけるチェックリスト
  16. 1 共通化において実現したいこと 04 改善までの道のり- STEP 2 インターフェースを共通化 Managerクラスを定義してI/Fも共通化 Manager I/F

    Managerにパーミッションを 渡した順序で順次実⾏する 要件 同時に複数のパーミッション要求がされた場合 にも順次実⾏
  17. 1 共通化において実現したいこと 04 改善までの道のり- STEP 2 インターフェースを共通化 Managerクラスを定義してI/Fも共通化 機能A パーミッションA

    パーミッションB 機能B パーミッションC Manager I/F Managerにパーミッションを 渡した順序で順次実⾏する 要件 同時に複数のパーミッション要求がされた場合 にも順次実⾏
  18. 1 共通化において実現したいこと 04 改善までの道のり- STEP 2 インターフェースを共通化 Managerクラスを定義してI/Fも共通化 機能A パーミッションA

    パーミッションB 機能B パーミッションC Manager I/F Managerにパーミッションを 渡した順序で順次実⾏する 要件 同時に複数のパーミッション要求がされた場合 にも順次実⾏
  19. 1 共通化において実現したいこと 04 改善までの道のり- STEP 2 インターフェースを共通化 Managerクラスを定義してI/Fも共通化 機能A パーミッションA

    機能B パーミッションC Manager I/F キューに追加 キュー 実⾏タスク パーミッションB キューでパーミッション要求処理をまとめて 管理して追加された順に実⾏する
  20. 1 共通化において実現したいこと 04 改善までの道のり- STEP 2 インターフェースを共通化 Managerクラスを定義してI/Fも共通化 機能A 機能B

    パーミッションC Manager I/F キューに追加 キュー 実⾏タスク パーミッションB パーミッションA パーミッションAを実⾏中 (ダイアログ表⽰状態) キューでパーミッション要求処理をまとめて 管理して追加された順に実⾏する
  21. 1 共通化において実現したいこと 04 改善までの道のり- STEP 2 インターフェースを共通化 Managerクラスを定義してI/Fも共通化 機能A 機能B

    パーミッションC Manager I/F キューに追加 キュー 実⾏タスク パーミッションB パーミッションA パーミッションAを実⾏中 (ダイアログ表⽰状態) Managerが実⾏中に パーミッション要求処理 
 → キューへの追加 キューでパーミッション要求処理をまとめて 管理して追加された順に実⾏する
  22. 1 共通化において実現したいこと 04 改善までの道のり- STEP 2 インターフェースを共通化 Managerクラスを定義してI/Fも共通化 機能A 機能B

    Manager I/F キューに追加 キュー 実⾏タスク パーミッションB パーミッションA キューに追加され 順番に実⾏される パーミッションC キューでパーミッション要求処理をまとめて 管理して追加された順に実⾏する パーミッションAを実⾏中 (ダイアログ表⽰状態)
  23. キューでパーミッション要求処理をまとめて 管理して追加された順に実⾏する 1 共通化において実現したいこと 04 改善までの道のり- STEP 2 インターフェースを共通化 Managerクラスを定義してI/Fも共通化

    Manager I/F キューに追加 キュー 実⾏タスク パーミッションB パーミッションA キューに追加され 順番に実⾏される パーミッションC パーミッションAを実⾏中 (ダイアログ表⽰状態) この動作を満たすように 実装していく
  24. 2 実装 04 改善までの道のり- STEP 2 インターフェースを共通化 // MARK: PermissionRequest

    protocol PermissionRequest { func request() async -> PermissionStatus } // MARK: PermissionStatus protocol PermissionStatus {} パーミッション要求をPermissionRequest としてプロトコルに定義 パーミッションステータスも PermissionStatusとしてプロトコルに定義 コードに落とし込む(パーミッション要求)
  25. 必要な要素 • PermissionRequestを⼊れるキュー • 実⾏中を管理できるOperationStatus • I/Fとして外部からキューに追加するadd() • キューにあるPermissionRequestを 


    順次実⾏するexecuteRequest() 2 実装 04 改善までの道のり- STEP 2 インターフェースを共通化 @MainActor final class PermissionManager { static let shared = PermissionManager() private init() {} private enum OperationStatus { case running case waiting } private var queue: [PermissionRequest] = [] private var operationStatus: OperationStatus = .waiting func add(request: PermissionRequest) { // ... // ࣮ߦதͰͳ͚Ε͹ executeRequest() } private func executeRequest() async { // ... } } コードに落とし込む(Managerクラス)
  26. @MainActor final class PermissionManager { static let shared = PermissionManager()

    private init() {} private enum OperationStatus { case running case waiting } private var queue: [PermissionRequest] = [] private var operationStatus: OperationStatus = .waiting func add(request: PermissionRequest) { // ... // ࣮ߦதͰͳ͚Ε͹ executeRequest() } private func executeRequest() async { // ... } } 必要な要素 • PermissionRequestを⼊れるキュー • 実⾏中を管理できるOperationStatus • I/Fとして外部からキューに追加するadd() • キューにあるPermissionRequestを 
 順次実⾏するexecuteRequest() 2 実装 04 改善までの道のり- STEP 2 インターフェースを共通化 排他的なキューへのアクセスとなるように PermissionManagerを@MainActorにする コードに落とし込む(Managerクラス)
  27. 実装後 • PermissionManagerにパーミッション要求を 
 追加するだけで実⾏できる • 同時に複数の要求処理が追加されても順次実⾏で 
 パーミッション要求を⾏える 2

    実装 04 改善までの道のり- STEP 2 インターフェースを共通化 // MARK: AsIs func requestMultiPermission() { requestLocationPermission { _ in requestATTPermission { _ in } } } // MARK: ToBe PermissionManager.shared.add( request: LocationPermission()) PermissionManager.shared.add( request: ATTPermission()) コードに落とし込む(Managerクラス)
  28. 実装後 • PermissionManagerにパーミッション要求を 
 追加するだけで実⾏できる • 同時に複数の要求処理が追加されても順次実⾏で 
 パーミッション要求を⾏える 2

    実装 04 改善までの道のり- STEP 2 インターフェースを共通化 // MARK: AsIs func requestMultiPermission() { requestLocationPermission { _ in requestATTPermission { _ in } } } // MARK: ToBe PermissionManager.shared.add( request: LocationPermission()) PermissionManager.shared.add( request: ATTPermission()) 同時に呼び出す要求処理が 増えてもネストが深くならない コードに落とし込む(Managerクラス)
  29. 1 2 共通化において実現したいこと 実装 • 異なるパーミッション要求処理の共通化 • 組み合わせても使⽤できる仕組み • コードに落とし込む

    04 改善までの道のり- STEP 2 インターフェースを共通化 課題 今後実現したいこと ✅ 可読性‧保守性の低さ ✅ 要求処理のボイラープレート化 ✅ 同時に複数の要求を扱うのが難しい ⬜︎ 事前説明画⾯の機能追加 ✅ 改善におけるチェックリスト
  30. 1 2 要求処理を組み合わせるワークフロー 実装 • 要求処理に紐づいた処理も共通化したい • 全体図 • コードに落とし込む

    04 改善までの道のり- STEP 3 要求処理のワークフロー化 課題 今後実現したいこと ✅ 可読性‧保守性の低さ ✅ 要求処理のボイラープレート化 ✅ 同時に複数の要求を扱うのが難しい ⬜︎ 事前説明画⾯の機能追加 ✅ 改善におけるチェックリスト
  31. 2 実装 // MARK: PermissionAction protocol PermissionAction { func execute()

    async } // MARK: PermissionRequest protocol PermissionRequest: PermissionAction { func request() async -> PermissionStatus } // Protocol ExtensionͰrequestΛݺͿ͜ͱͰ 
 // PermissionRequestଆ͸࣮૷ʹ͓͍ͯ 
 // PermissionActionͷ࣮૷Λҙࣝ͠ͳͯ͘ྑ͘͢Δ extension PermissionRequest { func execute() async { _ = await request() } } protocol PermissionStatus {} コードに落とし込む (PermissionRequestプロトコル) 04 改善までの道のり- STEP 3 要求処理のワークフロー化
  32. 2 実装 // PermissionRequest͸͜Ε·Ͱ௨Γͷ࣮૷Ͱྑ͍ʢલड़ʣ struct PermissionA: PermissionRequest { func request()

    async -> PermissionStatus { // PermissionAͷύʔϛογϣϯཁٻॲཧ } } struct PermissionB: PermissionRequest { func request() async -> PermissionStatus { // PermissionBͷύʔϛογϣϯཁٻॲཧ } } // ෳ਺ͷύʔϛογϣϯΛऔಘ͢Δύλʔϯ΋ϫʔΫϑϩʔʹͰ͖Δ struct MultiPermissionWorkflow: PermissionAction { func execute() async { let statusA = await PermissionA().request() let statusB = await PermissionB().request() } } コードに落とし込む 
 (ワークフローとパーミッション要求) 04 改善までの道のり- STEP 3 要求処理のワークフロー化
  33. 実装後 • 各機能からPermissionManagerに 
 パーミッション要求とワークフローを 
 キューに追加するのみで実⾏できる 2 実装 struct

    LocationPermission: PermissionRequest { func request() async -> PermissionStatus { // Ґஔ৘ใͷύʔϛογϣϯཁٻॲཧ } } // ෳ਺ͷύʔϛογϣϯΛཁٻ͢ΔϫʔΫϑϩʔ struct MultiPermissionWorkflow: PermissionAction { func execute() async { await BluetoothPermission().request() // Bluetoothͷཁٻॲཧޙʹඞཁͳॲཧ await ATTPermission().request() } } // ϫʔΫϑϩʔ΋୯ମͷύʔϛογϣϯ΋ಉ͡Α͏ʹݺͼग़ͤΔ PermissionManager.shared.add( action: MultiPermissionWorkflow()) PermissionManager.shared.add( action: LocationPermission()) 04 改善までの道のり- STEP 3 要求処理のワークフロー化 コードに落とし込む 
 (ワークフローとパーミッション要求)
  34. // MARK: ࣄલઆ໌ը໘ΛؚΜͩύʔϛογϣϯཁٻ struct PreAlertScreenWorkflow: PermissionAction { func execute() async

    { // ͜͜ʹࣄલઆ໌ը໘ͷॲཧʢҐஔ৘ใʣ await LocationPermission().request() // ͜͜ʹࣄલઆ໌ը໘ͷॲཧʢATTʣ await ATTPermission().request() // ޙଓॲཧ } } // ݺͼݩ PermissionManager.shared.add( action: PreAlertScreenWorkflow()) 実装後 • 各機能からPermissionManagerに 
 パーミッション要求とワークフローを 
 キューに追加するのみで実⾏できる 2 実装 04 改善までの道のり- STEP 3 要求処理のワークフロー化 コードに落とし込む 
 (ワークフローとパーミッション要求) 事前説明画⾯処理もワークフローとして PermissionManagerで扱える
  35. 1 2 要求処理を組み合わせるワークフロー 実装 • 要求処理に紐づいた処理も共通化したい • 全体図 • コードに落とし込む

    04 改善までの道のり- STEP 3 要求処理のワークフロー化 課題 今後実現したいこと ✅ 可読性‧保守性の低さ ✅ 要求処理のボイラープレート化 ✅ 同時に複数の要求を扱うのが難しい ✅ 事前説明画⾯の機能追加 ✅ 改善におけるチェックリスト