iOS/Swift: Một số kỹ thuật truyền dữ liệu phổ biến trong swift

by DaoNM2
2K views

Lời mở đầu

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. Bài viết này mình muốn chia sẻ với các bạn về một số kỹ thuật phổ biến và ưu nhược điểm của nó. Bài viết này mình sẽ đề cập đến 3 cách phổ biến đó là Delegation, Closure và NotificationCenter Observation

Bài toán

Màn hình của mình cần làm có một UITableView, trong UITableView này chứa các UITableViewCell các cell thì có chứa 2 UIButton Dark và Light. Việc của mình phải làm là mỗi khi người dùng tương tác với button ở trong cell thì UIViewController sẽ bắt được sự kiện và hiển thị lên màn hình thông tin cell và hành động mà người dùng vừa tương tác.

Các bạn tải về project bắt đầu này để tiện hơn trong quá trình thực hành nhé:

Delegation

Delegation là một design pattern cho phép đối tượng gửi thông điệp(data, message …) đến đối tượng khác khi có một sự kiện xảy ra. Ví dụ ta có 2 đối tượng A và B, trên B thực hiện hành động gửi thông điệp sang A để A thực hiện hành động dựa trên kết quả hành động trên B.

Do chúng bài toán của chúng ta cần bắt hành động của người dùng khi họ action lên các button trên cell vì thế trong trường hợp này trong UITableViewCell chúng ta cần tạo protocol cho MyTableViewCell.swift cụ thể như sau:

protocol ActionOnCell: class {
    func didTapDark(indexPath: IndexPath?)
    func didTapLight(indexPath: IndexPath?)
}

Ở đây mình tạo ra protocol định nghĩa các action trên cell. Khi người dùng bấm vào button Dark nó sẽ call protocol này tương tự với Button Light.

Tiếp đến chúng ta tạo thêm 2 variables để lưu delegate và indexPath:

var indexPath: IndexPath?
weak var delegate: ActionOnCell?
  • indexPath để lưu lại cell mà ngươi dùng tương tác.
  • delegate để biết đối tượng nào đang conform protocol của MyTableViewCell

Đối với delegate chúng ta cần khai báo weak để tránh tham chiếu strong dẫn đến Retain cycle, không giải phóng được các object này vì nó đang tham chiếu tới nhau.

Tiếp đến chúng ta cần gọi các protocol tương ứng khi người dùng tương tác lên các button trên cell:

    @IBAction func dark(_ sender: Any) {
        if let delegate = delegate {
            delegate.didTapDark(indexPath: indexPath)
        }
    }

    @IBAction func light(_ sender: Any) {
        if let delegate = delegate {
            delegate.didTapLight(indexPath: indexPath)
        }
    }

Vậy là chúng ta đã setup protocol cho MyTableViewCell.swift giờ tất cả những UIViewController nào sử dụng cell này đều có thể conform protocol của nó để bắt được event khi người dùng tương tác lên các button cell.

Giờ chúng ta quay lại file ViewController.swift kéo xuống hàm cellForRowAt indexPath của tableview và update như sau:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "\(MyTableViewCell.self)", for: indexPath) as! MyTableViewCell
        cell.indexPath = indexPath
        cell.delegate = self
        return cell
    }

Chúng ta gán lại giá trị indexPath cho các cell. Và set delegate cho Cell là self(viewController hiện tại)
Lúc này XCode sẽ báo lỗi Cannot assign value of type ‘ViewController’ to type ‘ActionOnCell?’ vì chúng ta chưa conform protocol ActionOnCell trên ViewController.

Vì vậy chúng ta sẽ tạo 1 extension của ViewController và conform ActionOnCell

extension ViewController: ActionOnCell {
    func didTapDark(indexPath: IndexPath?) {
        showDark(indexPath: indexPath)
    }

    func didTapLight(indexPath: IndexPath?) {
        showLight(indexPath: indexPath)
    }
}

Khi người dùng tap vào button trên cell nó sẽ call protocol tương ứng và trả dữ liệu về cho viewController vì vậy bạn có thể sử dụng dữ liệu mà bạn muốn để update dữ liệu lên màn hình.

Các bạn Build project sẽ nhận được kết quả như sau:

NotificationCenter observation

Là một cơ chế gửi thông báo cho phép truyền phát thông tin đến các đối tượng đã đăng ký quan sát notification đó.

Chúng ta sẽ dựa trên 2 cơ chế post và observer của NotificationCenter để gửi và nhận dữ liệu giữa các đối tượng. Đối với notification thì chúng ta có thể sử dụng gửi thông tin từ một đối tượng đến nhiều đối tượng khác đã đăng ký theo dõi notification đó.

Cụ thể trong bài toán này chúng ta sẽ làm như sau:
Tạo 2 notification Name đinh nghĩa việc người dùng bấm vào Button Dark và Light:

extension Notification.Name {
    static let didTapDarkNotification = NSNotification.Name(rawValue: "didTapDarkNotification")
    static let didTapLightNotification = NSNotification.Name(rawValue: "didTapLightNotification")
}

Mở file MyTableViewCell.swift thực hiện việc phát(post) thông báo tại 2 event của 2 button trên cell cụ thể như dưới đây:

    @IBAction func dark(_ sender: Any) {
        NotificationCenter.default.post(name: .didTapDarkNotification, object: indexPath)
    }

    @IBAction func light(_ sender: Any) {
        NotificationCenter.default.post(name: .didTapLightNotification, object: indexPath)
    }

Nhiệm vụ của 2 hàm này là để khi nào người dùng bấm vào nut Dark hoặc Light thì NotificationCenter sẽ gửi đi một notification, những đối tượng nào đang lắng nghe các notification name đó sẽ nhận được thông điệp.

Vì vậy chúng ta cần add observer cho ViewController.swift để nó nhận được thông tin khi người dùng tương tác lên các button trên cell.
Chúng ta sẽ làm như sau:

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(didTapDark(noti:)), name: .didTapDarkNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(didTapLight(noti:)), name: .didTapLightNotification, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(self)
    }

Chúng ta cần đăng ký theo dõi notification ở viewWillApper và Remove theo dõi notification khi viewWillDisappear vì nếu chúng ta không remove view controller sẽ luôn nhận được thông báo khi mà nó còn trong view hierarchy dẫn đến những lỗi không mong muốn.

// MARK: NotificationCenter handle
extension ViewController {
    @objc func didTapDark(noti: Notification) {
        showDark(indexPath: noti.object as? IndexPath)
    }

    @objc func didTapLight(noti: Notification) {
        showLight(indexPath: noti.object as? IndexPath)
    }
}

Giờ build ứng dụng và các bạn sẽ nhận được kết quả tương tự như phần delegation

Closure

Nếu các bạn muốn tìm hiểu rõ hơn về Closure hãy đọc tài liệu tham khảo của Apple: https://docs.swift.org/swift-book/LanguageGuide/Closures.html

Ý tưởng ở đây chúng ta sẽ tạo 1 closure callback có 2 tham số là action type và dữ liệu truyền lại là index path.

Mở file MyTableViewCell.swift và thêm 1 enum định nghĩa các kiểu action trên cell

enum ActionType {
    case dark
    case light
}

Tạo closure cho MyTableViewCell

var didTapButton: ((ActionType, IndexPath?) -> Void)?

Gọi closure khi người dùng bấm vào các nút trên cell

    @IBAction func dark(_ sender: Any) {
        if let didTapButton = didTapButton {
            didTapButton(.dark, indexPath)
        }
    }

    @IBAction func light(_ sender: Any) {
        if let didTapButton = didTapButton {
            didTapButton(.light, indexPath)
        }
    }

Tiếp đến để nhận được event khi người dùng bấm vào các button chúng ta cần gán giá trị cho closure ở nơi mà bạn muốn. Cụ thể trong trường hợp này là ViewController.swift

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "\(MyTableViewCell.self)", for: indexPath) as! MyTableViewCell
        cell.indexPath = indexPath
        cell.didTapButton = {[weak self] (action, index) in
            guard let self = self else {return}
            if action == .dark {
                self.showDark(indexPath: index)
            } else {
                self.showLight(indexPath: index)
            }
        }
        return cell
    }

Giờ run ứng dụng chúng ta sẽ được kết quả như 2 cách trên.

Đánh giá

  • Delegation: là cách dùng rất phổ biến và đễ sử dụng nhât khi truyền dữ liệu giữa các đối tượng. Nó dựa vào các protocol để truyền dữ liệu qua lại giữa các đối tượng. Thường dùng trong các trường hợp truyền dữ liệu dạng 1 – 1. Tuy nhiên nếu bạn muốn sử dụng delegate dạng 1 – n hãy tham khảo bài viết này https://magz.techover.io/2019/08/09/swift-design-patterns-multicast-delegate/
  • NotificationCenter: Là dạng truyền nhận dữ liệu 1 – n hoặc 1 – n. Và khi một thông báo được đẩy đi tất cả những đối tượng đã đăng ký nhận thông báo đó sẽ nhận được thông báo. Khi bạn dùng cần phải kiểm soát chặt các notification. Trong mỗi trường hợp khác nhau chúng ta lại có cách đăng ký notification và remove nó ở các chỗ khác nhau.
    VD:
    – Đối với các trường hợp bình thường chúng ta sẽ phải thêm notification ở ViewWillAppear và remove nó ở viewWillDisappear để đảm bảo lần viewWillAppear tiếp theo nó không đăng ký lại dẫn đến bị lặp nhiều lần.
    – Chúng ta không thể để ở viewDidLoad vì nếu remove ở ViewWillDisappear thì khi pop lại sẽ mất notification, nếu không remove thì khi nó vẫn còn trong view hierarchy chưa bị giải phóng nó sẽ vẫn nhận được notification mặc dù chungs ta không mong muốn điều đó.
    Trong tất cả thì cách này là cách rắc rối nhất. Bạn chỉ nên dùng khi muốn thực hiện dạng truyền dữ liệu 1-n
  • Closure: Là cách dùng khá phổ biến hiện tại, vì nó là cách viết khá gọn gàng vì vậy tiết kiệm được thời gian code. Tuy nhiên cách viết của nó khá khó hiểu với người mới. Các bạn cũng có thể nhận thấy sự phổ biến của nó thông qua các hàm completion, callback mà Apple đã sử dụng rất nhiều trong các API của họ.

Tổng kết

Mình hi vọng bài viết giúp cho các bạn có thể lựa chọn các cách truyền dữ liệu phù hợp với bài toán của mình. Chúc các bạn thành công!

Nếu bài viết có vấn đề gì các bạn hãy comment xuống dưới để mình update nhé!

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.