Most common communication patterns in iOS Development

Most common communication patterns in iOS Development

It's been a while since last time I've published an article. Excuse me for not writing more frequently but I've done a lot. I created this blog last year around these days. I was pushing myself to learn all about iOS development. Learning, repeating, forgetting and re-learning is the process we all have been living in. In this process I finished some courses, read some books, done a lot of coding and looked for a company to do an internship and gain some experience. After a lot of take home projects and interviewing process I was accepted from multiple companies.

So last summer I was in Ankara contributing a real world company at Bilkent CYBERPARK. Then I burned out and took some time for myself. After some time I thought I'm ready to build a real world app like I always dreamed and immediately I started to put some work. Another opportunity knocked my door at this time. I'm in a bootcamp right now making some new friends and refreshing my knowledge on iOS Development. I could make a whole article about my journey but let's just skip that with this summary for right now.

Today, I'd like to demonstrate some of the most common communication patterns in iOS Development. If you are struggling how to pass data on your UIKit app this article may help you. I'm also planing to write an article on the same subject for SwiftUI later. Let's start with may be the hardest one that most of the people spend some hard time when they first come across.

Protocol-Delegate Pattern

storyboards.png

These are the two main screens in our sample app. We basically want to take out some information from the second screen to the first screen. Excuse me for the naming of the view controllers in advance please. As you already know, naming things in programming might be the one of the hardest operations. In this article we are going to use this same app for different communication patterns.

import UIKit

protocol EditNameDPViewControllerDelegate: AnyObject {
    func didFinishEditing(name: String)
}

class EditNameDPViewController: UIViewController {

    @IBOutlet weak var editNameTextField: UITextField!

    weak var delegate: EditNameDPViewControllerDelegate?

    @IBAction func didTapCancelButton(_ sender: UIBarButtonItem) {
        dismiss(animated: true, completion: nil)
    }

    @IBAction func didTapDoneButton(_ sender: UIBarButtonItem) {
        guard let name = editNameTextField.text, editNameTextField.text != "" else {
            displayAlert(title: "Ooops!", message: "Something is missing!", buttonTitle: "OK")
            return
        }
        delegate?.didFinishEditing(name: name)
        dismiss(animated: true, completion: nil)
    }
}

As you can see above we imported UIKit and created our classic view controller. Between the import statement and the class declaration we created some protocol. This protocol is will be our delegate. Basically we need to create and weak instance in our class for this delegate. We try to escape from retain cycles by declaring this variable week. The second and final thing we need to do on this class is calling the delegate function and send out the information we need on the other screen. So let's move on to other view controller and see what we got there.

import UIKit

class DelegateViewController: UIViewController {

    @IBOutlet weak var greetingMessageLabel: UILabel!
    @IBOutlet weak var warningMessageLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.title = "Delegate"
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "editNameSegueDP" {
            if let destination = segue.destination as? EditNameDPViewController {
                destination.delegate = self
            }
        }
    }
}

extension DelegateViewController: EditNameDPViewControllerDelegate {
    func didFinishEditing(name: String) {
        greetingMessageLabel.text = "Hello, \(name)!"
        warningMessageLabel.text = "It is nice to meet you!"
    }
}

In this view controller we mainly need to accept the delegate that we wrote above. Prepare method for the segue seemed like perfect place for me. Because we need a reference for the second view controller to use its delegate instance. Please let me know if you know a better way of doing this in the comments below.

The last thing we need to do is inheriting the delegate protocol. I usually do these kinds of tasks in the extension apart from the view controller class for separation of concerns. Then we got the information we needed and update the user interface. That's it!

Notification Center

Let's move on to another way of passing data between screens. To be honest I rarely used this approach so far but I heard it is useful in some scenarios. May be not the best for our current scenario but we are going to use it for the educational purposes.


import UIKit

class EditNameNCViewController: UIViewController {

    @IBOutlet weak var editNameTextField: UITextField!

    @IBAction func didTapCancelButton(_ sender: UIBarButtonItem) {
        dismiss(animated: true, completion: nil)
    }

    @IBAction func didTapDoneButton(_ sender: UIBarButtonItem) {
        guard let name = editNameTextField.text, editNameTextField.text != "" else {
            displayAlert(title: "Ooops!", message: "Something is missing!", buttonTitle: "OK")
            return
        }
        configureEditNameNotification(name: name)
        dismiss(animated: true, completion: nil)
    }
}

extension EditNameNCViewController {
    func configureEditNameNotification(name: String) {
        NotificationCenter.default.post(
            name: Notification.Name(rawValue: "editName"),
            object: nil,
            userInfo: ["name": name]
        )
    }
}

We just created almost the same view controller except few different things. We created a different function to post a notification around the app. In this function we named the notification and gave it a value to publish it. We are calling this function at the same place like we did above. Important things to take note here we gave an identifier name for the notification and we sent the data as a dictionary.

import UIKit

class NotificationViewController: UIViewController {

    @IBOutlet weak var greetingMessageLabel: UILabel!
    @IBOutlet weak var warningMessageLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.title = "Notification"
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "editNameSegueNC" {
            if let _ = segue.destination as? EditNameNCViewController {
                configureEditNameNotificationObserver()
            }
        }
    }

    @objc func didReceiveNotification(_ notification: Notification) {
        guard let name = notification.userInfo?["name"] as? String else { return }
        greetingMessageLabel.text = "Hello, \(name)!"
        warningMessageLabel.text = "It is nice to meet you!"
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

extension NotificationViewController {
    func configureEditNameNotificationObserver() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(didReceiveNotification(_:)),
            name: Notification.Name("editName"),
            object: nil
        )
    }
}

In the first screen we created a function to observe the notification post we just published. Like I said above it is important to use the same identifier for not getting any error or something else. This observe operation needed an extra function to take out the information and update the user interface. Two important things to remember here are first we need to call the function that observes the notification and second we need to de-initialize the observer to escape memory leaks.

Closures

Closures might be the cleanest one around. It is simple to implement and use. Let's take a look at how we can we implement this structure in the second screen and how can we call it.

import UIKit

class EditNameCPViewController: UIViewController {

    @IBOutlet weak var editNameTextField: UITextField!

    var completion: ((_ name: String) -> Void)?

    @IBAction func didTapCancelButton(_ sender: UIBarButtonItem) {
        dismiss(animated: true)
    }

    @IBAction func didTapDoneButton(_ sender: UIBarButtonItem) {
        guard let name = editNameTextField.text, let completion = completion, editNameTextField.text != "" else {
            displayAlert(title: "Ooops!", message: "Something is missing!", buttonTitle: "OK")
            return
        }
        completion(name)
        dismiss(animated: true)
    }
}

Different than other view controllers we created a closure instance and called it from the function that we dismiss the screen. Nothing new here so I cut it short. In the first view controller we need to access this closure and take the information we need in somewhere somehow. Spoiler alert it is the same place that we prepare for the segue. Important things to remember here are using [weak self] to escape from retain cycles.

import UIKit

class ClosureViewController: UIViewController {

    @IBOutlet weak var greetingMessageLabel: UILabel!
    @IBOutlet weak var warningMessageLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.title = "Closure"
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "editNameSegueCP" {
            if let destination = segue.destination as? EditNameCPViewController {
                destination.completion = { [weak self] name in
                    self?.updateUI(with: name)
                }
            }
        }
    }
}

extension ClosureViewController {
    func updateUI(with name: String) {
        greetingMessageLabel.text = "Hello, \(name)!"
        warningMessageLabel.text = "It is nice to meet you!"
    }
}

I hope these patterns clearer in your mind now. Thank you for reading it and if you have any questions or you want to correct me somewhere or you want to add extra information to complete this article you are free to do that in the comments section below. I'd like to wish you a happy new year if you are reading this around December or January. I'm going to include the GitHub repo for this project here and you can fork it, clone it and play around with it. Until next time, take care!

Cover photo by Kaitlyn Baker on Unsplash