Wes Matlock

Swift Generics — A Refresher

I’ll never forget the time an interviewer asked me if I could build a SwiftUI container that held two different view types as generics. My…


Swift Generics — A Refresher

I’ll never forget the time an interviewer asked me if I could build a SwiftUI container that held two different view types as generics. My first reaction was, “Wait, we can do that?” It turns out it’s a straightforward idea once you get used to the syntax. If you’ve been in a similar spot, or just want a brush-up, this is for you. Let’s jump in.

Generics: The Flexible Friends in Your Toolkit

Generics let you write logic without locking yourself into a single type. You might have seen something like GenericClass, but generics can involve more than one type parameter. It’s often helpful when you’re building flexible components that can accept various kinds of inputs.

Naming Generics

You’re free to name your generic parameters something more descriptive than T or U. If you find names like DataType or Element more meaningful, go for it. Swift won’t complain, as long as you’re consistent and follow standard identifier rules.

A Standard Single-Parameter Example

Before we handle multiple parameters, here’s a refresher on a single-parameter scenario. We’ll manage a list of items that conform to Identifiable, then present them in SwiftUI:

import SwiftUI  
  
struct Product: Identifiable {  
  let id = UUID()  
  let title: String  
}  
  
@Observable  
class GenericController<U: Identifiable> {  
  private var elements: [U] = []  
  
  func insert(element: U) {  
    elements.append(element)  
  }  
  
  func getAll() -> [U] {  
    elements  
  }  
}  
  
struct GenericListView: View {  
  private var controller = GenericController<Product>()  
  
  var body: some View {  
    VStack {  
      Button("Add Product") {  
        let newProduct = Product(title: "Sample")  
        controller.insert(element: newProduct)  
      }  
  
      List(controller.getAll()) { product in  
        Text(product.title)  
      }  
    }  
  }  
}  
  
#Preview {  
  GenericListView()  
}
  • U is forced to conform to Identifiable, which gives each item an id. SwiftUI’s List needs that id for tracking items.
  • The GenericController doesn’t care if it’s storing Product or some other type; it only cares about that Identifiable constraint.
  • This approach saves you from writing multiple controllers for different item types.

Going Further: Multiple Generics in a Custom Container

Now, imagine you want a layout that can stack two views of potentially different types. Maybe you have a header view and a content view, and you’d like to reuse this layout while allowing different kinds of headers and contents. Here’s a SwiftUI structure that demonstrates multiple generic parameters:

import SwiftUI  
  
struct FooBar<Foo: View, Bar: View>: View {  
  let fooView: Foo  
  let barView: Bar  
  
  var body: some View {  
    VStack {  
      fooView  
      barView  
    }  
  }  
}  
  
struct DemoContainer: View {  
  var body: some View {  
    FooBar(  
      fooView: Text("It's going to be sunny today!").font(.headline),  
      barView: Image(systemName: "sun.max")  
        .resizable()  
        .frame(width: 200, height: 200)  
    )  
  }  
}  
  
#Preview {  
  DemoContainer()  
}
  • FooBar is a generic struct that takes two parameters: Foo and Bar, each constrained to View.
  • Instead of using single-letter generics, we name them Foo and Bar for clarity.
  • By passing specific Text views as fooView and barView, we can easily swap in something else (like an Image or a custom View) later without changing the container’s definition.

Best Practices and Pro Tips

  • Start with Minimal Constraints

It’s tempting to pile on multiple constraints, but keeping them lean can help maintain clarity. Add constraints only when you truly need them.

  • Naming Generics

Single-letter names like T or U are typical, though more descriptive names can be helpful if it improves readability. Pick a style that matches your team’s preferences.

  • Avoid Overcomplicating

Generics can get complex if you start nesting them everywhere. Before you do that, ask yourself if a single generic parameter with one or two constraints might be enough.

  • Make Room for Growth

Sometimes you’ll realize you need more constraints later. Don’t stress if you initially keep the class open and then tighten constraints when the need arises. Swift is pretty good at telling you when changes are needed.

  • Protocol Extensions

If you find yourself repeating the same logic in multiple places, consider using a protocol extension. A generic protocol can help you define shared behavior without rewriting everything.

  • Document Your Parameters

When you have multiple generics, clarity is key. Use comments or docstrings to explain what each parameter represents.

Testing the Basics

Unit tests provide that little boost of confidence before pushing code. A quick “happy path” test is a solid place to begin:

  @Test func testInsertProduct() async throws {  
    let controller - GenericController<Product>()  
  
    controller.insert(element: Product(title: "Test Product"))  
    let products = controller.getAll()  
    #expect(!products.isEmpty, "Expected at least one product in the controller")  
  }

This simple test checks if items are added and retrieved as expected. It might not cover unusual scenarios, but it confirms that our generic code works for the most common case.

Common Pitfalls

  • Overusing Generics: It’s easy to get carried away and throw generics into every corner of your code. That can make your codebase harder to understand. Use them mindfully.
  • Excessive Constraints: It’s handy to set rules for your generics, but be careful not to restrict them so much that you lose flexibility.
  • Forgetting Constraints: If you need a method like .sorted(), remember you might need Comparable. Failing to add the right constraints is a quick path to cryptic compiler errors.
  • Type Confusion: Keep your constraints clear. If the compiler complains about missing protocol conformance, adding the right : SomethingProtocol can save time.
  • Type Erasure: If you pass around generic types in contexts that expect a concrete type, you might get stuck. Type erasure is an advanced concept that hides generic details behind a single interface. It’s worth keeping in mind if your code evolves in that direction.
  • Naming Confusion: Overly fancy names might be hard to follow. Stick to something descriptive but concise.

Why This Matters

Sometimes everyone needs a refresher on generics. Interviewers love quizzing developers on these topics, and it’s also helpful when creating clean, scalable architectures in production. Reusable code is less prone to errors, simpler to maintain, and more fun to extend. That’s what generics bring to the table.

If you ever catch yourself repeating logic for multiple types — like writing nearly identical classes for different data models — pause and consider a generic approach. It might save you (and your future self) a headache or two.

So if an interviewer throws a question your way about defining a SwiftUI container with two generics, don’t sweat it. You’ve got this!

If you want to learn more about native mobile development, you can check out the other articles I have written here: https://medium.com/@wesleymatlock

🚀 Happy coding! 🚀

By Wesley Matlock on January 15, 2025.

Canonical link

Exported from Medium on May 10, 2025.

Written on January 15, 2025