はじめに
iOS エンジニアの 菅(@Nao_RandD | ナオランド)です。
先週、try! Swift Tokyo 2026に現地参加してきました。
今年は「iOS Private Playgrounds」というワークショップが印象的でしたので、その魅力と学びについてご紹介できればと思います。
まずは簡単に「iOS Private Playgrounds」についてご紹介します。
このワークショップは、App Storeの審査ガイドラインを一旦脇に置き、通常公開されていない非公開API(Private API)を意図的に使用することで、UIKitやSwiftUIの内部動作を深く理解することを目指す内容です。
iOS の非公開API(Private API)は、App Store に申請するアプリには使用できません。
Apple の審査ガイドライン(App Review Guidelines 2.5.1)により、公開APIのみが許可されています。
本記事の内容もtry! Swift Tokyo 2026 ワークショップにおけるデバッグ・学習を目的としたもので、プロダクションコードへの適用を推奨するものではありません。
当日は参加者全員が1つの共有リポジトリにアクセスし、ハッカソン形式で各自が見つけたPrivate APIの実装をマージしていく形式で進行しました。
ワークショップでの主な学び
ここからは、私が特に印象に残ったワークショップでの学びを紹介します。
Objective-Cランタイムを用いたAPIの探索と利用
iOSのフレームワークの多くは、依然としてObjective-Cを土台としています。
そのため、実行時にメソッドを解決する「動的ディスパッチ」の性質を利用すれば、公開されていないプロパティやメソッドを探索してアクセスすることができます。
1. lldbによるオブジェクトのダンプ
ワークショップの前半は、lldbコンソールから特定のコマンドを実行し、オブジェクトの内部構造を明らかにするアプローチから始まりました。
主に以下の3つのコマンドで、クラスが持つPrivateなインターフェースを把握していきます。
_ivarDescription: インスタンス変数の一覧を出力する。_shortMethodDescription: クラスやオブジェクトで利用可能なメソッドの一覧を簡潔に出力する。_methodDescription: メソッドの詳細なシグネチャを出力する。
UIViewController の隠しメソッドを調べる場合は、以下のように実行します。
// UIViewControllerを文字列から動的に生成し、Private APIである_shortMethodDescriptionを呼び出す (lldb) po [[NSClassFromString(@"UIViewController") new] _shortMethodDescription]
出力結果には _printHierarchy や _isModalSheet といった、Appleのエンジニアがデバッグや状態管理に使用していると思われるプロパティやメソッドが多数並びます。
対象のオブジェクトが内部でどのようなAPIを持っているか、ここで当たりをつけるのが第一歩です。
ちなみに、iOS フレームワークのヘッダーファイルを閲覧できる便利なサイトも共有されました。
名前で素早く検索したい場合や、型がどこで宣言されているか大まかに把握したい場合に重宝します。
2. KVC(Key-Value Coding)による値の注入
探索して見つけたプロパティ名を利用し、実際に値を読み書きする手段としてKVC(Key-Value Coding)が有効です。
ワークショップの共有リポジトリにあった UIAlertController の例では、公開されていない contentViewController というキーに対して、任意のViewControllerをセットする実装が紹介されていました。
let alertController = UIAlertController( title: "タイトル", message: "Private APIでMyCustomViewControllerを表示するアラート", preferredStyle: .alert ) let videoViewController = makeMyCustomViewController() // KVCを使用して、隠しプロパティに直接ViewControllerをセットする alertController.setValue(videoViewController, forKey: "contentViewController") guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let window = windowScene.windows.first(where: \.isKeyWindow), let presenter = window.rootViewController else { return } presenter.present(alertController, animated: true)
※記事簡略化のための擬似コードです
このアプローチにより、標準のテキストベースのアラート内に、動画やカスタムのレイアウトを持つビューを無理やり埋め込むことができます。
コンパイル時にメソッドやプロパティの存在を厳密にチェックせず、実行時のメッセージパッシング(objc_msgSend)に依存するObjective-Cの言語仕様だからこそできる芸当です。
SwiftにおけるPrivate APIへのアクセス
Objective-Cとは対照的に、Swiftは静的型付けであり、コンパイル時にシンボルの存在と型が厳密にチェックされます。
そのため、動的なAPIの発見や呼び出しはObjective-Cほど容易ではありません。
しかし、Appleは新しい内部APIの一部をSwiftで実装しており、これらにアクセスするための手法も解説されました。
1. @_silgen_name と dlsym を用いたシンボル解決
Swiftの静的ディスパッチの壁を越える手段の一つが、C言語レベルのシンボル解決です。
対象となる関数のマングリング(コンパイラが名前に型情報などを付与して一意にする処理)されたシンボル名が特定できれば、@_silgen_name 属性を使ってSwift側で直接リンクできます。
$s7SwiftUI12VisualEffectPAAE4blur6radius6opaqueQr12CoreGraphics7CGFloatV_SbtF というシンボル名を例に挙げてみます。
これを分解して解読すると、元のSwiftコードのシグネチャが浮かび上がってきます。
$s:Swiftのシンボルであることを示すプレフィックス7SwiftUI:SwiftUIモジュール12VisualEffect:VisualEffectプロトコルPAAE:プロトコルエクステンション4blur:メソッド名 blur6radius 6opaque:引数ラベル radius: と opaque:12CoreGraphics7CGFloatV / Sb:引数の型 CGFloat (Struct/Value) と BoolQr / F:不透明な戻り値型(some VisualEffect など)を返す関数(Function)
この難解な文字列の正体は、以下の隠しAPIの実体です。
extension VisualEffect { func blur(radius: CGFloat, opaque: Bool) -> some VisualEffect }
blur(radius:opaque:) | Apple Developer Documentation
このマングリングされたシンボル名さえ特定できれば、@_silgen_name 属性を使ってSwift側でC言語の関数のように直接リンクさせることが可能です。
// マングル名を用いて、PrivateなSwift関数を宣言して直接呼び出せるようにする @_silgen_name("<MangleName>") func PrivateSwiftFunction()
また、ヘッダを用意せずに実行時にシンボルを解決したい場合は、dlsym を使ってメモリアドレスを取得し、unsafeBitCast でSwiftの関数型にキャストして呼び出すアプローチも紹介されていました。これはコンパイル時のチェックを完全にバイパスし、ランタイムのメモリ上の実体を直接叩く強烈な手法です。
2. .swiftinterface の編集
もう一つのアプローチは、モジュールの公開インターフェースを管理している .swiftinterface ファイルを活用する手法です。
Xcode内のSDKパス(例: SwiftUICore.framework 内)をたどり、このインターフェースファイルに記載されているPrivateな型やメソッドの記述を確認・抽出します。これを利用すると、SwiftUIの Material._GlassVariant のような通常は隠蔽されている内部構造を、Swiftのコード上で型安全に扱うことができるようになります。
ワークショップでも、この手法を使ってSwiftUIの新しい視覚効果(Glass Effectなど)をカスタムビューに適用する実験が行われていました。
Appleのユーモアに触れる
ワークショップ中はホストからPrivate API周辺の小話も紹介され、中でも興味深かったものをこちらでも紹介します。
UIResponder のメソッド一覧をダンプした際、以下のような異常に長い名前のメソッドが存在します。
- (void)attentionClassDumpUser:(id)arg1 yesItsUsAgain:(id)arg2 althoughSwizzlingAndOverridingPrivateMethodsIsFun:(id)arg3 itWasntMuchFunWhenYourAppStoppedWorking:(id)arg4 pleaseRefrainFromDoingSoInTheFutureOkayThanksBye:(id)arg5;
和訳すると、以下のようになります。
クラスダンプしているユーザーへ そう、また私たち(Apple)です。
SwizzleやプライベートメソッドのOverrideは楽しいかもしれませんが、アプリが動かなくなったら全然楽しくありませんよね。
だから今後は控えてくださいね、ありがとう、さようなら
堅牢に見えるOSの裏側に、こうした血の通ったユーモアあるコードが存在しているのを見つけられるのは、Private APIを掘り下げる面白さのひとつだなと感じました。(ちなみに呼び出しても何も起きません)
ワークショップで実装したもの
ワークショップの後半では、見つけたPrivate APIを使って実際に手を動かす時間が設けられました。
そこで自身は、「キーボードの背景を透過させ、背面にカメラ映像をリアルタイムで表示する」という実装を試してみました。
使用したPrivate API
キーボードの透過には、UIKeyboardAppearance の非公開の列挙子(enum case)を利用しています。
通常の開発では .light や .dark などを指定しますが、ここに直接 rawValue: 12 を渡すことで、キーボード全体が透過する状態を作り出せます。
// 通常は .light や .dark を指定するところに、非公開の値を強制的に渡す textField.keyboardAppearance = UIKeyboardAppearance(rawValue: 12)!
映像の表示アプローチ
キーボードを透過させた後、その背面に映像を配置します。
具体的には、キーボードのレイアウトガイド(keyboardLayoutGuide)に合わせて背面に土台となる UIView (コード内の inputBackdrop)を配置し、そこに AVCaptureSession を使って取得したデバイスの背面カメラ映像を AVCaptureVideoPreviewLayer として流し込んでいます。
カメラ映像をレイヤーに追加する主要な処理は以下の通りです。
private func startCaptureSession() { let session = AVCaptureSession() // 背面カメラの取得 guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back), let input = try? AVCaptureDeviceInput(device: device) else { return } if session.canAddInput(input) { session.addInput(input) } // カメラのプレビューレイヤーを作成し、背景用のViewに追加 let layer = AVCaptureVideoPreviewLayer(session: session) layer.videoGravity = .resizeAspectFill inputBackdrop.layer.addSublayer(layer) self.mediaLayer = layer self.captureSession = session // メインスレッドをブロックしないように非同期でセッションを開始 Task.detached(priority: .userInitiated) { session.startRunning() } }
また、映像の上部がUIと自然に馴染むように、CAGradientLayer を使って上部が徐々にフェードアウトするマスク処理をかけています。
今後の開発に活きること
今回のワークショップを通じて得られた最大の収穫は、単なる「裏技」の習得にとどまらず、OSや言語の根幹に対する理解が一段深まったことです。
Interoperability Requestの質向上
Private APIを探索することは、「Appleが内部でどのようにUIや機能を実装しているか」を知る強力な手がかりになります。近年、EUのDMA(デジタル市場法)などの影響もあり、Appleに対してOSの機能開放を求める「Interoperability Request(相互運用性のリクエスト)」という公式プロセスが存在します。
私たちがアプリを開発する中で「こういうAPIが欲しい」と考えた際、すでにOS内部でどのような形でPrivate APIとして存在しているのかを把握しておくことで、Appleへ提出する要望の具体性と説得力を格段に上げることができます。
言語仕様の境界とコンパイル・ランタイムの理解
また、iOSを支えるObjective-CとSwiftという2つの言語がどのように処理されているか、その差異をコードレベルで実感できたことも重要です。
実行時にメソッドを検索し、KVCなどを通じて強制的に値を注入できるObjective-Cの強力な動的ディスパッチ。
対して、コンパイル時にマングリングが行われるため実行時の動的なハックは難しいものの、シンボル名を特定すれば @_silgen_name や dlsym で解決でき、公開インターフェースが .swiftinterface で厳密に管理されているSwiftの静的型付けの世界。
これらを実際に手を動かして行き来したことで、コンパイルからリンク、そしてランタイム実行に至るまでに、言語がコードをどう解釈して処理しているかというアーキテクチャの深い理解に繋がりました。
まとめ
「iOS Private Playgrounds」は、本番環境へのリリースという制約を外し、Private APIからiOSの深層を技術的興味から探索する非常に有意義な時間でした。
APIの動的な性質や、言語仕様の境界を越えるアクセス手法を知ることは、フレームワークの設計思想や制約を理解し、日常の開発におけるデバッグやパフォーマンスチューニングの能力を底上げすることに直結します。
今回リポジトリに集まったハックや実験コードをベースに、今後もローカル環境でOSの挙動を深く検証していきたいと思います。
前年以上に深い学びが詰まったワークショップでした!来年もまた新しい発見ができることを楽しみにしています。
同じくヤプリでiOSエンジニアをしている白数 (@cychow_app)から「Enhance your apps with the Foundation Models (Foundation Modelsでアプリを強化する)」のワークショップに関する紹介記事もありますので、ご興味ある方はぜひ合わせて読んでみて下さい。
ヤプリではエンジニアのカンファレンス参加を積極的に後押ししています。
この記事やtry! Swift Tokyoで気になったことについてもっと語りたい方、ヤプリの開発にご興味ある方はぜひカジュアル面談にお越しください。