Contents
Chào mừng bạn đến với Fx Studio. Chúng ta lại tiếp tục với chủ đề Protocol vs. Closure và bài viết lần này là Asynchronous. Và từ đó, bạn sẽ tìm ra được những điểm giống & khác nhau khi sử dụng hai thế lực này.
Đây là bài viết tiếp nối bài viết đầu tiên về chủ đề Protocol vs. Closure. Nếu bạn chưa đọc qua thì có thể ghé sang link để tham khảo.
Ngoài ra, bạn cần tham khảo thêm các kiến thức về tương tác API trong iOS. Vì chúng ta sẽ dùng nó cho demo của bài viết này.
Còn nếu mọi thứ đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
-
- Xcode 12
- iOS 13.x
- Swift 5.x
Bài viết này, chúng ta dùng tiếp project đã tạo ở bài trước. Tuy nhiên, bạn sẽ cần một ViewController mới, đơn giản vì tách code ra cho dễ đọc mà thôi. Bạn có thể checkout lại nó tại đây.
1. Mục đích
Cũng tương tự như bài viết trước, bạn sẽ khám phá ra mục đích thật sự mà mình muốn hướng tới. Nếu bạn để ý tại bài viết trước thì Passing Data & Call back đều thực hiện trên cùng một thread. Và mọi thứ đồng bộ với nhau. Do đó, các thao tác xử lý sẽ lần lượt thực hiện. Cũng như việc phản hồi cũng xảy ra một cách tuần tự với nhau.
Khá là EZ!
Tuy nhiên, bạn đặt việc xử lý & phản hồi tại các Thread khác nhau, đó là một câu chuyện đau não à. Trong trường hợp này người ta sẽ gọi là:
Bất đồng bộ
Lúc này, Protocol & Closure sẽ có những cách khác nhau để xử lý. Chúng sẽ khác nhau hầu như là tất cả. Từ bản chất tới cách sử dụng. Đây mới chính là mục đích mà mình muốn bạn hiểu được. Vậy chốt cho mục đích bài viết này hướng tới là:
-
- Bản chất của mỗi loại
- Sự khác biệt trong cách dùng
2. Vấn đề
Ta đã biết được về mục đích rồi, bây giờ sang các vấn đề được đề cập trong bài viết này.
-
- Bất đồng bộ
- Tham chiếu
2.1. Bất đồng bộ (Asynchronous)
Ví dụ kinh điển cho trường hợp này là việc gọi một API để lấy dữ liệu. Khi đó, ứng dụng của bạn bắt buộc phải tương tác với API và phân tích dữ liệu nhận được. Cuối cùng là cập nhật dữ liệu lên giao diện. Nói qua thì không khó mấy, nhưng chúng ta phải biết được kẻ thù thực sự là gì. Đó là:
Block UI
Nguyên nhân đến từ Main Thread. Nó quản lý giao diện ứng dụng của bạn. Khi bạn muốn thêm một tác vụ vào, nhưng Main Thread hoạt động theo tuần tự. Do đó, các tiến trình về UI sẽ bị dừng lại.
Nhưng nó vẫn chưa phải là điều kinh khủng nhất.
2.2. Tham chiếu
Kinh khủng nhất đó là việc ứng dụng của bạn sẽ bị crash
. Khi bạn tiến hành cập nhật lại giao diện từ các thread khác nhau. Phần lớn nguyên nhân đó là do lỗi tham chiếu giữa các đối tượng với nhau. Bạn một mặt phải đảm bảo sự liên kết của các đối tượng. Một mặt phải quản lý các con trỏ đó đang tham chiếu hay đã bị giải phóng rồi. Tại sao vậy?
Kẻ thù lớn nhất là THỜI GIAN.
Asynchronous có nghĩa là bạn sẽ không biết lúc nào tác vụ của mình hoàn thành. Trong khoản thời gian đó sẽ có rất nhiều việc xảy ra. Các con trỏ có thể bị giải phóng rồi hoặc ngay cả các đối tượng lớn như ViewController cũng có thể bị người dùng giải phóng.
Đau khổ thêm nữa thì việc này hầu như diễn ra thường xuyên. Đơn giản như là người dùng thấy việc chờ đợi một API load, mà nó lại rất là lâu. Khi đó, hầu như 100% người dùng sẽ thoát & về màn hình trước đó. Có nghĩa là người dùng đã tự tay giết đi màn hình (ViewController) hiện tại. Và khi dữ liệu từ API trả về lại không còn các con trỏ hứng đợi hoặc việc gọi các con trỏ đã bị giải phóng … dẫn tới crash
chương trình.
3. Giải pháp
Tiếp theo, chúng ta sẽ đi vào giải pháp cho các vấn đề trên. Và vẫn là 2 thế lực thần thánh Protocol & Closure. Hi vọng cũng từ đó bạn sẽ thấy được điểm khác biệt của cả hai cho vấn đề Asynchronous này.
3.1. Cấu trúc
Ta sẽ vẫn dùng cấu trúc MVVM cơ bản với ViewModel & ViewController. Để phục vụ demo cho bài viết thì bạn hãy tạo các file như sau:
- MusicsViewController : chứa một TableView với cell cơ bản.
class MusicsViewController: UIViewController { // MARK: - Properties @IBOutlet weak var tableView: UITableView! var viewmodel = MusicsViewModel() override func viewDidLoad() { super.viewDidLoad() setupData() setupUI() } func setupUI() { title = "Musics" } func setupData() { // tableview tableView.delegate = self tableView.dataSource = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") } } extension MusicsViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { viewmodel.numberOfRowsInSection(session: section) } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) let item = viewmodel.musicItem(at: indexPath) cell.textLabel?.text = item.name return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } }
(Code ví dụ cho MusicsViewController)
- MusicsViewModel : xử lý việc tương tác API, phân tích dữ liệu.
class MusicsViewModel: NSObject { // MARK: - Properties var urlString = "https://rss.itunes.apple.com/api/v1/us/itunes-music/new-music/all/100/explicit.json" var musics: [Music] = [] // MARK: - init override init() { } // MARK: - Data func getAPI() { } // MARK: - TableView func numberOfRowsInSection(session: Int) -> Int { musics.count } func musicItem(at indexPath: IndexPath) -> Music { musics[indexPath.row] } }
(Code ví dụ cho MusicsViewModel)
Demo của chúng ta sẽ sử dụng một API để lấy những bài hát mới nhất từ trang iTunes của Apple. Bạn tham khảo link API sau:
3.1.1. Define
Ta cần phải định nghĩa thêm các đối tượng sử dụng cho việc demo này. Bạn tạo một file Music.swift như sau:
struct Music: Codable { var artistName: String var id: String var releaseDate: String var name: String var kind: String var copyright: String var artistId: String var artworkUrl100: String } struct MusicFeed : Codable { var results: [Music] } struct MusicResult: Codable { var feed: MusicFeed }
Cấu trúc các đối tượng này dựa theo cấu trúc JSON của dữ liệu từ API. Trong đó:
- Tên các thuộc tính của struct trùng với
key
trong JSON trả về - Bạn chú ý việc phân cấp của chúng nó.
- Ta sử dụng Codable protocol để có thể
decode
một cách nhanh chóng.
3.1.2. Error
Đây là phần phụ thôi, giúp cho code ta trông pro
lên. Tuy nhiên, mình cũng khuyến cáo bạn nên sử dụng nhiều cách này. Mọi thức sẽ đơn giản khi code đó và logic đó do bạn viết ra. Bạn tham khảo code khai báo một Error cho riêng project như sau:
enum APIError: Error { case error(String) case errorURL var localizedDescription: String { switch self { case .error(let string): return string case .errorURL: return "URL String is error." } } }
Đây là một enum
và bạn có thể thêm các trường hợp case
lỗi của riêng bạn. Vừa đảm bảo việc tương thích với các Protocol Error khác và tuỳ biến được nữa.
3.2. Protocol
Việc xử lý Asynchronous cho tương tác API & phản hồi kết quả nhận được bằng Protocol là cũng rất là lâu lắm rồi. Đó là thời đại của Objective-C và auto-release pool. Theo dòng phát triển của ngôn ngữ & công nghệ thì bây giờ chắc cũng rất ít dev iOS còn nhớ phương pháp này.
3.2.1. Define
Bắt đầu, bạn cần phải khai báo Protocol của bạn. Bạn mở file MusicViewModel và thêm đoạn code khai báo vào.
protocol MusicsViewModelDelegate: class { func musicViewModel(viewmodel: MusicsViewModel, didFinishedAPIWith error: Error?) }
Hiện tại, chúng ta chỉ cần một function đơn giản như vậy thôi. Nó chịu trách báo cho ViewController biết việc tương tác API đã xong. Tiếp theo, bạn khai báo thêm một thuộc tính delegate
trong ViewModel đó.
var delegate: MusicsViewModelDelegate?
Phần cuối cùng cho việc khai báo là implement cho ViewController. Bạn mở file MusicsViewController và thêm đoạn code cho extension
vào
extension MusicsViewController: MusicsViewModelDelegate { func musicViewModel(viewmodel: MusicsViewModel, didFinishedAPIWith error: Error?) { if let _ = error { print("Display ERROR") } else { tableView.reloadData() } } }
Cái này khá là EZ, bạn chỉ cần kiểm tra biến error
để biết là thành công hay thất bại. Sau đó, cập nhật lại giao diện của bạn cho đúng mà thôi.
3.2.2. Connection
Đây là phần trọng tâm, tất nhiên tất cả sẽ sử dụng Protocol. Tại file ViewModel, bạn truy cập tới function getAPI()
mà ta đã chuẩn bị từ trước. Sau đó thêm đoạn code sau vào.
func getAPI() { if let url = URL(string: urlString) { let request = URLRequest(url: url) let session = URLSession(configuration: .default, delegate: self, delegateQueue: .main) let task = session.dataTask(with: request) task.resume() } }
Vẫn là các dòng code quen thuộc. Trong đó:
- Đối tượng
url
tạo ra được từ URL String, chính là link của API. - Bạn cần thêm đối tượng
request
đảm đương nhiệm vụ request. session
là đối tượng quản lý phiên làm việc của mình.- Bạn cần chú ý là ta dùng Delegate thay cho handle closure
- Delegate sẽ hoạt động ở Main Thread
task
nhận nhiệm vụ thực thi việc kết nối
Điểm khác ở đây chính là sử dụng Delegate.
3.2.3. Parse Data
Do đó, bạn cần phải implement các function cần thiết trong các Delegate Protocol của URLSession. Bạn tiếp tục tạo thêm extension
cho ViewModel. Tham khảo đoạn code sau:
extension MusicsViewModel: URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate { func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { print("⚠️ didReceive response") data = Data() completionHandler(.allow) } func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { print("⚠️ Get data: \(data.count)") self.data.append(data) } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let error = error { print("⚠️ Error: \(error.localizedDescription)") delegate?.musicViewModel(viewmodel: self, didFinishedAPIWith: error) } else { print("⚠️ API DONE --> PARSE") do { let decoder = JSONDecoder() let result = try decoder.decode(MusicResult.self, from: data) self.musics = result.feed.results delegate?.musicViewModel(viewmodel: self, didFinishedAPIWith: nil) } catch { print("⚠️ Parse error: \(error.localizedDescription)") delegate?.musicViewModel(viewmodel: self, didFinishedAPIWith: error) } } } }
Với xử lý Asynchronous tương tác API bằng Delegate thì khá là vất vả. Trước tiên, bạn cần phải cho phép việc nhận response
tại hàm didReceive response
- Đơn giản chỉ cần
.allow
cho completionHandle - Tại đây, ta cũng bắt đầu cho việc phân tích dữ liệu. Chính là reset lại biến
data
.
Vì dữ liệu data
nhận được từ API sẽ không liên tục, chúng sẽ được gởi đi theo từng gói tin. Do đó, bạn cần phải khai báo thêm một thuộc tính data
cho ViewModel.
var data = Data()
Tại function didReceive data
, chúng ta sẽ nối các gói tin nhận được vào biến data
. Lúc này, nó như là một bộ đệm nhận dữ liệu từ API.
Sau khi nhận đầy đủ dữ liệu thì function didCompleteWithError
sẽ được gọi thực thi. Tại đây, bạn cũng thực hiện phân tích dữ liệu (Parse Data). Cách phân tích dữ liệu thì mình có trình bài ở các bài tương tác API (link ở phần trên bài viết). Bạn sẽ dùng thuộc tính delegate
của ViewModel mà phản hồi hết quả về ViewController.
Cuối cùng, bạn sẽ gọi việc tương tác API từ ViewController. Bạn truy cập tới function setupData
để thực hiện.
func setupData() { // viewmodel viewmodel.delegate = self viewmodel.getAPI() // tableview tableView.delegate = self tableView.dataSource = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") }
Quan trọng là bạn cần phải xét delegate
của ViewModel chính là ViewController. Build và cảm nhận kết quả nào!
3.2.4. weak / strong
Về bản chất chính của việc sử dụng Delegate chính là:
Sử dụng con trỏ A trong B. Với đại diện A là Delegate. Nó là một thuộc tính của B.
Do đó, vấn đề chính của ta cần quan tâm đó làm tham chiếu. Bạn để ý các bước trên, ta khai báo thuộc tính delegate
của ViewModel, mà không có bất cứ tuỳ chọn gì. Nên Mặc định là strong
. Và điều này sẽ dẫn tới rất nhiều vấn đề gặp phải khi xử lý Asynchronous.
Ví dụ, trong lúc chờ API nhưng API lại rơi vào time out
hoặc load quá chậm. Lúc đó, người dùng sẽ back về màn hình trước đó. Như vậy, đối tượng ViewController với con trỏ của nó đã được giải phóng. Nhưng con trỏ delegate
thì vẫn tham chiếu tới vùng nhớ kia. Kết quả sẽ là:
- Bộ nhớ bị chiếm dụng. Chỉ cần bạn vào/ra tầm vài lần thì có thể dẫn tới treo ứng dụng vì
ram
lên quá nhiều. - Crash chương trình, nếu có nhiều xử lý phức tạp lúc nhận được dữ liệu.
- Gây ra các lỗi ngớ ngẩn như là các Observer vẫn còn ở ViewController
- …
Giải quyết nó thì cũng khá là EZ. Bạn thay đổi lại khai báo thuộc tính delegate
của ViewModel như sau:
weak var delegate: MusicsViewModelDelegate?
Với weak
thì liên kết sẽ không bền vững. Nó sẽ không làm tăng retain count
của vùng nhớ. Do đó, rất là có lợi khi xử lý Asynchronous. Đi kèm theo combo Optional thì bạn sẽ hoàn toàn yên tâm khi sử dụng và không lo lắng bất cứ điều gì.
3.2.5. Perform
Ta sẽ nâng cao tính linh hoạt của Protocol. Nó sẽ được áp trong các trường hợp một ViewModel sẽ xử lý nhiều tác vụ bất động bộ. Lúc này ta sẽ sử dụng kiểu tương tác Protocol là Perform. Bạn sẽ thêm một function nữa cho Delegate Protocol.
protocol MusicsViewModelDelegate: class { func musicViewModel(viewmodel: MusicsViewModel, didFinishedAPIWith error: Error?) func musicViewModel(viewmodel: MusicsViewModel, needPerformWith action: MusicsViewModel.Action) }
Trong đó, ta thấy có Action trong tham số của function delegate. Nó chính là một enum
định nghĩa các tác vụ của chúng ta. Bạn khai báo tại ViewModel thêm enum đó như sau:
enum Action { case loadAPI(APIError?) }
Bạn muốn thêm trường hợp nào nữa thì cứ thêm các case
tương ứng. Còn việc implement, bạn sẽ cập nhật lại extension tại ViewController như sau:
extension MusicsViewController: MusicsViewModelDelegate { func musicViewModel(viewmodel: MusicsViewModel, didFinishedAPIWith error: Error?) { if let _ = error { print("Display ERROR") } else { tableView.reloadData() } } func musicViewModel(viewmodel: MusicsViewModel, needPerformWith action: MusicsViewModel.Action) { switch action { case .loadAPI(let error): if let _ = error { print("Display ERROR") } else { tableView.reloadData() } } } }
Mọi thứ sẽ được giải quyết đẹp với switch ... case
. Cuối cùng, bạn cập nhật lại cách gọi function mới của delegate
. Ví dụ như sau:
// Cách cũ delegate?.musicViewModel(viewmodel: self, didFinishedAPIWith: error) // Cách mới delegate?.musicViewModel(viewmodel: self, needPerformWith: .loadAPI(.error(error.localizedDescription)))
Như vậy là tạm xong cho việc xử lý Asynchronous bằng Protocol. Chúng ta tiếp tục sang xử lý bằng Closure nào.
3.3. Closure
Với Closure, hầu như được các bạn dev sử dụng rất nhiều. Và Apple cũng khuyến cáo bạn nên sử dụng nó. Fx Studio có rất nhiều bài viết liên quan tới xử lý Asynchronous tương tác API. Bạn có thể tìm kiếm trong website để đọc thêm.
3.3.1. Define
Bắt đầu, bạn sẽ khai báo một bí danh cho Closure mà bạn sẽ dùng là call back
chính cho ViewModel. Bạn mở file ViewModel thêm khai báo sau:
typealias MusicAPICompletion = (APIError?) -> Void
Mục đích của nó là khai báo trong function cho nhanh mà thôi. Và cũng được đề cập tại bài viết trước rồi.
3.3.2. Callback
Tới phần chính nào! Bạn sẽ xử lý Asynchronous tương tác API với 100% dùng Closure để handle 2 việc:
- Parse data
- Callback
Bạn thêm một function cho việc tương tác API tại ViewModel. Tham khảo đoạn code sau:
func getAPI(completion: @escaping MusicAPICompletion<[Music]>) { if let url = URL(string: urlString) { let request = URLRequest(url: url) let session = URLSession(configuration: .default) let task = session.dataTask(with: request) {(data, _, error) in DispatchQueue.main.async { if let error = error { completion(.error(error.localizedDescription)) } else { guard let data = data else { completion(.error("Data error")) return } do { let decoder = JSONDecoder() let result = try decoder.decode(MusicResult.self, from: data) self.musics = result.feed.results completion(nil) } catch { completion(.error("Parse error")) } } } } task.resume() } else { //completion(.errorURL) completion(.failure(.errorURL)) } }
Vẫn là các đoạn code quen thuộc thôi. Ta sẽ Parse Data tại closure của dataTask
. Trong đó, bạn chỉ cần chú ý việc xử lý này sẽ phải được thử hiện ở Main Thread. Vì dataTask
xử lý Asynchronous ở một Thread khác, các tác vụ của chúng ta sẽ liên quan tới cập nhật giao diện. Nên phải quay về Main Thread để xử lý.
Việc callback
thông qua thực thi completion
của function. Nó cũng không có gì khó hết.
Cuối cùng, bạn sẽ sử dụng nó ở ViewController như thế nào. Bạn mở file ViewController và truy cập tới function setupData
để thêm việc gọi API. Tham khảo đoạn code sau:
func setupData() { // viewmodel viewmodel.delegate = self // call API //viewmodel.getAPI() viewmodel.getAPI { error in if let _ = error { print("Display ERROR") } else { self.tableView.reloadData() } } // tableview tableView.delegate = self tableView.dataSource = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") }
Bạn chỉ cần kiểm tra error
để biết callback nhận được là thành công hay thất bại. Sau đó, bạn build lại project và kiểm tra xem code chúng ta đã hoạt động ổn hay không.
3.3.3. Result
Ta nâng cấp thêm cho phần dữ liệu đi kèm với callback cho nó đúng ngữ nghĩa câu chuyện. Còn với cách trên, bạn sẽ phải đi kiểm tra 1 biến để biết thành công hay là thất bại.
Không đảm bảo tính tương minh của code.
Ta sẽ thay đổi lại định nghĩa của typealias
cho Closure Callback như sau:
typealias MusicAPICompletion<T> = (Result<T, APIError>) -> Void
Result là gì?
- Đây là một kiểu dữ liệu đặc biệt. Tương tự như một enum. Dữ liệu nhận được chỉ là 1 trong 2 trường hợp mà thôi.
- Bạn cần cung cấp 2 kiểu dữ liệu cho 2 trường hợp của Result
- Với 2 trường hợp (case)
.success
dành cho trường hợp thành công. Trả kèm với nó là tham số với kiểu dữ liệu<T>
mà ta cung cấp lúc khởi tạo.failure
dành cho trường hợp thất bại. Với lỗi theo kiểu dữ liệu phù hợp với Error Protocol.
Bạn thay đổi code cho việc gọi các completion
thực thi theo cách mới. Bạn tham khảo code function với kiểu define mới sau đây.
func getAPI(completion: @escaping MusicAPICompletion<[Music]>) { if let url = URL(string: urlString) { let request = URLRequest(url: url) let session = URLSession(configuration: .default) let task = session.dataTask(with: request) {(data, _, error) in DispatchQueue.main.async { if let error = error { //completion(.error(error.localizedDescription)) completion(.failure(.error(error.localizedDescription))) } else { guard let data = data else { //completion(.error("Data error")) completion(.failure(.error("Data error"))) return } do { let decoder = JSONDecoder() let result = try decoder.decode(MusicResult.self, from: data) self.musics = result.feed.results //completion(nil) completion(.success(self.musics)) } catch { //completion(.error("Parse error")) completion(.failure(.error("Parse error"))) } } } } task.resume() } else { //completion(.errorURL) completion(.failure(.errorURL)) } }
Tiếp theo, bạn quay trở lại ViewController để cập nhật lại cách gọi ViewModel xử lý tương tác. Bạn tham khảo đoạn code sau:
viewmodel.getAPI { (result) in switch result { case .success(_): self.tableView.reloadData() case .failure(let error): print(error.localizedDescription) } }
Handle dữ liệu nhận được từ callback trở nên đơn giản hơn. Khi bạn chỉ cần phân biệt ra các trường hợp của Result bằng switch ... case
mà thôi.
3.3.4. @escaping
Đây là phần không thể nào thiếu khi xử lý Asynchronous bằng Closure. Bạn để ý kĩ thì nó có trong function này
func getAPI(completion: @escaping MusicAPICompletion<[Music]>) { // ... }
Về nó thì mình đã đề cập tại bài viết Closure trong 10 phút, bạn có thể tham khảo.
Khi truyền Closure làm đối số (function argument), có những trường hợp mà Closure sẽ được giữ lại để thực thi (execute) sau khi function được execute và trả lại compiler. Lúc này, closure không bị out of scope
và vẫn được giữ lại trong bộ nhớ. Hai trường hợp thường thấy là:
- Khi cần giữ lại Closure thành thuộc tính của class để chờ execute sau. Để Closure có thể được giữ lại trong bộ nhớ sau khi function execute xong và return compiler. Ví dụ: chờ response API…
var complitionHandler: ((String) -> Void)? func requestURL(urlString: String, handler: @escaping (String) -> Void) { // 2. Excute function. var response = "" // Call API code ... // 3. Closure được gán lại thành property, không bị out of scope complitionHandler = handler } func loadData() { // 1. Gọi function, truyền closure vào làm tham số. requestURL(urlString:"https://mocky.io/api/v1/accounts") { response in // Handle response string code ... // 4. Closure được excute xong, return compiler, nhưng vẫn được giữ lại trong bộ nhớ. } }
- Khi execute closure trên một thread khác, một asynchronous dispatch queue. Queue này sẽ giữ closure trong bộ nhớ, nhằm execute sau. Trường hợp này, chúng ta sẽ không biết được closure sẽ được execute chính xác vào thời điểm nào.
func getSumOf(numbers: [Int], completion: @escaping (Int) -> Void) { // 2. execute function. var sum = 0 for aNumber in numbers { sum += aNumber } // Delay 5s và execute closure trên global queue. DispatchQueue.glonal.asyncAfter(deadline: .now() + 5) { completion(sum) } } func doSomething() { // 1. Gọi function, truyền closure vào làm tham số. getSumOf(numbers: [34, 16, 231, 6 , 23, -83]) { result in print("Sum is \(result)") // 4. Closure được execute xong, return compiler và closure chưa bị giải phóng vì đang được giữ lại trên queue khác để excute sau. } }
Qua trên bạn sẽ thấy kẻ thù lớn nhất của chúng ta vẫn là THỜI GIAN. Nó sẽ ảnh hưởng tới toàn bộ quán trình xử lý Asynchronous. Do đó, đối với Protocol hay Closure thì bạn cũng phải quan tâm với đề này. Nhằm đảm bảo một điều duy nhất.
Ứng dụng không được crash.
Tạm kết
- Các vấn đề gặp phải khi thực hiện xử lý Asynchronous
- Sự khác nhau trong 2 cách dùng Protocol & Closure để giải quyết vấn đề.
- Với Protocol thì bạn sẽ quan tâm tới tham chiếu của con trỏ.
- Với Closure thì bạn sẽ quan tâm tới phạm vi (scope) thực thi của function.
Okay! Tới đây thì mình xin kết thúc bài viết Protocol vs. Closure với phần thứ hai là Asynchronous. 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.
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
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!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- 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)