応用情報に駆け込んで ネイティブアプリケーション編
はじめに
応用情報本番まであと3日。
午後の部は情報システム開発にしようと思ったら、過去にアプリの処理が出題されてた。
スマホの「ネイティブアプリケーション」だ。設問中の開発者さんが「Webアプリケーション」とどっちにしよっかなって考えていわゆるアプリを選んだらしい。開発はJAVAだそうだ。
少しかじった程度だ。たぶんLollipopかMarshmallow時代だと思う。本を見まねにTwitterのクライアンを作って非同期でくじけてやめた。
Android: Java or Kotlin
iOS: Objective-C or Swift
なんで本日も即席のガイドブックを生成AIたちに作ってもらったよ。
応用情報技術者のためのモバイル並行処理入門
1. 高度な概念と実装
非同期処理 (Asynchronous Processing):
処理の完了を待たずに次の処理を開始する手法
Android: AsyncTask(非推奨), Coroutines, CompletableFuture
iOS: DispatchQueue.async, Combine, async/await (iOS 13+)
バックグラウンド処理 (Background Processing):
UIをブロックせずに実行される処理
Android: WorkManager, IntentService, JobScheduler
iOS: BackgroundTasks framework, URLSession background tasks
並行処理 (Concurrent Processing):
複数のタスクを見かけ上同時に実行する手法
Android: ExecutorService, Coroutines
iOS: DispatchQueue.concurrent, OperationQueue
並列処理 (Parallel Processing):
複数のタスクを実際に同時に実行する手法(マルチコア利用)
Android: ForkJoinPool, Parallel Streams
iOS: DispatchQueue.concurrentPerform, Grand Central Dispatch
スレッドプール (Thread Pool):
再利用可能なスレッドの集合
Android: ExecutorService.newFixedThreadPool()
iOS: NSOperationQueue(内部的にスレッドプールを使用)
コルーチン (Coroutine):
協調的にマルチタスクを実現する軽量スレッド
Android: Kotlin Coroutines
iOS: Swift Concurrency (async/await, iOS 13+)
タスクキュー (Task Queue):
実行待ちのタスクを管理するデータ構造
Android: HandlerThread, WorkManager
iOS: DispatchQueue, OperationQueue
ワーカースレッド (Worker Thread):
バックグラウンドでタスクを処理するスレッド
Android: HandlerThread, WorkManager Worker
iOS: DispatchQueue.global().async { }
メインスレッド (Main Thread) / UIスレッド (UI Thread):
UIの更新や描画を行う主要なスレッド
Android: runOnUiThread { }, Handler(Looper.getMainLooper())
iOS: DispatchQueue.main.async { }
ハンドラー (Handler):
スレッド間通信とメッセージングを管理
Android: Handler, Looper
iOS: 直接的な等価物はないが、DispatchQueue等で類似機能を実現
2. スレッド制御の基本
スレッド作成:
Android: new Thread(() -> { }).start()
iOS: DispatchQueue.global().async { }
スレッド終了:
Android: thread.interrupt()
iOS: (GCDでは明示的な終了は不要)
同期メカニズム:
a) ミューテックス (相互排他):
1つのスレッドのみがリソースにアクセス可能
Android: synchronized(object) { } または ReentrantLock
iOS: NSLock または DispatchQueue(label: "com.example").sync { }
b) セマフォ:
複数のスレッドが同時にリソースにアクセス可能(上限あり)
Android: Semaphore semaphore = new Semaphore(permits)
iOS: DispatchSemaphore(value: permits)
条件変数:
Android: object.wait(); object.notify(); object.notifyAll()
iOS: NSCondition
スレッド間通信:
Android: Handler, LiveData, または EventBus
iOS: NotificationCenter または Combine
デッドロック回避 (タスクチェック):
リソースの獲得順序を一貫させる
タイムアウトの使用: obj.wait(timeout), lock.tryLock(time, unit)
循環依存を検出するツールの使用 (Thread Dump解析)
スレッドスケジューリングと優先度:
Android: thread.setPriority(Thread.MAX_PRIORITY)
iOS: DispatchQueue.global(qos: .userInitiated).async { }
スレッド一時停止:
Android: object.wait() または LockSupport.park()
iOS: condition.wait() または semaphore.wait()
スレッド再開:
Android: object.notify() または LockSupport.unpark(thread)
iOS: condition.signal() または semaphore.signal()
スレッド結合:
Android: thread.join()
iOS: DispatchGroup
アトミック操作:
Android: AtomicInteger, AtomicReference
iOS: OSAtomicIncrement32, OSAtomicDecrement32
ロック/アンロック:
Android: ReentrantLock lock = new ReentrantLock(); lock.lock(); lock.unlock()
iOS: NSLock lock = NSLock(); lock.lock(); lock.unlock()
Wait と Notify の関係:
Wait: スレッドを一時停止し、条件を待つ
Notify: 待機中のスレッドに条件成立を通知
使用例:
synchronized(obj) { while(!condition) { obj.wait(); } }
別スレッドで:
synchronized(obj) { condition = true; obj.notify(); }
3. ベストプラクティスと注意点
UIの更新は必ずメインスレッドで行う
長時間実行される処理はバックグラウンドで実行
スレッドの直接管理よりも、高レベルの抽象化(ExecutorService, GCD等)を使用
デッドロック回避のため、複数のロックの獲得は常に同じ順序で行う
リソースの過剰な占有を避けるため、ロックの保持時間は最小限に
可能な限り、スレッドセーフなデータ構造やイミュータブルオブジェクトを使用
適切なエラーハンドリングと例外処理を実装
パフォーマンスとレスポンシビリティのバランスを考慮
4. プラットフォーム推奨の高レベルAPI
Android:
Kotlin Coroutines: 構造化された非同期プログラミング
RxJava: リアクティブプログラミング
LiveData: ライフサイクル認識型データホルダー
ViewModel: UI関連データの保持と管理
WorkManager: 制約付きバックグラウンドジョブの管理
iOS:
Grand Central Dispatch (GCD): 低レベルの並行処理API
Operation and OperationQueue: 高レベルの並行処理抽象化
Combine: 宣言的な非同期プログラミングフレームワーク
Swift Concurrency: async/await, Actor model (iOS 13+)
注意:
このガイドは基本的な概念と実装方法を示していますが、実際の開発では各プラットフォームの最新のベストプラクティスとAPIを使用することが推奨されます。例えば、長時間実行されるバックグラウンドタスクには、AndroidではWorkManager、iOSではBackgroundTasks frameworkを活用するのが最適です。また、低レベルのスレッド操作よりも、AndroidではCoroutines、iOSではasync/awaitやCombineなどの高レベルな抽象化を使用することが一般的です。これにより、コードの保守性や可読性が大幅に向上します。