10 quick tips to increase your iOS code quality

Practical Strategies for Elevating Your iOS Code Quality

Bruno Lorenzo
4 min readJun 26, 2023

Writing quality code is a continuous improvement process.

We all improve over the years if we continue to practice. The code that you write today is probably better than the code you wrote two years ago, and it will probably be worse than the code you’ll write one year from now. And that’s ok, it’s part of the process.

So, if you are looking to constantly improve your code quality, here are some small tips that will help you in your journey.

Photo by Caspar Camille Rubin on Unsplash

1. Use the extension pattern to group chunks of code

You can group your setup code in one extension, your delegates in another, and so on. It will be easier to read and maintain in the future.

// MARK: - Setup
extension HomeViewModel {
func configureInitialState() { ... }
func fetchCards() { ... }
}

// MARK: - Actions
extension HomeViewModel {
func search(text: String) { ... }
func addCard(_ card: Card) { ... }
}

2. Define a maximum number of lines per file

And stick to it. If you pass that number, break down your file into two or more files. You could even use the extension pattern that we saw before.

3. Create variables and functions with descriptive names

The best documentation for your code is a code auto-documented. The foundation of this concept is to have descriptive names. In addition, use parameter placeholders when you can.

var result = 3
// Not the same as:
let numberOfSessions = 3

func update(id: Int, amount: Int) { .. }
// Not the same as:
func updateBalance(toUser userID: Int, with amount: Int) { .. }

4. Take advantage of typealias

Add more semantics to your code by using typealias. This way, when someone looks at your code for the first time, it will be easier to understand it.

typealias ApiHeaders = [String:String]

func getHeaders() -> ApiHeaders {
var headers = ApiHeaders()
headers["x-api-token"] = "asf232asgf5"
headers["content"] = "application/json"
return headers
}

5. Take care of the “no-happy path”

What happens when an error occurs? Do you let your user know? Do you give they the possibility to retry the action? We always focus on the happy path first and we tend to forget to handle the errors afterward.

// Avoid this approach
do {
// Perform some task
...
} catch {
print(error)
}

------

struct SinginView: View {
@State private var showingAlert = false
@State private var alertMessage = ""

var body: some View {
VStack {
.....
Button("Sign In") { tryToSignin() }
}
.alert("Error", isPresented: $showingAlert, actions:
Button("Ok") {}
}, message: {
Text(alertMessage)
})
}
}

extension SinginView {
func tryToSignin() {
do {
// Perform some task
...
} catch {
showingAlert = true
alertMessage = error.localizedDescription
}
}
}

6. Review force unwrap & indexes

Force unwrapping nil objects and out-of-bound indexes are one of the most common crash causes. It’s easy to avoid them by writing just an extra couple of lines.

let intString = ... // some value you get from a server
let int = Int(intString)

// Avoid
print(int!)

// Go safe with if-let
if let int = Int(intString) {
print(int)
}

let array = [.....] // Bunch of elements

// Add an extra safety net to access the values
extension Array {
public subscript(safeIndex index: Int) -> Iterator.Element? {
return index >= 0 && index < endIndex ? self[index] : nil
}
}

let numbers = [10, 23, 12, 63, 1, 3]
let number1 = numbers[safeIndex: 0] // = 10
let number2 = numbers[safeIndex: 6] // = nil

7. How do you handle sensitive information?

Are you hashing passwords before sending them to the server? Are you storing confidential information like API keys in the keychain?

You can check my Keychain article if you want an easy wrapper to use.

8. Avoid code duplication

If you need to write the same piece of code twice, it’s an indicator for a refactor. If you need to write for the third time, it’s a must-do.

9. Avoid hard-coding messages directly in the code

I’ve found over the years that it’s best to have either a manager to centralize all the messages, or directly localize them. This way, if you use one message several times, your maintenance will be easier.

enum UserMessages {
case loginSuccess
case loginError(_ error: String)

func getMessage(withKey key: String = "") -> String {
switch self {
case .success:
return NSLocalizedString("Success!", comment: key)
case .error:
return NSLocalizedString("An error occurred. Please try again later.", comment: key)
}
}
}

10. Take advantage of pre-built-in features

Use functions that are already included in the Foundation framework. Here are some examples.

var bool = false
bool.toggle() // bool = true

struct Person {
let name: String
let age: Int
}
var array = [Person(name: "John", age: 30), Person(name: "Jane", age: 20), Person(name: "Mike", age: 20)]

print(array.map({ $0.name })) // ["John", "Jane", "Mike"]
print(array.filter({ $0.age == 30 })) // [{"John", 30}]
array.forEach{print($0.name)} // "John" "Jane" "Mike"

Do you have any other tips that you use to improve code quality? Let me know in the comments.

Have any questions or need assistance? Feel free to drop me a message!🙂

  • 🤓 Join me on Twitter for regular content on iOS development tips and insights
  • 🚀 Check out my GitHub where I share all my example projects

--

--

Bruno Lorenzo

Software Engineer | Innovation Manager at www.houlak.com | Former iOS Tech Lead | I write about iOS, tech, and producitivy