Chapters

Hide chapters

Expert Swift

Second Edition · iOS 18.5 · Swift 6.1 · Xcode 16.3

14. API Design Tips & Tricks
Written by Marin Bencevic

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Welcome to the last chapter of the book!

You’ve spent most of your time in this book diving into specific topics, learning how they work, writing code to sharpen your instincts around them and working through real-life examples.

Although using Swift and all its incredible capabilities is a wonderful skill to have, it doesn’t help much without actually shipping code with it. More often than not, though, you’ll find yourself creating code that’s used not only by you but also by your team, or even other teams if you’re creating an open-source project.

In those cases, just knowing Swift as a language isn’t enough, and neither is just practicing a specific language feature. That’s why this chapter is going to be a bit different.

In this chapter, you’ll explore a few different topics. Each of these isn’t directly related to the previous one, but they all tie into enhancing your skillset and intuition for designing great APIs. You may freely explore each of these individual topics based on your interest:

  • What developers consider a good API.
  • How to separate and encapsulate your implementation details from your public API using access levels.
  • Powerful language features with examples you can leverage for your APIs, including examples from Swift itself: Literals, Dynamic Member Lookup, Dynamic Callable, Property Wrappers and others.
  • Documenting your APIs using Swift’s powerful markup syntax.
  • Finally, a few important concepts and ideas related to the process of shipping your API to the world.

This chapter will also be less code-heavy than previous ones and won’t require you to copy-paste code or run any project. It’s more of a philosophical and exploratory chapter that doesn’t cover one specific topic. You’re welcome to stop at any point to experiment with a specific idea in Xcode. We’re opening a discussion together, me and you, to hopefully help inspire you with some fresh new ideas and ways of thinking about API design.

Note: API design is a highly opinionated topic. As such, you should take everything in this chapter with a grain of salt and mainly as an inspiration rather than a single truth. Take the portions that make sense for your use case and taste, and discard the ones that don’t.

What do developers want?

Wow, that’s a tough question. I wish I knew, really; it might’ve helped bring this book even further! And yet, some things are obvious and universal for developers.

The first time a developer interacts with a new piece of code, they have certain hopes and expectations. It doesn’t matter if that code is your app and the person is a new developer on your team, or if it’s an open-source library you shared with the community and the person is a new consumer of it.

In essence, developers look for some characteristics that make an API “feel good”:

  • It should be obvious: This means using the API “makes sense” for a developer and that your API’s design aligns with their expectations. For example, a Zip class might have an expected method called unzip or extract rather than pullContentsTo(path:), which is not common or obvious.

  • It should be well-documented: People often say Swift is a self-documenting language and, as such, good APIs don’t need documentation. I personally disagree with that statement. Even though Swift is a very expressive language, documenting the public portions of your API is language-agnostic and crucial to help self-exploration, reduce ambiguity and make sure your intention is clear to the consumer. It would be good for internal APIs to also be documented well, but public-facing documentation is the bare minimum.

  • Reduces mental load: This ties to being obvious but is a bit broader and more open to interpretation. Some things that fall into this category include trying to use the minimum obvious naming for APIs, using prior art if a convention exists in the domain you’re developing for (you might use View instead of Screen if it makes sense in that domain, for example) and using abstractions that are simple to the consumer. As the Swift API guidelines sharply note: “Don’t surprise an expert. Don’t confuse a beginner.”

  • Being modern: This point touches a wide range of topics. Using proper language-specific conventions, leveraging the correct language features a consumer would expect to see and inspiring proper usage and creativity from the consumer are all small parts of this point.

What is the core of your API?

When the Kodeco team works on a tutorial or a book, it always asks: “What is the most important 80 percent of this topic?”

Using access levels properly

Access levels define which entities of your code are exposed and to which scopes they’re exposed. Swift specifically provides a relatively fine-grained set of five access levels (from most permissive to most restrictive): open, public, internal, fileprivate and private.

Internal by default

Every piece of code that doesn’t have an explicit access level set is internal by default. This means other files in the same module can access it, but files outside of the module can’t.

The public world

In cases where internal doesn’t suffice, you’ll want to use either open or public. These levels mean the same thing in essence: This entity is available to every piece of code inside or outside the module it was defined in.

Keeping it private

With public and open representing the more permissive side of the possible access levels, it’s also critical to properly limit access to the private portions of your code. These pieces of code are often implementation details and don’t concern consumers of your public interface or even your internal interfaces.

Finally…

No, this isn’t the end of the chapter, but final has a different meaning you should know. I mentioned that public means an entity is public outside of a module but can’t be overridden and subclassed.

Exploring your interface

A great feature built into Xcode is the ability to view the generated interface of source files. You used this a bit earlier in this book, in the “Objective-C Interoperability” chapter, but you can use the same capability for Swift files, too.

Language features

This section will focus on some interesting language features you can leverage to improve your API surface, and provides short examples of how API designers and developers commonly use them.

Literals

Literals are a great abstraction to let consumers initialize your types using typed literals, such as String, Bool, Array and many others.

Dynamic member lookup

Dynamic member lookup was initially shipped in Swift 4.2 (SE-0195) and meant to provide a somewhat type-safe way to access arbitrary string keys for a type. This was relatively helpful to bridge dynamic languages, such as Python, or create proxy APIs. Unfortunately, it lacked real type-safety when it came to abstracting existing Swift code as well as providing actual runtime safety.

Wrapping types naturally

It’s quite common to create types that would wrap existing types. An example of this might be trying to create your own SearchBar view that wraps a regular UITextField:

Enriching key paths

Exposing or mirroring the key paths of a linked object is extremely useful, but you can return anything you want from the dynamic member subscript method.

Dynamic callable

Dynamic callable was introduced in Swift 5 (SE-0216) to provide syntactic sugar when creating wrappers around dynamic languages/calls inside Swift and allows to naturally invoke values as if they’re functions.

Property wrappers

Property wrappers, introduced in Swift 5.1 (SE-0258), provide a way to abstract the handling of the get/set accessor portions of properties. Some of the common built-in ones are @Published, @State and @Binding, which you used in the Functional Reactive Programming chapter.

Reusing accessor logic

A property wrapper’s primary goal is encapsulating the get/set accessors for properties, both internally for you as a developer and for other people contributing to your codebase. But also, if this sort of abstraction is powerful outside your module, you might want to make it public.

Layering with projection

A somewhat hidden superpower of property wrappers is their projected value. It’s an auxiliary value you can access for the wrapped property using the $ prefix. This feature is heavily used in Combine and SwiftUI.

Documenting your code

As mentioned earlier in this chapter, documenting at least the public-facing portions of your code is crucial for new consumers of your code. Documenting your internal code is just as important because a different kind of consumer (developers) will use it later on.

Symbol documentation

Back in Objective-C days, Apple used a variation of Headerdoc for documentation. Luckily, with Swift, you can now use (almost) full-blown markdown to write documentation. Apple calls this Swift-flavored markdown Markup.

Additional metadata fields

Like parameters, returns or even note, Xcode supports a wide range of metadata fields you can use in your documentation:

iatzug auttuzb zejqrephw yoju heouswe loqro yavfeux eljaqtuat ric ossenivorh teja kobanm mana lalmmajimp okhocaeyn eytuwcenq gcizohrabouw xubbjuhcutuiy biboorib wiwlash QUZOCIS LEQOCUHI WEPLIGI EYRA

Code markers

Aside from symbol-specific documentation, you can also divide your code using code markers. Xcode supports three of these: MARK, TODO and FIXME:

Publishing to the world

In this section, you’ll explore some ideas and important tidbits about how to release a library or other piece of code to the outside world. It doesn’t matter if you’re open-sourcing your code to the entire world or publishing it as an internal library in your company, some guidelines exist that you should follow to make fellow developers’ lives easier.

Versioning

When writing code for yourself, versioning doesn’t matter much. But as soon as a piece of code is bundled into some reusable dependency and consumed by other developers, versioning becomes quite critical in ensuring consistency and expectability.

Deprecation

In the lifetime of every piece of code, you might need to retire some classes or methods. First, you should almost always bump the major version of your framework if you deprecate a piece of code because it means you’re making a breaking change to your API contract with your consumer.

Key points

Great work on finishing this chapter!

Where to go from here?

As mentioned at the beginning of this chapter, API design is the subject of many opinions. As such, the best way to gain intuition as to what you like is to learn as much as possible about different perspectives, combined with the official guidelines from Apple, and experiment with different creative options! There is usually more than a single way to expose functionality, and the process of finding the right API is part of the fun of designing code!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now