Nếu ứng dụng của bạn có nhiều User Interfaces (UI) và bạn muốn truyền dữ liệu từ UI này sang UI tiếp theo. Làm thế nào để bạn có thể truyền dữ liệu giữa các ViewController trong Swift? Truyền dữ liệu giữa các ViewController là một phần quan trọng trong phát triển iOS. Khả năng truyền dữ liệu giữa các ViewController dễ bị ảnh hưởng bởi kiến trúc ứng dụng của bạn.

Trong Swift, chúng ta có khá nhiều cách để truyền dữ liệu qua lại giữa các đối tượng.

Truyền data giữa các ViewController với Properties (A -> B)
Cách dễ nhất để lấy dữ liệu từ ViewController A đến ViewController B (chuyển tiếp) là sử dụng một property.

Một property là một biến thuộc một phần của class. Tất cả instance của class đó đều có sẽ có property đó, và bạn có thể gán giá trị cho property đó. ViewController có kiểu UIViewController có thể có các thuộc tính giống như bất kỳ class nào khác.

Ở đây, một ViewController MainViewController với một property được gọi là text:

class MainViewController: UIViewController
{
    var text: String = ""

    override func viewDidLoad()
    {
        super.viewDidLoad()
    }
}


Bất cứ khi nào bạn tạo instance của MainViewController, bạn có thể gọi thuộc tính và gán giá trị cho property text giống như thế này:

let vc = MainViewController()
vc.text = "Hammock lomo literally microdosing street art pour-over"

ViewController là một phần của navigation controller và chúng ta muốn truyền dữ liệu tới ViewController thứ 2 thì chúng ta sẽ gọi method pushViewController:

navigationController?.pushViewController(vc, animated: true)

hoặc sẽ điều hướng màn hình sử dụng presentViewController:

present(vc, animated: true, completion: nil)


Truyền data ngược lại giữa các ViewController với Properties (A <- B)

Bây giờ ... nếu bạn muốn truyền data ngược lại từ ViewController thứ 2 tới MainViewController thì sao?

Trạng thái:

  • Người dùng ứng dụng của bạn đã chuyển từ ViewController A sang ViewController B.
  • Trong ViewController B, người dùng tương tác với data và bạn muốn data đó truyền ngược lại ViewController A.

Passing data với delegation

Delegation là rất quan trọng và thường xuyên được sử dụng trong iOS SDK. Nó là rất quan trọng nếu bạn viết iOS app.

Với delegation, một base class có thể chuyển chức năng cho một secondary class. Như vậy coder có thể implement secondary class này và phản hồi event cho base class bằng cách sử dụng protocol.

Ví dụ: 

- Bạn xác định một protocol

protocol PizzaDelegate
{
    func onPizzaReady(type: String)
}

Nếu một class tuân thủ protocol, thì một protocol là một thỏa thuận về những func mà một class nên implement. Bạn có thể thêm nó vào một class như thế này:

class MainViewController: UIViewController, PizzaDelegate
{
    ...

}

Việc definition class này nói rằng:

  • Tên của class là MainViewController
  • Mở rộng (hoặc sub class) của class UIViewController
  • Implement (or conform) the PizzaDelegate class

Nếu bạn nói rằng bạn muốn conform một protocol, bạn cũng phải implement nó. Bạn thêm chức năng này vào MainViewController

func onPizzaReady(type: String)
{
    print("Pizza ready. The best pizza of all pizzas is... \(type)")
}

Khi bạn tạo Secondary ViewController, bạn cũng phải tạo liên kết delegation, giống như property trong ví dụ trước:

vc.delegate = self

Và đây là khía cạnh chính của delegation. Bây giờ bạn có thể thêm property và code của hàm delegate vào class, giống Secondary ViewController.

Đầu tiên, property:

weak var delegate:PizzaDelegate?

Và sau đó là code:

@IBAction func onButtonTap()
{
    delegate?.onPizzaReady(type: "Pizza di Mama")
}

Các thành phần chính của delegation:

  • Bạn cần delegate protocol
  • Bạn cần delegate property
  • Class mà bạn muốn xử lý dữ liệu cần phải conform giao thức
  • Class mà bạn muốn delegate, cần gọi hàm được định nghĩa trong protocol

Passing Data Back Với A Closure 

Sử dụng closure để truyền data giữa các ViewController cũng không khác mấy so với sử dụng property hoặc delegation.

Lợi ích lớn nhất của việc sử dụng closure là nó tương đối dễ sử dụng, và bạn có thể định nghĩa nó ở cục bộ, không cần thêm protocol hay func

Để bắt đầu bạn hãy tạo một property ở SecondaryViewController giống như thế này:

var completionHandler:((String) -> Int)?

property completionHandler có kiểu closure. Closure này là optional, ký hiệu bởi ? và có (String) -> Int nghĩa là closure này có một parameter có kiểu là String và return Int

Một lần nữa, trong SecondaryViewController, chúng ta gọi closure này khi button được tap vào:

@IBAction func onButtonTap()
{
    let result = completionHandler?("FUS-ROH-DAH!!!")

    print("completionHandler returns... \(result)")
}

Chuyện gì đã xảy ra?

Closure completionHandler được gọi với một argument String, kết quả được gán cho result
Kết quả được in ra ngoài với print()
Sau đó trong MainViewController bạn có thể định nghĩa một closure như thế này:

vc.completionHandler = { text in

    print("text = \(text)")

    return text.characters.count
}

Trong closure parameter text được in ra, và sau đó độ dài của string được trả về như kết quả của func.

Closure cho phép bạn truyền data 2 chiều. Bạn có thể định nghĩa closure, làm việc với data vào và trả ra kết quả với closure.

Bạn có thể nghĩ ở đây là một lệnh gọi func, với delegate hoặc property trực tiếp, cũng cho phép bạn trả về một giá trị cho func.

Closure có thể có ích trong các tình huống sau:

  • Bạn không cần một delegate, protocol, bạn chỉ cần tạo một hàm nhanh
  • Bạn muốn gọi closure trong nhiều class. Không có closure bạn sẽ phải tạo ra một chuỗi các lệnh gọi hàm. Nhưng với closure bạn có thể truyền vào block code.
  • Bạn cần định nghĩa một block code với một closure, bởi vì data bạn muốn làm việc chỉ tồn tại ở cục bộ.
  • Một trong những rủi ro của việc sử dụng các closure để truyền dữ liệu giữa các ViewController làm code của bạn có thể trở nên dense. Tốt nhất là sử dụng closure cho việc truyền data giữa các ViewController.

Passing Data Between View Controllers với NotificationCenter

Nếu bạn muốn truyền data giữa các ViewController nhưng nó lại không có hoặc không thể có kết nối giữa chúng. Bạn có thể truyền dữ liệu giữa các ViewController với NotificationCenter, thông qua class NotificationCenter.

Notification Center xử lý các notifications, và chuyển tiếp notifications tới components nơi mà nó được lắng nghe. Notification Center là iOS SDK tiếp cận Observer-Observable software design pattern.

Làm việc với NotificationCenter có 3 ý chính:

  • Observing notification
  • Gửi notification
  • Responding tới notification

Trước tiên hãy bắt đầu với observing notification. Trước khi bạn phản hồi tới notification, bạn cần nói với NotificationCenter rằng nó cần observe nó. Sau đó NotificationCenter nói bới bạn rằng nó notifications nào sẽ được thông qua, bởi vì bạn đã chỉ ra cho bạn rằng bạn đang tìm kiếm nó.

Tất cả notification có tên để định danh nó. Trong MainViewController bạn định nghĩa một static property trên đầu của class:

static let notificationName = Notification.Name("myNotificationName")

Static property này, còn được gọi là class property, nó available bất cứ đâu trong code bằng cách gọi MainViewController.notificationName. Đây là cách bạn xác định notification với một hằng số duy nhất. Bạn sẽ không muốn trộn lẫn các notification của mình bằng cách nhập sai ở đâu đó!

Đây là cách bạn observe notification:

NotificationCenter.default.addObserver(self, selector: #selector(onNotification(notification:)), name: MainViewController.notificationName, object: nil)

Bạn thường thêm nó trong viewDidLoad() hoặc viewWillAppear() để observation được đăng ký khi ViewController được đưa lên màn hình. Đây là những gì xảy ra trong mẫu mã ở trên:

Bạn sử dụng NotificationCenter.default, đó là default của Notification Center. Bạn cũng có thể tạo riêng Notification Center cho chính bạn, ví dụ cho một loại notification nhất định, nhưng dafault vẫn ổn định hơn.
Sau đó bạn gọi addObserver(_:selector:name:object:)

  • Argument đầu tiên là instance observation, và nó gần như luôn luôn self
  • Argument thứ hai là selector bạn muốn gọi khi notification được observed và đây hầu như luôn là một func của class hiện tại.
  • Parameter thứ 3 là tên của notification, nên bạn sẽ truyền vào static constant notificationName.
  • Parameter thứ 4 là object nơi mà notifications bạn muốn nhận được. Bạn thường truyền nil ở đây nhưng bạn có thể sử dụng nó để chỉ observe notifications từ một object cụ thể.

Tại một thời điểm sau, bạn có thể ngừng observe notifications bằng cách này:

NotificationCenter.default.removeObserver(self, name: MainViewController.notificationName, object: nil)

Bạn cũng có thể ngừng observe tất cả các notifications với:

NotificationCenter.default.removeObserver(self)

Hãy nhớ rằng các notifications là rõ ràng, vì vậy bạn luôn observe một loại notification dẫn đến gọi một func trên một object (thường là self) khi notification xảy ra. Func sẽ được gọi khi notification xảy ra là onNotification (notification:), vì vậy, hãy để thêm điều đó vào class:

@objc func onNotification(notification:Notification)
{
    print(notification.userInfo)
}

@objc là từ khoá được yêu cầu đối với Swift 4 +, bởi vì NSNotificationCenter là framework Objective-C. Trong func, bạn sẽ chỉ in ra thông báo với notification.userInfo. Sau đó, gửi notification là dễ dàng. Đây là cách bạn làm điều đó:

NotificationCenter.default.post(name: MainViewController.notificationName, object: nil, userInfo:["data": 42, "isImportant": true])

Lần nữa hay xem lại điều gì xảy ra:

Bạn gọi func post(name:object:userInfo:) trong default NotificationCenter, chính xác cùng một center như bạn đã sử dụng trước đây.

  • Argument đầu tiên là tên của notification, static constant mà bạn đã định nghĩa trước đó.
  • Argument thứ 2 là object gửi notification. Bạn thường để nó nil, nhưng nếu bạn dùng object argument khi observing notification bạn có thể truyền cùng một object ở đây để chỉ observe và gửi cho object.
  • Argument thứ 3 là notification payload gọi là userInfo. Bạn có thể truyền một dictionary với bất kỳ data ở đây. Trong ví dụ, bạn truyền một vài data và giá trị boolean.

NotificationCenter có ích trong một vài tình huống:

  • Các ViewController hoặc các class khác mà bạn muốn truyền dữ liệu mà giữa chúng không có liên kết với nhau.
  • Các ViewController không nhất thiết phải tốn tại trước. Nó có thể xảy ra khi REST API và nhận được data trước khi table view đi vào màn hình. Observing notification là optional, nó là một lợi thế.
  • Nhiều ViewController có thể phản hồi một notification, hoặc một ViewController muốn phản hồi nhiều notification. NotificationCenter là many-to-many.
  • Bạn có thể nghĩ về NotificationCenter như một đường cao tốc về thông tin, nơi các notification liên tục được gửi qua các làn đường của nó, theo nhiều hướng và cấu hình.

Nếu bạn muốn sử dụng local traffic giữa các ViewController, thì không cần phải sử dụng NotificationCenter, đơn giản là bạn chỉ cần dùng delegate, property hoặc closure. Nhưng nếu bạn muốn liên tục và thường xuyên gửi dữ liệu từ một phần của ứng dụng này sang phần khác, NotificationCenter là một giải pháp tốt.