Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on October 12, 2020

RxSwift vs. UIKit – Fetching Data from API

RxSwift

Contents

  • Chuẩn bị
  • 1. Fetching API
  • 2. Setup
  • 3. Request
  • 4. Response
  • 5. share() vs. share(replay: 1)
  • 6. Parse Data
    • 6.1. Object Model
    • 6.2. Handle Response
    • 6.3. Decode JSON
  • 7. Subscribe & Update UI
    • 7.1. Subscribe
    • 7.2. Update UI
  • Tạm kết

Chào bạn đến với Fx Studio. Chúng ta sẽ tiếp tục series RxSwift vs. UIKit. Chủ đề bài viết này là Fetching Data from API.

Về bài viết, bạn chỉ cần biết về cách sử dụng cơ bản RxSwift trong UIKit là đã ổn rồi. Nếu bạn chưa biết hoặc quên, thì có thể bắt đầu đọc từ link này:

    • RxSwift vs. UIKit – Hello ViewController

Còn mọi thứ đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

    • Xcode 11
    • Swift 5.x

Chúng ta vẫn sử dụng project ở bài trước. Ngoài ra, dành cho các bạn chưa biết checkout ở đâu, thì hãy tham khảo link sau:

    • Link: checkout
    • Thư mục: /Examples/BasicRxSwift

1. Fetching API

Chúng ta đã đi qua lần lượt các vấn đề trong UIKit khi sử dụng RxSwift như sau:

    1. Hiển thị dữ liệu
    2. Sự kiện tương tác trên một màn hình
    3. Tương tác giữa nhiều màn hình
    4. Viết model sử dụng với RxSwift

Tiếp theo, bạn cần phải tương tác với nguồn dữ liệu không ở trong ứng dụng. Và trong giới lập trình, tương tác dữ liệu với API hay với service, thì là một trong những điều bắt buộc. Có như vậy, ứng dụng của bạn mới sinh động được. Quá trình này thì bao gồm các bước lần lượt như sau:

    1. Tạo request tới endpoint của API/Server
    2. Tiến hành kết nối
    3. Nhận response
    4. Phần tích data nhận được
    5. Tiến hành cập nhật lại giao diện ứng dụng

Và công việc này thì người ta hay gọi là:

Fetching API

2. Setup

Trước khi bắt đầu tạo request, chúng ta cần chuẩn bị thêm một số thứ cần thiết thêm nữa.

Về giao diện, màn hình của chúng ta thì khá là đơn giản. Nó có 1 TableView với các cell để hiển thị item lấy được từ API. Tuỳ thuộc bạn sử dụng API nào, còn mình sẽ sử dụng API của iTunes để lấy các bài hát mới nhất.

    • Link: https://rss.itunes.apple.com/api/v1/us/itunes-music/new-music/all/100/explicit.json

Về thư viện sẽ sử dụng là:

    • URLSession
    • RxSwift & RxCocoa

Và kiến thức, cũng không cần đỏi hỏi quá nhiều kiến thức với RxSwift. Chủ yếu bạn cần nắm là sự biến đổi qua lại giữa các dữ liệu nhận được (các operators). Cũng không đưa ra một giải pháp hoàn hảo cho bạn với việc tương tác API.

Bạn tiến hành mở file MusicListViewController. Trong này, mình có cấu hình cơ bản cho 1 UITableView. Bạn hãy chú ý tới function loadAPI . Tại đó, ta sẽ thực hiện công việc chính của mình. Còn về code của ViewController bắt đầu thì sẽ như thế này.

import UIKit
import RxSwift
import RxCocoa

class MusicListViewController: UIViewController {
    
    // MARK: - Outlets
    @IBOutlet weak var tableView: UITableView!
  
  	// MARK: - Properties
    private let urlMusic = "https://rss.itunes.apple.com/api/v1/us/itunes-music/new-music/all/100/explicit.json"
    
    // MARK: - Life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        configUI()
        loadAPI()
    }
    
    // MARK: - Private Methods
    private func configUI() {
        title = "New Music"
        
        let nib = UINib(nibName: "MusicCell", bundle: .main)
        tableView.register(nib, forCellReuseIdentifier: "cell")
        
        tableView.delegate = self
        tableView.dataSource = self
    }
    
    private func loadAPI() {
        
    }
    
}

extension MusicListViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        20
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

Chúng ta tạm thời hard code cho TableView như vậy. Giúp bạn khi build & run thì cũng thấy được giao diện ban đầu của màn hình ra sao.

3. Request

Bây giờ, chính thức tới bước đầu tiên bạn cần làm. Đó là tạo request. Để lấy được dữ liệu, bạn phải tạo được request tới server. Nhưng hiện tại, bạn đang có là 1 link url để truy cập tới. Và bạn cũng biết được là chúng ta cần tạo ra được đối tượng URLRequest. Vì vậy, công việc  chúng ta sẽ như sau:

URL String  >  URL  >  URLRequest

Okay, bắt đầu thôi. Tại hàm loadAPI() chúng ta thêm dòng code đầu tiên như sau:

let observable = Observable<String>.of(urlMusic)

Bạn chỉ cần tạo ra 1 Observable với kiểu là String , với toán tử of thì dữ liệu cung cấp chính là urlMusic ở trên. Tiếp tục nào!

let observable = Observable<String>.of(urlMusic)
            .map { urlString -> URL in
                return URL(string: urlString)!
            }

Đây là bước biến đổi thứ nhất , biết String thành URL với việc dùng toán tử map. Cái này được handle bằng 1 closure và chỉ cần return đúng về kiểu dữ liệu mà mình mong muốn, là URL. Tiếp tục tiếp nào!

let observable = Observable<String>.of(urlMusic)
            .map { urlString -> URL in
                return URL(string: urlString)!
            }
            .map { url -> URLRequest in
                return URLRequest(url: url)
            }

Áp dụng tiếp toán tử map lần nữa, thì bạn có lần biến đổi thứ hai. Lúc này, sẽ thành là URLRequest. Quá EZ phải không nào, từ 2 dòng lệnh chúng ta đã có được đối tượng cần thiết rồi.

4. Response

Tiếp sau việc tạo được request, là việc nhận response. Bạn tiếp tục với đoạn code trên và thêm toán tử flatMap này vào.

.flatMap { request -> Observable<(response: HTTPURLResponse, data: Data)> in
                return URLSession.shared.rx.response(request: request)
            }

Giải thích như sau:

Với flatMap:

  • Không những giúp cho việc biến đổi dữ liệu mà còn biến đổi luôn của Observable này thành Observable khác
  • Nó phù hợp với cách tương tác bất đồng bộ (vì 2 bước biến đổi trên vẫn toàn là đồng bộ)
  • Nó sẽ chờ phản hồi đầy đủ từ server trả về. Sau đó sẽ thực thi các đoạn code tiếp theo

Với URLSession:

  • Bạn đang dùng các thuộc tính mới được thêm vào từ RxCocoa
  • share.rx sẽ gọi toán tử response với tham số là request từ trên
  • Kết quả trả về là 1 Observable, với kiểu giá trị của phần tử bao gồm HTTPURLResponse và Data cho body

Cuối cùng bạn thêm dòng code này vào:

.share(replay: 1)

Để cho phép có nhiều subscriptions tới Observable đó và kết quả sẽ được lưu lại ở bộ đệm. Khi đó đảm bảo sẽ có được dữ liệu cho các Subscriber.

5. share() vs. share(replay: 1)

Update thêm kiến thức mới nha. À, nó cũng có ích trong phạm vi bài này đó.

Đầu tiên, khi bạn sử dụng URLSession.rx.response(request:), tức là bạn gởi yêu cầu tới máy chủ. Khi bạn nhận được phản hồi trở lại. Thì Observable sẽ emit ra duy nhất một phần tử và kết thúc.

Mọi thứ sẽ không có vấn đề gì. Và nếu bạn tiếp tục subscribe lần thứ 2, lần 2 … lần n.

Do đó, mọi công việc sẽ bắt đầu chạy lại từ đầu.

Để tránh việc làm tốn tài nguyên và công sức như thế này. Thì sử dụng toán tử share(replay:scope). Toán tử sẽ giữ lại phần tử cuối cùng trong bộ đệm. Cứ như vậy, các Subscriber tiếp theo khi đăng kí tới, thì sẽ nhận được dữ liệu ngay lập tức và không cần phải thực hiện lại đám lệnh kết nối tới API ở trên.

Về scopes thì bạn có 2 lựa chọn

  • .forever bộ đệm sẽ lưu lại mãi mãi. Chờ người đăng ký mới.
  • .whileConnected bộ đệm sẽ giữ lại cho đến khi không còn người nào đăng kí tới và loại bỏ bộ đệm ngay sau đó. Các đăng ký tiếp theo thì sẽ load lại từ đầu.

Tuỳ thuộc vào ý đồ bạn muốn sử dụng việc load API đó ra sao. Mà có cách sử dụng share  phù hợp.

OKE, hết thời gian phụ đạo nha!

6. Parse Data

6.1. Object Model

Bắt đầu cho công việc Parse Data thì bạn cần có một Model cho dữ liệu cần phân tích. Ta tạo một file và đặt tên là Music.swift và dựa vào dữ liệu json của link trên ta khai báo các thuộc tính như sau:

final class Music: Codable {
    var artistName: String
    var id: String
    var releaseDate: String
    var name: String
    var copyright: String
    var artworkUrl100: String
}

Class này là dữ liệu của các item để hiện thị lên Tableview cell.

6.2. Handle Response

Giờ tới phần, phân tích dữ liệu nhận được từ server. Bạn cũng biết việc nhận response không phải lúc nào cũng thành công. Do đó, trước tiên chúng ta phải lọc đi các trường hợp không thành công khi tương tác với API.

observable
            .filter { response, _ -> Bool in
                return 200..<300 ~= response.statusCode
            }

Bạn hãy thêm một dòng code mới và bắt đầu bằng toán tử filter. Các statusCode từ 200~299 là thành công. Về các trường hợp lỗi thì chúng ta hãy phân tích tại một bài khác.

Tiếp tục, là phần parse data chính. Ta có đoạn code tiếp như sau với toán tử map.

.map { _, data -> [Music] in
                
            }

Trong đó, map là toán tử huyền thoại dùng để biến đổi kiểu dữ liệu. Trong bài toán này, chúng ta biến đổi (HTTPURLResponse, Data) thành Array Music.

6.3. Decode JSON

Để giúp cho khi biến đổi một cách nhanh chóng. Thì bạn hãy xem lại cấu trúc JSON của API là như thế nào. Rồi từ đó, chúng ta sẽ đưa ra cấu trúc dữ liệu phù hợp. Bạn mở file Music.swift và thêm khai báo này vào.

final class Music: Codable {
    var artistName: String
    var id: String
    var releaseDate: String
    var name: String
    var copyright: String
    var artworkUrl100: String
}

struct MusicResults: Codable {
  var results: [Music]
}

struct FeedResults: Codable {
  var feed: MusicResults

Ta có:

  • FeedResults là đại diện cho cấu trúc lớn nhất, nó có 1 key là feed
  • MusicResults là kiểu dữ liệu cho key results , nó nằm trong feed. Nếu bạn muốn parse gì thêm trong cấu trúc này thì hãy thêm vào
  • Music là đại diện kiểu dữ liệu cho từng item của mãng results

Tất cả đều kế thừa protocol Codable, nếu bạn chưa biết chúng là gì. Thì nó đơn giản giúp cho bạn chuyển đổi kiểu dữ liệu một cách dễ dàng, thông qua các đối tượng Encoder hay Decoder.

Codable = Encoder + Decoder

    • Tham khảo : Encoding và Decoding trong Swift

Quay lại file MusicListViewController, chúng ta hoàn thành công việc phân tích dữ liệu từ server trả về.

.map { _, data -> [Music] in
                let decoder = JSONDecoder()
                let results = try? decoder.decode(FeedResults.self, from: data)
                return results?.feed.results ?? []
            }

Ta đã biết dữ liệu nhận được từ server là JSON, nên sẽ dùng JSONDecoder để biến đổi data thành FeedResults. Từ đó, chúng ta sẽ return về Array Music là dữ liệu cho Tableview.

Cuối cùng, nếu trường hợp phân tích JSON mà bị lỗi thì chúng ta sẽ lọc tiếp lần nữa. Để đảm bảo tính toàn vẹn của chương trình.

.filter { objects in
                return !objects.isEmpty
            }

7. Subscribe & Update UI

7.1. Subscribe

Công việc cuối cùng, chính là subscribe tới Observable. Vì khi có kết nối từ Subscriber tới Observable kia, việc kết nối với API mới hoạt động và bạn sẽ nhận được dữ liệu từ server. Ta tiếp tục với subscribe nào, bạn thêm đoạn code này vào tiếp đoạn code trên.

.subscribe(onNext: { musics in
                DispatchQueue.main.async {
                    self.musics = musics
                    self.tableView.reloadData()
                }
            })
            .disposed(by: bag)

Trong closure onNext là phần code bạn sẽ handle. Tại đó, bạn sẽ xử lý các công việc khi nhận được kết quả của quá trình phân tích JSON. Bao gồm:

  • Lưu trữ lại dữ liệu
  • Tiến hành update UI

Nếu bạn muốn làm thêm công việc khác, như: lưu vào bộ đệm, gọi thêm api khác …. thì cũng sẽ tiến hành gọi tại đó luôn.

7.2. Update UI

Với dữ liệu nhận được là một mãng Music và reload Tableview, nên cần phải thực hiện chúng ở Main Thread. Vì, công việc gọi API luôn chạy ở thread khác. Nếu không update UI tại Main Thread thì sẽ crash chương trình.

Nhớ khai báo thêm túi rác quốc dân và array dữ liệu cho Tableview  tại ViewController nha.

    private let bag = DisposeBag()

    private var musics: [Music] = []

Cập nhật lại dữ liệu cho các protocol của TableView nào.

extension MusicListViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        musics.count
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MusicCell
        
        let item = musics[indexPath.row]
        cell.nameLabel.text = item.name
        cell.artistNameLabel.text = item.artistName
        cell.thumbnailImageView.kf.setImage(with: URL(string: item.artworkUrl100)!)
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

Bạn hãy build lại ứng dụng và tận hưởng kết quả nào! Và kết quả của mình sẽ như thế này.

OKAY! Tới đây là kết thúc bài viết này. Tuỳ thuộc vào yêu cầu mà bạn hãy sử dụng chúng nó một cách hợp lý nha! Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.

Tạm kết

  • Với RxSwift thì chúng ta có thể tương tác với API một cách đơn giản.
  • Rất nhanh và hiệu quả.

Bạn có thể xem lại toàn bộ code như sau:

private func loadAPI() {
    		// create Observable
        let response = Observable<String>.of(urlMusic)
            .map { urlString -> URL in
                return URL(string: urlString)!
            }
            .map { url -> URLRequest in
                return URLRequest(url: url)
            }
            .flatMap { request -> Observable<(response: HTTPURLResponse, data: Data)> in
                return URLSession.shared.rx.response(request: request)
            }
            .share(replay: 1)
        
        // parse data
        response
            .filter { response, _ -> Bool in
                return 200..<300 ~= response.statusCode
            }
            .map { _, data -> [Music] in
                let decoder = JSONDecoder()
                let results = try? decoder.decode(FeedResults.self, from: data)
                return results?.feed.results ?? []
            }
            .filter { objects in
                return !objects.isEmpty
            }
            // update UI
            .subscribe(onNext: { musics in
                DispatchQueue.main.async {
                    self.musics = musics
                    self.tableView.reloadData()
                }
            })
            .disposed(by: bag)
        
    }

 

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares18

Related Posts:

  • RxSwift vs. UIKit – Tạo Model với Custom Observable
    RxSwift vs. UIKit – Tạo Model với Custom Observable
  • RxSwift – Phần 2 : UIKit
    RxSwift – Phần 2 : UIKit
  • RxSwift vs. UIKit - Hello ViewController
    RxSwift vs. UIKit - Hello ViewController
  • Fetching Data to List from API - SwiftUI Notes #44
    Fetching Data to List from API - SwiftUI Notes #44
Tags: rxswift
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

3 comments

  • Đăng Tuấn has written: May 29, 2021 at 4:31 am Reply

    Mình có 2 API để lấy dữ liệu, 1 để lấy thông tin, 1 để lấy ảnh. Thì làm sao để lấy được cả 2 và truyền cho 1 model để hiển thị ạ?

    • chuotfx has written: June 1, 2021 at 2:16 pm Reply

      Bạn xem và tham khảo ở đây này. Về bản chất thì cũng giống như bài toán bạn đưa ra. https://fxstudio.dev/rxswift-vs-uikit-networking/#7_Connect_multi_APIs

  • Đăng Tuấn has written: May 31, 2021 at 7:58 am Reply

    Mình có 2 API, một để gọi data, một để gọi ảnh thì làm thế nào ạ?

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • RxSwift vs. UIKit - Hello ViewController
    RxSwift vs. UIKit - Hello ViewController
  • async/await to Fetch REST API - Swift 5.5
    async/await to Fetch REST API - Swift 5.5
  • Fetching Data to List from API - SwiftUI Notes #44
    Fetching Data to List from API - SwiftUI Notes #44
  • RxSwift vs. UIKit – Tương tác giữa các ViewController
    RxSwift vs. UIKit – Tương tác giữa các ViewController
  • Tích hợp UIView (UIKit) vào SwiftUI Project - SwiftUI Notes…
    Tích hợp UIView (UIKit) vào SwiftUI Project - SwiftUI Notes #15

Archives

  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!