Powering Your Mobile App Development with Protocols: Use Cases and Examples

Powering Your Mobile App Development with Protocols: Use Cases and Examples

·

10 min read

Photo by Brett Jordan on Unsplash

Have you ever wondered how to make your code more modular and easier to maintain? Have you ever had to pass data between different parts of your app, but struggled to keep everything organized? If so, you may want to consider using protocols in your mobile application development. In this article, we’ll explore what protocols are, how they work, and why they can be useful in a variety of mobile app development scenarios.

What are protocols? In Swift, protocols are a way to define a blueprint of methods, properties, and other requirements that a class or struct must adopt if it wants to conform to the protocol. Think of protocols as a contract between the implementing class and the rest of the app — the class promises to provide certain functionality, and the rest of the app can rely on that functionality being available. This allows you to write more modular and maintainable code, since different parts of the app can interact with each other via protocols instead of directly accessing each other’s internal properties and methods.

Benefits of using protocols in mobile app development: There are several benefits to using protocols in your mobile app development:

  1. Reusability: By defining functionality in a protocol, you can reuse that functionality across multiple parts of your app. For example, if you have multiple view controllers that need to perform a network request, you can define a protocol that specifies the required methods for making a network request, and have each view controller conform to that protocol.

  2. Testability: Protocols make it easier to write unit tests for your code, since you can write test cases that rely on the functionality defined in a protocol. This allows you to test individual components of your app in isolation, without worrying about how those components interact with each other.

  3. Flexibility: Protocols give you more flexibility in how you design your app architecture. For example, you can use protocols to define the interface between your model and view layers, or between different parts of your app that need to communicate with each other.

Now that we understand the benefits of using protocols, let’s take a look at some use cases for protocols in mobile app development.

Use Cases for Protocols in Mobile App Development:

Case 1: Data Transfer between View Controllers

One of the most common use cases for protocols in mobile app development is for transferring data between different view controllers. For example, let’s say you have a form that collects user information, and you want to pass that information to a confirmation screen. You could define a protocol that specifies the required properties and methods for passing that data, and have both the form view controller and the confirmation view controller conform to that protocol.

Here’s an example implementation of this use case:

protocol DataPassing {
var data: String? { get set }
}

class FormViewController: UIViewController, DataPassing {
var data: String?

func submitButtonTapped() {
// collect user data
data = "User data goes here"

// navigate to confirmation screen
let confirmationVC = ConfirmationViewController()
confirmationVC.dataPasser = self
navigationController?.pushViewController(confirmationVC, animated: true)
}
}

class ConfirmationViewController: UIViewController {
var dataPasser: DataPassing?

override func viewDidLoad() {
super.viewDidLoad()

// display user data
let dataLabel = UILabel()
dataLabel.text = dataPasser?.data ?? "No data"
view.addSubview(dataLabel)
}
}

In this example, the FormViewController collects user data and stores it in the data property, which is required by the DataPassing protocol. When the submit button is tapped, the FormViewController creates an instance of the ConfirmationViewController and sets its dataPasser property to self. This allows the ConfirmationViewController to access the data property of the FormViewController and display it on the screen.

Case 2: Implementing Delegation

Another common use case for protocols in mobile app development is implementing delegation. Delegation is a design pattern that allows one object to communicate with and control the behavior of another object. In Swift, delegation is typically implemented using protocols.

For example, let’s say you have a view controller that displays a list of items, and you want to allow the user to select an item from the list. You could define a protocol that specifies the required methods for handling item selection, and have the parent view controller conform to that protocol.

Here’s an example implementation of this use case:

protocol ItemSelectionDelegate: AnyObject {
func didSelectItem(item: String)
}

class ItemListViewController: UIViewController {
weak var delegate: ItemSelectionDelegate?

// display list of items

func itemSelected(item: String) {
delegate?.didSelectItem(item: item)
}
}

class ParentViewController: UIViewController, ItemSelectionDelegate {
func didSelectItem(item: String) {
// handle item selection
}

func showItemList() {
let itemListVC = ItemListViewController()
itemListVC.delegate = self
navigationController?.pushViewController(itemListVC, animated: true)
}
}

In this example, the ItemListViewController has a delegate property of type ItemSelectionDelegate, which is required by the protocol. When an item is selected from the list, the ItemListViewController calls the didSelectItem method on its delegate, passing in the selected item as an argument. The ParentViewControllerconforms to the ItemSelectionDelegate protocol and implements the didSelectItem method to handle item selection. When the showItemList method is called on the ParentViewController, it creates an instance of the ItemListViewController and sets its delegate property to self, allowing the ParentViewController to receive item selection events from the ItemListViewController.

Case 3: Network Requests

Protocols can also be useful for handling network requests in your mobile app. For example, you could define a protocol that specifies the required methods for making a network request, and have multiple classes conform to that protocol to perform different types of requests.

Here’s an example implementation of this use case:

protocol NetworkRequestProtocol {
func makeRequest(url: URL, completionHandler: @escaping (Data?, Error?) -> Void)
}

class NetworkRequestManager: NetworkRequestProtocol {
func makeRequest(url: URL, completionHandler: @escaping (Data?, Error?) -> Void) {
// perform network request using URLSession
URLSession.shared.dataTask(with: url) { data, response, error in
completionHandler(data, error)
}.resume()
}
}

class ImageRequester {
let networkRequester: NetworkRequestProtocol

init(networkRequester: NetworkRequestProtocol = NetworkRequestManager()) {
self.networkRequester = networkRequester
}

func getImage(url: URL, completionHandler: @escaping (UIImage?, Error?) -> Void) {
networkRequester.makeRequest(url: url) { data, error in
guard let data = data, let image = UIImage(data: data) else {
completionHandler(nil, error)
return
}
completionHandler(image, nil)
}
}
}

In this example, we define a protocol called NetworkRequestProtocol that specifies the required methods for making a network request. We then create a `NetworkRequestManager class that conforms to this protocol and provides an implementation for the makeRequest method using the URLSession API.

We also create an ImageRequester class that relies on the NetworkRequestProtocol to fetch images from a remote server. The ImageRequester class takes an instance of a class that conforms to the NetworkRequestProtocol as a dependency. This allows us to easily swap out the implementation of the NetworkRequestProtocol in case we need to use a different networking library or API.

Case 4: Building Custom UI Components

Protocols can also be useful for building custom UI components that can be easily reused across your app. For example, you could define a protocol that specifies the required methods for configuring a custom button, and have multiple view controllers conform to that protocol to create buttons with consistent styling and behavior.

Here’s an example implementation of this use case:

protocol CustomButtonProtocol {
var text: String? { get set }
var font: UIFont { get set }
var textColor: UIColor { get set }
var backgroundColor: UIColor { get set }
func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event)
}

class CustomButton: UIButton, CustomButtonProtocol {
var text: String? {
get { titleLabel?.text }
set { setTitle(newValue, for: .normal) }
}
var font: UIFont {
get { titleLabel?.font ?? UIFont.systemFont(ofSize: 17) }
set { titleLabel?.font = newValue }
}
var textColor: UIColor {
get { titleLabel?.textColor ?? UIColor.black }
set { setTitleColor(newValue, for: .normal) }
}
var backgroundColor: UIColor {
get { super.backgroundColor ?? UIColor.clear }
set { super.backgroundColor = newValue }
}
}

class MyViewController: UIViewController {
let customButton = CustomButton()

override func viewDidLoad() {
super.viewDidLoad()

customButton.text = "Custom Button"
customButton.font = UIFont.systemFont(ofSize: 20)
customButton.textColor = UIColor.white
customButton.backgroundColor = UIColor.blue
customButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

view.addSubview(customButton)
customButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
customButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
customButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}

@objc func buttonTapped() {
// handle button tap
}
}

In this example, we define a protocol called CustomButtonProtocol that specifies the required properties and methods for configuring a custom button. We then create a CustomButton class that extends the UIButton class and conforms to the CustomButtonProtocol. The CustomButton class overrides the default implementations of the properties and methods defined in the protocol to provide a custom implementation.

We also create a MyViewController class that uses the CustomButton class to create a custom button on the screen. The MyViewController class sets the text, font, text color, background color, and target/action for the button using the properties and methods defined in the CustomButtonProtocol.

Case 5: Organizing Code with MVVM

Finally, protocols can also be useful for organizing your code using the Model-View-ViewModel (MVVM) architectural pattern. MVVM is a pattern that separates the responsibilities of your app into three layers: the model layer, the view layer, and the view model layer.

In MVVM, the view model layer is responsible for providing the data and logic that the view layer needs to render itself. By defining protocols that specify the interface between the view and view model layers, we can make it easier to test and maintain our app.

Here’s an example implementation of this use case:

protocol ViewModelProtocol {
var title: String { get }
var subtitle: String { get }
var imageURL: URL? { get }
var isLoading: Bool { get }
func fetchData(completion: @escaping (Error?) -> Void)
}

class MyViewModel: ViewModelProtocol {
let dataService: DataServiceProtocol
var data: DataModel?
var error: Error?

init(dataService: DataServiceProtocol = DataService()) {
self.dataService = dataService
}

var title: String {
data?.title ?? "Loading..."
}

var subtitle: String {
data?.subtitle ?? "Loading..."
}

var imageURL: URL? {
data?.imageURL
}

var isLoading: Bool = false

func fetchData(completion: @escaping (Error?) -> Void) {
isLoading = true
dataService.fetchData { [weak self] result in
self?.isLoading = false

switch result {
case .success(let data):
self?.data = data
self?.error = nil
completion(nil)
case .failure(let error):
self?.data = nil
self?.error = error
completion(error)
}
}
}
}

class MyViewController: UIViewController {
let viewModel: ViewModelProtocol

let titleLabel = UILabel()
let subtitleLabel = UILabel()
let imageView = UIImageView()
let activityIndicatorView = UIActivityIndicatorView(style: .large)

init(viewModel: ViewModelProtocol = MyViewModel()) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()

view.addSubview(titleLabel)
view.addSubview(subtitleLabel)
view.addSubview(imageView)
view.addSubview(activityIndicatorView)

viewModel.fetchData { [weak self] error in
if let error = error {
// handle error
return
}

self?.updateUI()
}
}

func updateUI() {
titleLabel.text = viewModel.title
subtitleLabel.text = viewModel.subtitle

if let imageURL = viewModel.imageURL {
activityIndicatorView.startAnimating()
viewModel.dataService.fetchImage(url: imageURL) { [weak self] result in
self?.activityIndicatorView.stopAnimating()

switch result {
case .success(let image):
self?.imageView.image = image
case .failure(let error):
// handle error
break
}
}
}
}
}

In this example, we define a protocol called ViewModelProtocol that specifies the required properties and methods for a view model that provides data and logic to a view. We then create a MyViewModel class that conforms to the ViewModelProtocol. The MyViewModel class relies on a DataServiceProtocol to fetch data from a remote server and provides an implementation for the properties and methods defined in the protocol.

We also create a MyViewController class that uses the MyViewModel class to display data on the screen. The MyViewController class defines the UI components that display the data, and calls the fetchData method on the viewModel property to fetch the data from the server. Once the data is fetched, the updateUI method is called to update the UI components with the fetched data.

Conclusion

Protocols are a powerful tool in your mobile application development arsenal. They allow you to define a contract between different parts of your app, making it easier to write modular, reusable, and maintainable code. By using protocols to define the interface between different parts of your app, you can decouple those parts and make it easier to swap out implementations or add new features without affecting the rest of the app.

In this article, we explored several use cases for protocols in mobile app development, including data transfer between view controllers, implementing delegation, handling network requests, building custom UI components, and organizing code with the MVVM architectural pattern. We provided code examples for each use case, showing how to define protocols, conform to them, and use them to make your app more modular and flexible.

When using protocols in your app, it’s important to keep in mind some best practices. For example, it’s a good idea to use descriptive names for your protocols and their methods and properties, and to keep your protocols as simple as possible. You should also avoid making your protocols too specific or tightly coupled to a particular implementation, as this can make it harder to change your implementation later.

In conclusion, protocols are a powerful tool for mobile app developers. They allow you to define a contract between different parts of your app, making it easier to write modular, reusable, and maintainable code. By using protocols in your app, you can improve the flexibility and scalability of your app, and make it easier to add new features and change your implementation as your app evolves over time.