【SwiftUI】AlamofireをCombineで使ってみた

# はじめに
https://qiita.com/SNQ-2001/items/7dba99cc69d4b85e108b

以前はCombineでURLSessionを使って通信をしてみました。
今回はCombineでAlamofireを使って通信してみます。

# サンプルアプリ
![Simulator Screen Recording - iPhone 14 Pro - 2022-11-21 at 20.36.34.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/94d5d19d-4305-4d0b-452d-b37cb2f77ddc.gif)

# 実装
### View
```swift:ContentView
import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = ViewModel()
    var body: some View {
        List(viewModel.repositories.items) { item in
            Label {
                Text(item.name)
            } icon: {
                avatar(item: item)
            }
        }
        .listStyle(.grouped)
    }
    func avatar(item: Item) -> some View {
        AsyncImage(url: URL(string: item.owner.avatarURL), transaction: .init(animation: .default)) { result in
            switch result {
            case .success(let success):
                success
                    .resizable()
                    .scaledToFit()
                    .clipShape(Circle())
            default:
                ProgressView()
            }
        }
    }
}
```

### ViewModel
```swift:ViewModel
import Alamofire
import Combine
import Foundation

final class ViewModel: ObservableObject {
    @Published var repositories: SearchRepositories = .init(items: [])

    private let url = "https://api.github.com/search/repositories?q=swift"

    private var cancellable = Set<AnyCancellable>()

    init() {
        fetchSearchRepositories()
    }

    private func fetchSearchRepositories() {
        AF.request(url, method: .get).publishDecodable(type: SearchRepositories.self)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] response in
                guard let self else { return }
                switch response.result {
                case .success(let response):
                    self.repositories = response
                case .failure:
                    self.repositories = .init(items: [])
                }
            }
            .store(in: &cancellable)
    }
}
```
### Model
```swift:SearchRepositories
import Foundation

// MARK: - SearchRepositories
struct SearchRepositories: Codable {
    var items: [Item]

    enum CodingKeys: String, CodingKey {
        case items
    }
}

// MARK: - Item
struct Item: Codable, Identifiable {
    let id = UUID()
    let name: String
    let owner: Owner
    let htmlURL: String
    let itemDescription: String?
    let languagesURL: String
    let stargazersCount: Int
    let language: String?
    let forksCount: Int
    let openIssuesCount: Int

    enum CodingKeys: String, CodingKey {
        case name
        case owner
        case htmlURL = "html_url"
        case itemDescription = "description"
        case languagesURL = "languages_url"
        case stargazersCount = "stargazers_count"
        case language
        case forksCount = "forks_count"
        case openIssuesCount = "open_issues_count"
    }
}

// MARK: - Owner
struct Owner: Codable {
    let login: String
    let avatarURL: String
    let url: String
    let htmlURL: String

    enum CodingKeys: String, CodingKey {
        case login
        case avatarURL = "avatar_url"
        case url
        case htmlURL = "html_url"
    }
}
```

# おわり
Combineを使うとコードがスッキリするので結構好きです