[SC]()

iOS. Apple. Indies. Plus Things.

Asking AI to Refactor Swift Code

// Written by Jordan Morgan // Apr 29th, 2024 // Read it in about 5 minutes // RE: The Indie Dev Diaries

This post is brought to you by Emerge Tools, the best way to build on mobile.

Recently I’ve been adding quality-of-life platform fixes to Elite Hoops. Among those? Home Screen Quick Actions.

It’s been a minute since I’ve created dynamic ones, so I forgot how much bookkeeping can be involved. I wanted a dynamic quick action for each recently viewed team in Elite Hoops. But, that also means that…

  • I need to add it to UIApplication.shared.shortcutItems when one is opened.
  • And update it when the name of a team could be changed.
  • And remove it when it’s been deleted.
  • And write it in such a way to where when I add cloud syncing, it’ll stay in lockstep with any changes from the server.

So, one of those hard easy things.

let concatenatedThoughts = """

In fact, the last time I wrote Home Scren Quick Actions was for the chapter in my book series. Which, as it were, I leaned upon heavily to implement these. I'm in the middle of releaseing an update for it now, do check it out if you haven't yet. Five books, one price forever.

"""

To that end, here is the function I initially wrote:

// This function is kind of hard to read - can you make it simpler?  

func updateQuickActionsForCourts() {
    if appSettings.quickActionCourtsToAdd.isEmpty &&
       appSettings.quickActionCourtsToRemove.isEmpty {
        return
    }
    
    var quickActions: [UIApplicationShortcutItem] = UIApplication.shared.shortcutItems ?? []
    
    // Any removals?
    var removalIndices: [Int] = []
    appSettings.quickActionCourtsToRemove.forEach { courtIDToRemove in
        for (index, item) in quickActions.enumerated() {
            let courtID = item.userInfo?["courtID"] as? Int64 ?? 0
            if courtIDToRemove == courtID {
                removalIndices.append(index)
            }
        }
    }
    
    removalIndices.forEach { removalIndex in
        quickActions.remove(at: removalIndex)
    }
    
    // Any additions?
    appSettings.quickActionCourtsToAdd.forEach { court in
        if quickActions.filter({ item in
            let courtID = item.userInfo?["courtID"] as? Int64 ?? 0
            return courtID == court.id
        }).isEmpty {
            quickActions.append(court.quickAction())
        }
    }
    
    UIApplication.shared.shortcutItems = quickActions.reversed()
    
    appSettings.quickActionCourtsToAdd.removeAll()
    appSettings.quickActionCourtsToRemove.removeAll()
}

It’s ugly, but it works. So, I began to wonder — how would AI tools suggest I change it. Below are the results, all given the same prompt.

The Prompt

// This function is kind of hard to read - can you make it simpler?  

/* Then I included the function above right here */

chatGPT 4

First off, the institutional choice. OpenAI’s chatGPT using chatGPT 4:

/*
Certainly! Here's a simplified and refactored version of your Swift function. I've added comments for clarity and improved the readability of the code by separating the removal and addition processes into separate functions:
*/

func updateQuickActionsForCourts() {
    guard !appSettings.quickActionCourtsToAdd.isEmpty || !appSettings.quickActionCourtsToRemove.isEmpty else {
        return
    }
    
    var quickActions = UIApplication.shared.shortcutItems ?? []
    
    // Process removals
    quickActions = removeCourts(from: quickActions)
    
    // Process additions
    quickActions = addCourts(to: quickActions)
    
    // Update quick actions in reverse order and clear settings
    UIApplication.shared.shortcutItems = quickActions.reversed()
    clearCourtSettings()
}

/// Removes courts from the quick actions list based on app settings.
private func removeCourts(from quickActions: [UIApplicationShortcutItem]) -> [UIApplicationShortcutItem] {
    let courtIDsToRemove = Set(appSettings.quickActionCourtsToRemove)
    return quickActions.filter { item in
        guard let courtID = item.userInfo?["courtID"] as? Int64 else { return true }
        return !courtIDsToRemove.contains(courtID)
    }
}

/// Adds courts to the quick actions list based on app settings.
private func addCourts(to quickActions: [UIApplicationShortcutItem]) -> [UIApplicationShortcutItem] {
    var updatedQuickActions = quickActions
    appSettings.quickActionCourtsToAdd.forEach { court in
        let exists = updatedQuickActions.contains { item in
            let courtID = item.userInfo?["courtID"] as? Int64 ?? 0
            return courtID == court.id
        }
        if !exists {
            updatedQuickActions.append(court.quickAction())
        }
    }
    return updatedQuickActions
}

/// Clears the lists of courts to add or remove from app settings.
private func clearCourtSettings() {
    appSettings.quickActionCourtsToAdd.removeAll()
    appSettings.quickActionCourtsToRemove.removeAll()
}

/*
Explanation of Changes:
Guard Statement: A guard statement at the start of the function checks whether any updates are needed, improving readability by reducing nesting.

Separation of Concerns: I extracted the logic for removing and adding courts into separate functions, removeCourts and addCourts. This makes the main function more concise and each piece of functionality easier to maintain.

Filter and Contains: The removal function uses filter and contains to efficiently remove items. This eliminates the need for finding indices and then removing by index, which can introduce errors or require additional adjustments in indexing after each removal.
Function Encapsulation: Moving specific functionality into private helper functions clarifies the main function's flow and isolates each specific task.

Reverse Logic Consolidation: The reversal of the quick actions list is kept at the point where the list is updated in the UIApplication, making the order manipulation more visible.

This refactor should make your function easier to read, maintain, and modify in the future.
*/

I’ve found that chatGPT is more verbose in its response. Not only does it write code, but it tries to explain it to you. This code is nice if you’re the type of who believes each function should be a single thing, and not much more. Of all the choices, when I go back and pretty up my code (because we always go back, right?!) — this is probably the closet to how I tend to author it.

meta.ai

The new kid on the block, personifying the “if it’s free you are the product” approach is Meta’s newly minted meta.ai. Here’s how it refactored my code:

// Here's a simplified version of the function:

func updateQuickActionsForCourts() {
    guard !(appSettings.quickActionCourtsToAdd.isEmpty && appSettings.quickActionCourtsToRemove.isEmpty) else {
        return
    }

    var quickActions = UIApplication.shared.shortcutItems ?? []

    // Remove quick actions
    quickActions.removeAll { item in
        appSettings.quickActionCourtsToRemove.contains(item.userInfo?["courtID"] as? Int64 ?? 0)
    }

    // Add quick actions
    appSettings.quickActionCourtsToAdd.forEach { court in
        if !quickActions.contains(where: { item in
            item.userInfo?["courtID"] as? Int64 == court.id
        }) {
            quickActions.append(court.quickAction())
        }
    }

    UIApplication.shared.shortcutItems = quickActions.reversed()

    appSettings.quickActionCourtsToAdd.removeAll()
    appSettings.quickActionCourtsToRemove.removeAll()
}

/*
This version uses more concise and expressive syntax, such as guard statements, removeAll and contains methods, and more. It should be easier to read and understand. Let me know if you have any questions!
*/

What I like about this is how much simpler it is to read. Even though I most write code like chatGPT 4, I think I can read Meta’s take on it easier.

Claude by Anthropic

Finally, there’s Claude. I consider it the last of the “big three”, at least in my sphere of influence. I should note, this is not Anthropic’s best model — that’s Claude 3 Opus, but anyways here is what I got:

// Here's a simplified version of the updateQuickActionsForCourts function:

func updateQuickActionsForCourts() {
    guard !appSettings.quickActionCourtsToAdd.isEmpty || !appSettings.quickActionCourtsToRemove.isEmpty else {
        return
    }

    var quickActions = UIApplication.shared.shortcutItems ?? []

    // Remove unwanted quick actions
    quickActions = quickActions.filter { action in
        guard let courtID = action.userInfo?["courtID"] as? Int64 else {
            return true
        }
        return !appSettings.quickActionCourtsToRemove.contains(courtID)
    }

    // Add new quick actions
    let existingCourtIDs = quickActions.compactMap { $0.userInfo?["courtID"] as? Int64 }
    let newQuickActions = appSettings.quickActionCourtsToAdd
        .filter { !existingCourtIDs.contains($0.id) }
        .map { $0.quickAction() }
    quickActions.append(contentsOf: newQuickActions)

    UIApplication.shared.shortcutItems = quickActions
    appSettings.quickActionCourtsToAdd.removeAll()
    appSettings.quickActionCourtsToRemove.removeAll()
}

/*
Here are the main changes:

- The early return is moved to the beginning of the function to avoid unnecessary work if there are no actions to add or remove.

- The removal of unwanted quick actions is done using the filter method, which creates a new array containing only the elements that satisfy the condition.

- The addition of new quick actions is done by first creating an array of existing court IDs, then filtering the appSettings.quickActionCourtsToAdd array to remove any courts that already have a quick action, and finally mapping the remaining courts to their corresponding quick actions.

- The append(contentsOf:) method is used to add the new quick actions to the existing array in a single operation.

- The reversed() call has been removed since it is unnecessary.

These changes make the code more concise and easier to read, while preserving the original functionality.
*/

Not bad, like chatGPT 4, it tends to explain in detail what its changed are, and why it made them. And, like Meta, it’s solution is still within one function, but greatly simplified.

Final Thoughts

I offer no verdict in this post other than…it’s interesting to see how large language models reason about code. All three models quickly pointed out an early guard statement, for example. And, each one tends to lean into language features, such as Swift’s functional aspects. As it should, I presume.

So where did I land?

I just left my original, ugly function in and wrote this post 😇.

Until next time ✌️.

···

Spot an issue, anything to add?

Reach Out.