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

Method Swizzlingを行うライブラリにおけるマルチモジュール設計

Method Swizzlingを行うライブラリにおけるマルチモジュール設計

Method Swizzlingはメソッドの実行時の挙動を動的に変更する強力な機能ですが、アプリケーションの安定性に影響を与える可能性があります。今回はSwift Package Managerのマルチモジュール構成を採用した場合でも正しくSwizzlingが行われるよう、設計した事例について紹介します

yoshikma/松原良和

August 29, 2024
Tweet

Other Decks in Programming

Transcript

  1. Method Swizzlingのやり方 • viewDidLoadをSwizzlingしてみる • AppDelegateにSwizzling処理を追加 @UIApplicationMain class AppDelegate: UIResponder,

    UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { UIViewController.swizzling() return true } }
  2. Method Swizzlingのやり方 • viewDidLoadをSwizzlingしてみる extension UIViewController { func swizzling() {

    let originalSelector = #selector(viewDidLoad) let swizzledSelector = #selector(swizzled_viewDidLoad) guard let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector), let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector) else { return } method_exchangeImplementations(originalMethod, swizzledMethod) }() // 交換するMethod @objc func swizzled_viewDidLoad() { print(”swizzled_viewDidLoad") // 元のviewDidLoadを呼び出し swizzled_viewDidLoad() } }
  3. Method Swizzlingのやり方 • viewDidLoadをSwizzlingしてみる extension UIViewController { func swizzling() {

    let originalSelector = #selector(viewDidLoad) let swizzledSelector = #selector(swizzled_viewDidLoad) guard let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector), let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector) else { return } method_exchangeImplementations(originalMethod, swizzledMethod) }() // 交換するMethod @objc func swizzled_viewDidLoad() { print(”swizzled_viewDidLoad") // 元のviewDidLoadを呼び出し swizzled_viewDidLoad() } }
  4. Method Swizzlingの利用例 状態の追跡 • ユーザーのログイン状態を追跡し、特定のメソッドが呼ばれた際にログイン状態を確認する • UIApplicationのsendEvent:メソッドをSwizzlingして、ユーザーがアプリを操作するたびにログイン状態を チェック 自動可視判定 •

    特定のメソッドが呼ばれた際にアナリティクスイベントを送信する • UIViewControllerのviewWillAppear:メソッドをSwizzlingして、画面表示時にアナリティクスイベントを送信 Viewの動的変更 • アプリ全体のテーマを動的に変更するために、特定のメソッドの動作を変更する • UIButtonのsetTitleColor:forState:メソッドをSwizzlingして、テーマに応じた色を動的に設定 etc…
  5. Method Swizzlingの利用例 • 画面の可視判定 • 画面上にViewが表示された、されていないを判定したい場合 • ロギングや動画の自動再生 Cell1 Cell2

    Cell3 Cell4 画面スクロール Cell1 Cell2 Cell3 Cell4 見えなくなった 見えるようになった 見えない 見える
  6. Method Swizzlingの利用例 • iOS18+はonScrollTargetVisibilityChangeなどのAPIが利用可能 ScrollView { } .onScrollTargetVisibilityChange(idType: Int.self, threshold:

    0.5) { newItems in // 画面内に表示された!! } https://developer.apple.com/documentation/swiftui/view/onscrolltargetvisibilitychange(idtype:threshold:_:) override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // ページが表示された!! }
  7. Method Swizzlingの利用例 • 可視判定処理をSwizzlingを使ってライフサイクルに差し込む • View側で実装することなくViewControllerのViewに可視判定をつけることが可能 イベント Viewのライフサイクル 画面描画が完了 viewDidAppear

    現在の画面が非表示 viewDidDisappear スクロール scrollViewDidScroll アクションの送信(clickなど) sendAction scrollViewWillBeginDecelerating Cellの可視判定 scrollViewDidEndDecelerating scrollViewDidScroll スクロールのライフサイクル
  8. Method Swizzlingの危険性 Method Swizzlingしていることが明示的でない • 別の場所に実装しているコードが呼ばれる • 勝手にSwizzlingされていると気付けない こちらが呼ばれる 本来はこちらが呼ばれる

    元の処理を呼び出している // アプリ内のコード class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } } // ---SDK内でSwizzlingする処理--- // 略 // --- // Swizzling対象 @objc func swizzling_viewDidLoad() { self.title = ”Title" self.swizzling_viewDidLoad() }
  9. Method Swizzlingの危険性 同じメソッドを複数の箇所からSwizzlingすると競合し動作が不安定に class ViewController: UIViewController { override func viewDidLoad()

    { super.viewDidLoad() } @objc func first_swizzling_viewDidLoad() { self.title = ”Title" self.swizzling_viewDidLoad() } @objc func second_swizzling_viewDidLoad() { self.title = ”Title2" self.swizzling_viewDidLoad2() } } • もし同時にSwizzlingをしていたら… • 非同期でSwizzlingしていたら…
  10. Method Swizzlingの危険性 framework Swizzling処理 可視判定 データ送信 バッファリング 暗号化 SDK内でSwizzlingをしているかは コードを読まなければわからない

    Other framework Swizzling処理 可視判定 データ送信 バッファリング 暗号化 他のSDKでも同じメソッドを Swizzlingしているかもしれない 同じメソッドを複数の箇所からSwizzlingすると競合し動作が不安定に
  11. Method Swizzlingの危険性 • Method Swizzlingしていることが明示的でない →細かなSDKに分割しSwizzlingをしていることを明示的にする • 動作の不安定化 →Swizzlingを行う範囲を小さくする •

    Swizzlingを利用しない機能でもSwizzlingされてしまう →Swizzlingするものとしないものでうまく分割する 非常に便利な機能であるが以下の3つの懸念がある Swift Package Managerのマルチモジュール構成を利用すると良さそう
  12. Swift Package ManagerのModule構成を利用する 通常のSDK SPMのモジュール構成を用いたSDK Package.swift Swizzling 可視判定 ログ送信 自動可視判定モジュール

    共通モジュール その他のI/Fモジュール framework Swizzling 可視判定 I/Fその1 データ処理 I/Fその2 メタデータ取得 I/Fその3 ログ送信 データ処理 メタデータ取得 Swizzlingを利用する機能としない機能でModuleを分ける
  13. Swift Package ManagerのModule構成を利用する Swizzlingモジュールの構成 モジュールのStart処理 Swizzling Swizzle結果を返却 • 呼び出し元のアプリでは必ず起動時に同期的に実行する •

    2度Start処理が実行されないようにする Swizzlingを行い結果を返却 事象 結果 対象のメソッドが取得できない場合 .failed 対象のメソッドがoverrideされていない場合 .add 対象のメソッドがoverrideされている場合 .exchanged
  14. Swift Package ManagerのModule構成を利用する • 異なるメソッドに対してSwizzlingを行う場合はモジュールを分けても問題なし • 同じメソッドに対してSwizzlingを行う場合はモジュールを同一のものにする 機能1 viewDidLoad 機能2

    機能 Swizzling対象 モジュール1 モジュール 機能1 viewDidLoad 機能2 モジュール1 scrollViewDidScroll モジュール2 機能1を使う人が無駄にscrollViewDidScroll のSwizzlingをする必要がない 機能1と2でviewDidLoadのSwizzling を行う場合は同一モジュール