[SwiftUI] Get Data from API with Combine framework
Bài đăng này đã không được cập nhật trong 3 năm
Hôm nay mình xin chia sẻ về cách get Data API với Combine framework và SwiftUI.
Combine framework:
Được giới thiệu từ tháng 6/2019. Comebine phục vụ cho việc xử lý sự kiện và dữ liệu bất đồng bộ. Có 3 khái niệm về Combine:
- Publishers: phát sự kiện/dữ liệu đi.
- Subscribers: Nhận sự kiện/dữ liệu (cả error) từ Publishers, Subscribers thông dụng nhất là sink() và assign().
- Operators: gồm các phương thức biến đổi dữ liệu trước khi Publishers.
Vào làm thử nha:
Mình sẽ lấy danh sách posts từ api: https://jsonplaceholder.typicode.com/posts
(Mình lấy api từ https://jsonplaceholder.typicode.com/)
- Đầu tiên mình tạo 1 object PostModel:
struct PostModel: Identifiable, Codable {
let userId: Int
let id: Int
let title: String
let body: String
}
- Tạo 1 PostViewModel có kiểu là ObservableObject để lấy data từ api:
import Combine
class PostViewModel: ObservableObject {
@Published var posts = [PostModel]()
var cancellables = Set<AnyCancellable>()
init() {
getPost()
}
func getPost() {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }
//1. create a publisher
//2. subcribe the publisher on background thread
//3. receive on main thread
//4. tryMap (check the data)
//5. decode data (decode data into PostModel with JSONDecoder())
//6. replace error with nil data
//7. sink (get and put items into app)
//8. store (cancel subscription if needed)
URLSession.shared.dataTaskPublisher(for: url) //1
.subscribe(on: DispatchQueue.global(qos: .background)) //2
.receive(on: DispatchQueue.main) //3
.tryMap { (data, response) -> Data in //4
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return data
}
.decode(type: [PostModel].self, decoder: JSONDecoder()) //5
.replaceError(with: []) //6
.sink { (completion) in //7
print("completion: \(completion)")
} receiveValue: { [weak self] (returnPosts) in
self?.posts = returnPosts
}
.store(in: &cancellables) //8
}
}
- Cuối cùng là hiển thị data lên UI:
// MARK: View
struct Posts: View {
@StateObject var viewModel = PostViewModel()
var body: some View {
List {
ForEach (viewModel.posts) { post in
Text(post.title)
.font(.subheadline)
.fontWeight(.bold)
.foregroundColor(.blue)
Text(post.body)
.font(.subheadline)
.foregroundColor(.gray)
}
}
.padding([.top, .bottom], 10)
}
}
struct Posts_Previews: PreviewProvider {
static var previews: some View {
Posts()
}
}
Ở đây mình có sử dụng các kiểu dữ liệu:
@ObservedObject : là 1 protocol dùng để quan sát sự thay đổi của các @Published properties.
@Published: là 1 property wrapper nằm trong một observable object, khi property có thay đổi thì sẽ trigger cho view update lại.
@StateObject: là 1 instances của observable object, được tạo trong một view cập nhật lại data trên view.
Trong class PostViewModel: ObservableObject
có instance @Published var posts = [PostModel]()
khi instanceposts
này nhận được data mới từ api thì sẽ phát trigger ra ngoài.
Ở ngoài View có @StateObject var viewModel = PostViewModel()
(nó giống như là thể hiện của class PostViewModel: ObservableObject
) khi nó lắng nghe được sự thay đổi thì sẽ update lại data trên view.
Và đây là kết quả:
Hy vọng bài viết này hữu ích! Cảm ơn các bạn!
All rights reserved