ネイティブアプリにも宣言型UIとかなんかいろいろ入ってきたので、「いい加減そろそろ覚えないとな~~」ってなってる人は少なくないと思ってるんですけれど、一足先に一通り触ったのでどんな感じで学べばいいのかなという参考になればという感じの記事です。
宣言型UI
WebにjQueryがもたらされたとき、だれでも簡単に直接DOMを触ってViewを弄れるようになった結果、アプリの大規模化・保守の長期化に伴って手に負えなくなったので、それを解決するためにReact辺りが宣言型UIの思想を生み出した…というざっくりした認識を持ってるんですけれど、合ってるかは知りません。
とまれ、ネイティブアプリにも数周遅れではありますが宣言型UIの流れがきています。
SwiftUI
SwiftUIの現在の最新バージョンは3です。
どんどん便利になるのはいいことなんですけれど、サポートするiOSのバージョン指定によっては使えない機能とかがあります…。
これは公式のコードなのですけれど、
struct Content: View {
@State var model = Themes.listModel
var body: some View {
List(model.items, action: model.selecteItem) { item in
Image(item.image)
VStack(alignment: .leading) {
Text(item.title)
Text(item.subtitle)
.color(.gray)
}
}
}
}
- Opaque Result Type
- Property Wrapper
- Dynamic Member Lookup
のような最近の言語仕様の追加を駆使して実現されてるため、UIを作るだけならスッキリなんですけれど、挙動についてもスッキリと理解するにはこうした言語仕様の変更も追いかける必要があります。
それぞれの言語仕様について、プロポーザルを眺めながら簡単なコードで動きを見てみたり、$model.items
とか_model.items.wrappedValue
とかの型情報をインスペクションで眺めたりすると掴めてくるのかなと思います。
Jetpack Compose
ComposeはViewの組み立てが関数で構成されているというのが面白ポイントですね。
@Composable
fun JetpackCompose() {
Card {
var expanded by remember { mutableStateOf(false) }
Column(Modifier.clickable { expanded = !expanded } ) {
Image(painterResource(R.drawable.jetpack_compose))
AnimatedVisibility(expanded) {
Text(
text = "Jetpack Compose",
style = MaterialTheme.typography.h2,
)
}
}
}
}
最初はby remember
やmutableStateOf()
が見慣れないかもしれませんが、翻訳済みの公式ドキュメントがかなり整備されているので、とっつきやすいと思います。
状態ホイスティングのようなCompose構築のための良いプラクティスも書いてありますし、ComposeはAndroid SDKとは別のライブラリとして提供されているので、バージョンによる分断もめいびーありません。
Composeがバージョンごとに特定のKotlinバージョンをロックインしている点と、Material Designも並行してアップデートしている辺りはちょっと困りポイントかもしれません。
リアクティブプログラミングっぽいの
初期の学習コストこそ高いものの、Reactive Extensionsを一つ覚えれば、他の言語でも知識を応用できる、みたいな話がありましたよね…。
Combine
これはApple的にはリアクティブプログラミングではなく、非同期イベントを処理する仕組みみたいです。個人的な感想では、どう見てもRxでは…という感じです。
そういうわけで、Rxを理解しているとほぼほぼ「そーなのねー」で終わると思います。あとはSwift固有の問題として、オペレーターを挟むと型がどんどん入り組んでいくので、eraseAnyPublisher()
による型消去を適当に挟む感じというのだけ覚えればよさそう。
CombineはSwiftUIの裏側ではいろいろ出てくるので、その辺りの理解度を高めるために習得するのはありなのかもですけれど、あんまり直接いじる印象はないと思います。たぶん。
Flow
Flowも見た目はRxこそ似ていますが、Coroutineの一種という点に違いがあります。
RxやChannelは不慣れだとあっさりリソースをリークさせてしまうのですが、Flowは基本的にColdなストリームを扱いつつ、HotなStateFlowに変換するにはCoroutineScope内でやってね~みたいな感じがあり、ある程度安全性も担保している印象があります。
こちらも結局非同期イベントによる状態変化をComposeへ流す用途に使うのがメインだと思っていて、直接いじる印象は少ないです。
とはいえ、Hot/Coldについての最小限の知識がないと、サーバーに何度もアクセスしてしまう問題のあるコードが生まれるリスクはあるので、公式ドキュメントを眺めるくらいはしてもいいかも…。
非同期処理
C#にasync/awaitが出てから10年くらいでしょうか。ようやくどの言語でも同じような書き方ができるようになりました。
Swift Concurrency
Swiftで非同期処理を書こうとすると、completion
クロージャがネストしまくり、非常に追い辛いコードになりがちでした。
待ちに待ったSwift Concurrencyの登場によって、とても簡潔に書けるようになったと思います。
func fetchImage(request: URLRequest) async throws -> UIImage {
let (data, response) = try await URLSession.shared.data(for: request)
guard
let image = UIImage(data: data)
else {
throw ImageDecodeError
}
return image
}
Swiftは2.0でキャッチ例外を取り入れることを選択したので、async
/await
だけではなく、async throws
/try await
があるのが特色ですね。
ほかにもActor
という非同期処理におけるRace Conditionの問題解決に大きく貢献する仕組みも入っています。
この辺りはWWDCのビデオを見るのが手っ取り早いと思います。非常に便利なんですけれど、SDKやエコシステムが対応していくようになるのはまだこれからかなという印象です。