[SwiftUI] OSSニュースアプリを作った
7月を通してiOS向けのニュースアプリを作りました。
OSSなのでこれからSwiftUIを学びたい人の参考になればいいなと思います。
また、API部分は別のパッケージとして切り出しているのでそちらもみていただければと。
どちらもスターくれたら嬉しいです! ⭐
モチベーション
今回の開発モチベーションはiOS15から新しく使えるようになった新たな機能(Searchable, Refreshable)やSwift5.5に搭載されているasync/await
のような言語仕様を試すことです。
そのためアプリ自体はXcode 13以上、iOS15以上(現状どちらもベータ版しかありません)が必要になります。
アプリについて
このアプリはnewsapi.orgのAPIを利用し、世界のトップニュースや特定のワードで検索ができるアプリです。
3タブ構成で必要最低限の機能だけを実装しました。
上記であげた新たな機能を試すという目的を達成できたので個人的には満足しています。
Headline | Search | Publishers |
---|---|---|
[NewsAPI]非同期処理について
アプリ作成に伴い、newsapi.orgのAPIをラップしてiOSライブラリとして公開しました。
内部実装のままでもよかったですが、
- newsapiのSwiftライブラリで非同期処理に対応したものがなかった
- iOSアプリを作ってみたい人に利用してもらいたい
- OSS活動の一環としてやってみたかった
の理由からライブラリとして切り出しました。
ここからは、Swift5.5の最も大きな追加された言語機能であるasync/await
構文を利用したAPIについて軽く紹介します。
下記は、NewsAPIを利用して、ヘッドラインニュースを取得するサンプルコードです。
func getHeadlines() async -> [NewsArticle] {
do {
let articles = try await NewsAPI(apiKey: "YOUR_API_KEY").getTopHeadlines()
return articles
} catch {
// do something
}
}
上記のコードを読むと従来のクロージャを利用したりする代わりにasync
というキーワードがメソッドに追加され、内部にawait
というキーワードを利用することがわかります。
このgetHeadlines
というメソッドは非同期関数として定義されているので直前にawait
キーワードが必要です。
await
は、記述直後の処理を保留するので、より直感的に記述することができるようになりました。
コード行数やネストの深さを減少させ可読性が向上するだけでなく、複数の非同期処理を同時に処理することができるようになるなどより表現できる幅の広がりを感じます。
[NewsUI]Architectureについて
ここからはアプリ本体についての説明です。
アプリの構成についてわかりやすく図にまとめたのが下記の画像です。
[NewsAPI]非同期処理について
でも触れた通り、NewsAPI(赤色部分)は外部ライブラリとして切り出しました。
NewsAPI
に依存するのは内部のNewsClient
のみで、さらに各Featureがそれぞれ依存する形になっています。
画像の黄色部分のFeatureは全てPackageとして管理されており、Package.json
は以下の構成になっています。
// https://github.com/mtfum/NewsUI/blob/main/Package.swift
import PackageDescription
let package = Package(
name: "NewsUI",
platforms: [
.iOS("15.0")
],
products: [
.library(name: "AppFeature", targets: ["AppFeature"]),
.library(name: "SearchFeature", targets: ["SearchFeature"]),
.library(name: "HeadlinesFeature", targets: ["HeadlinesFeature"]),
.library(name: "SourcesFeature", targets: ["SourcesFeature"])
],
dependencies: [
.package(url: "https://github.com/mtfum/NewsAPI.git", from: "0.1.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "0.0.1"),
],
targets: [
.target(name: "AppComponent", dependencies: ["NewsAPI"]),
.target(name: "AppFeature", dependencies: ["SearchFeature", "HeadlinesFeature", "SourcesFeature"]),
.target(name: "NewsClient", dependencies: ["NewsAPI"]),
.target(name: "SearchFeature", dependencies: ["AppComponent", "NewsClient", .product(name: "OrderedCollections", package: "swift-collections")]),
.target(name: "HeadlinesFeature", dependencies: ["AppComponent", "NewsClient"]),
.target(name: "SourcesFeature", dependencies: ["AppComponent", "NewsClient"])
]
)
アプリ内部は複数のモジュールで分けられており、各機能(今回の場合はタブごとの画面)で分割し、それぞれが相互参照できないようになっています。
マルチモジュール化はメリットとして、依存関係の明確化やコンパイルの最適化など挙げられますが今回のような小さなアプリではあまり旨味は得られないでしょう。
蛇足ですが、このアイデアをさらに突き詰めることでHyper-modularization
と呼び、86個のモジュールで開発をしているのがisowordsです。
また、各FeatureはAppFeature
に統合され、アプリ本体はAppFeature
を参照するだけのシンプルな構成にすることができました。
import SwiftUI
import AppFeature
@main
struct NewsUIApp: App {
var body: some Scene {
WindowGroup {
AppFeatureView()
}
}
}
おわりに
簡単ではありますが、NewsUIとNewsAPIについて工夫した点について紹介しました。
とりあえず一区切りついたので公開することができ、嬉しく思います。
もしも参考になったらスターください!
お読み頂きありがとうございました。
Discussion