Một số kỹ thuật Passing Data giữa các View Controllers (Phần 2)

Trong phần trước, chúng ta đã cùng nhau tìm hiểu một số kỹ thuật Passing Data khá đơn giản và thường xuyên được sử dụng trong lập trình ứng dụng iOS. Trong phần này, chúng ta sẽ tiếp tục tìm hiểu thêm một số kỹ thuật khác cũng được dùng để truyền dữ liệu qua lại giữa các View Controllers. Các bạn có thể tham khảo Phần 1 của bài viết ở link sau: https://viblo.asia/tinhnq/posts/E375zBjj5GW

4. Passing data thông qua Delegation

Trong thực tế, các phương pháp truyền dữ liệu thông qua Segue không phải lúc nào cũng thực hiện được hoặc là giải pháp tốt nhất:

  • Dữ liệu truyền trở lại có thể là tạm thời và do đó không thuộc về trạng thái được chia sẻ của ứng dụng.
  • Khi quay trở lại View Controller liền trước trong Navigation Controller, nhưng unwind segue không được kích hoạt.
  • Chúng ta muốn dữ liệu truyền trở lại nhưng không cùng thời điểm chuyển màn hình.

Công việc cần làm để truyền được dữ liệu là tạo ra 1 kết nối giữa 2 View Controller. Giải pháp có thể là tạo một tham chiếu giữa 2 VC bằng cách sử dụng các phương pháp đã được giới thiệu trước đây và sau đó sử dụng chúng để tương tác ngược trở lại. Nhưng chúng lại không hề đơn giản như cách tiếp cận sau đây:

class SourceViewController: UIViewController {
	var data: Data?
	
	override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
		if let destination = segue.destination as? DestinationViewController {
			destination.source = self
		}
	}
}
 
class DestinationViewController: UIViewController {
	var source: SourceViewController?
	
	func passDataToSourceViewController() {
		let data = Data()
		source?.data = data
	}
}

Vấn đề ở phương pháp trên là chúng ta đã tham chiếu 2 VC một cách trực tiếp, mặt khác DestinationViewController cũng có thể được truy cập từ nhiều đường dẫn khác nhau và như vậy có thể sẽ có nhiều source khác nhau. Cách làm trên sẽ rất khó để quản lý và gây ra nhầm lẫn trong việc truyền data tới các source view controllers khác nhau. Bên cạnh đó việc tham chiếu trực tiếp như vậy còn tiềm ẩn nguy cơ rơi gặp phải vòng tham chiếu mạnh, gây ra leak memory.

Giải pháp ở đây là sử dụng Delegation. Việc sử dụng Delegation để truyền dữ liệu trở lại có 1 lợi thế là VC truyền dữ liệu không cần phải tham chiếu đến VC trước đó.

Những gì chúng ta cần là tạo 1 delegate protocol cho VC 2 - nơi data được truyền đi. Khi VC 2 cần truyền dữ liệu trở lại cho VC 1, nó sẽ truyền thông qua delegate. Delegate sẽ đóng vai trò là trung gian, đứng ra nhận và truyền dữ liệu giữa 2 View Controllers.

protocol Delegate: class {
	func doSomething(with data: Data)
}
 
class ViewController2: UIViewController {
	weak var delegate: Delegate?
	
	func passDataBackwards() {
		let data = Data()
		delegate?.doSomething(with: data)
	}
}

Lưu ý: Thuộc tính delegate cần được khai báo tham chiếu weak để tránh vòng tham chiếu mạnh, gây leak memory.

Sau đó, để nhận được data, VC 1 phải conform chính protocol đó.

class ViewController1: UIViewController {
	override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
		if let destination = segue.destination as? ViewController2 {
			destination.delegate = self
		}
	}
}

extension ViewController1: Delegate {
	func doSomething(with data: Data) {
		// Uses the data passed back
	}
}

Lợi thế của phương pháp này là VC đích không cần phải biết đối tượng nào là delegate miễn là đối tượng đó conform protocol là được.

5. Passing data bằng cách sử dụng Closures

Bên cạnh việc sử dụng Delegation để truyền dữ liệu ngược trở lại giữa các View Controllers, chúng ta cũng có thể sử dụng closures để truyền dữ liệu theo cách sau.

Chúng ta khai báo 1 closure làm cầu nối để truyền data từ VC 2 ngược trở lại VC 1. Closure cần được khai báo ở nơi gửi dữ liệu đi.

class ViewController2: UIViewController {
    var onCompletion: ((data: Data) -> ())?
    let data = Data()

    @IBAction func backButtonTapped(sender: Any?) {
        onCompletion?(data: data)
    }
}

Khi cần truyền data, chúng ta chỉ cần gọi closure để nhận data. Ở VC 1, nơi nhận data ngược trở lại, chúng ta cần xác định VC 2 là destination và là nơi sẽ truyền data trở lại VC gốc là VC 1. Sau đó gọi đến closure onCompletion của destination để nhận dữ liệu được truyền lại.

class ViewController1: UIViewController {
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let destination = segue.destination as? ViewController2 else { return }
        destination.onCompletion = { [weak self] (data) in
            // this will be executed when `backButtonTapped(_:)` will be called
        }
    }
}

Như vậy, trong phần này, chúng ta đã tìm hiểu thêm 2 phương pháp truyền dữ liệu giữa 2 View Controllers thông qua Delegation và Closure.

Hy vọng những kỹ thuật mà tôi đã giới thiệu cùng các bạn qua 2 phần của bài viết sẽ phần nào giúp ích cho các bạn trong công việc lập trình ứng dụng iOS.

Chân thành cảm ơn bạn đã theo dõi!

Bài viết được tham khảo từ nguồn: http://matteomanferdini.com/how-ios-view-controllers-communicate-with-each-other/