I am talking about what I learned by designing APIs for various frameworks and internal modules and my experience in making APIs more robust and how to prevent errors before they ship to the user.
What is an API? • Method • Group of methods • Class • Group of classes • Modules • Everything you see from the outside Higher Level API Class Category Method Method Category Method Method Class Category Method Method Category Method Method
What is an API good for? • Separation of concerns • Hides complex logic from consumers • Abstracts a problem and makes it understandable • Makes a solution reusable for different consumers
General Considerations • API design is more important than having a straight forward implementation • Often these two are interconnected, but if not, choose the better API, not the better implementation • Design your API based on use cases and think about how an ideal API would look like for the use cases in mind • Don’t start with what you have and try to build a nice API wrapper around it
General Considerations • Better copy from the best than having a bad API that is fully yours • Build a team environment where designing APIs is at least as important as writing code
Consider your audience • There are different layers of APIs • These layers may have different consumers • Consider these groups when designing your APIs • A private API might not need to be as rock solid as a public API • It may be okay if some helper method, private to a class, has a horrible API to speed things up, but is it okay for a public method?
Consider your audience • The average experience of a developer using an API might be different based on what the API is for • The newest animation hipster framework • An API to get a 3D model from image data in real time to analyze the traffic around an autonomous vehicle • If you think customer support is bad, you clearly never did any support on GitHub…
Consider your audience /** Renders an image. @note This is a long running, blocking task. Only call from a background queue! !*/ - (UIImage *)renderImage; vs /** Renders an image. !*/ - (void)renderImage:(void(^)(UIImage *))completionHandler
Methods and Classes • The less dependencies the better • The less features the better • Only provide what you really need • Keep the interface of classes small • Group methods together • Think hard about the naming
Methods and Classes • Everything that starts with PSPDFRender… belongs together • Inside these classes, methods don’t have the render… prefix -[PSPDFRenderTask initWithRequest:] -[PSPDFRenderQueue scheduleTask:] Example: Rendering in PSPDFKit PSPDFRenderRequest PSPDFRenderTask PSPDFRenderQueue PSPDF Cache
Methods and Classes • Every class has a very well defined lifetime and purpose • Request: Describes the image • Task: Controls the rendering • Queue: Manages task prioritization Example: Rendering in PSPDFKit PSPDFRenderRequest PSPDFRenderTask PSPDFRenderQueue PSPDF Cache
!// UIGestureRecognizerSubclass.h @interface UIGestureRecognizer (UIGestureRecognizerProtected) @property(nonatomic,readwrite) UIGestureRecognizerState state; !!... @end Example: Hiding as much as possible
Methods and Classes • MyObject.h • MyObject+Internal.h • MyObject+Protected.h • MyObject+Private.h • Use the appropriate visibility attributes in Swift
Methods and Classes • Always prefix category methods – in Swift too! • -[NSString foo_myMethod] / -[NSString fooMyMethod] • If it is an Objective-C class under the hood, Swift will expose these things • If a framework’s class adds a method: • In your subclass it breaks your subclass • In a category it breaks every consumer of that method
Nullability • NS_ASSUME_NONNULL all the things • Use nonnull as much as possible • e.g. collections almost never need to be nullable. An empty collection is just fine.
Enforce your API contracts - (void)setFileURL:(NSURL *)fileURL { NSParameterAssert([fileURL isKindOfClass:NSURL.class] !&& fileURL.isFileURL); _fileURL = fileURL; } • People will try to put a string or a remote URL in there! • Enforce things like this early to avoid trouble afterwards
Immutability • Never return mutable structures such as NSMutableArray • Make properties for potentially mutable classes copy their values • Make all properties on a class readonly • Consider creating mutable / immutable pairs if necessary
Immutability • When creating mutable / immutable pairs, remember to make all consumers copy the class! • Homework: Use the Objective-C runtime to automatically create the implementation of a mutable / immutable pair including support for NSCopying, NSMutableCopying, and NSCoding Example: Readonly properties
Thread Safety • An easy approach is: binding the internals of a class to a private queue • read concurrent, write serial dispatch_sync, dispatch_barrier_async • Consider prefixing internal methods: queue_ or barrier_ Makes it almost a no brainer when refactoring
Thread Safety • Make it very clear on what thread a callback arrives - (void)performRequest:(MyRequest *)request queue:(dispatch_queue_t)queue completionHandler:(void(^)(void))block;
Thread Safety • If there is no explicit queue parameter and it is not absolutely clear on what queue a block is called: • As the maintainer of a public API: always call on the main queue • As the consumer of an API: always assume any queue • As the one writing the documentation: lie • “The block is called on an arbitrary queue”
Documenting APIs • Header documentation is not about what the method does internally • Document only what you want to guarantee • What you want to guarantee and what you do might be distinct • Use the wording that Apple is using
Documenting APIs • Guarantees in public APIs are hard to change • Try to prevent breaking changes whenever possible • But don’t cripple your API for that • Provider proper deprecation – most of the time it’s easy • Use semantic versioning Public APIs
Conclusion • If you choose the right grouping and separation of concerns, the problems are by far not as big as they look like • Keeping your APIs small helps to keep the focus of this particular API • Naming things properly helps the consumer of the API and your future self when refactoring things • Immutability helps with designing a proper information flow and it automatically makes your code thread safe • Breaking things into pieces makes things like thread safety much easier
–Me, now Great API design will always pay off. If your APIs are thread safe, immutable, small, simple, and separated you can refactor, optimize, change, iterate, test, and improve things very fast, easy and cost efficient because you don’t need to think – and thinking is where the shit hits the fan.