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

# はじめに
HTTP通信はいつもAlamofireを使用していて、URLSessionを使ったことがなかったので使ってみました。

# サンプルアプリ
![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
```ContentView.swift
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
```ViewModel.swift
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() {
        URLSession.shared.dataTaskPublisher(for: URL(string: url)!)
            .tryMap { data, _ in return data }
            .decode(type: SearchRepositories.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink { complete in
                print(complete)
            } receiveValue: { [weak self] response in
                guard let self else { return }
                self.repositories = response
            }
            .store(in: &cancellable)
    }
}
```

## Model
```SearchRepositories.swift
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"
    }
}
```

# おわり
普通にURLSessionを使うとごちゃごちゃした感じになりますが、Combineで使うとスッキリしてますね。
これならAlamofireじゃなくて、URLSessionを採用してもいいかなと思いました。