Skip to main content
Objective-C, despite being a powerful language for Apple platforms, has several common anti-patterns that can lead to memory leaks, performance issues, and maintainability problems. Here are the most important anti-patterns to avoid when writing Objective-C code.
Always use Automatic Reference Counting (ARC) in new Objective-C projects. ARC automatically manages memory for Objective-C objects, reducing the risk of memory leaks and crashes due to over-releasing or accessing deallocated objects.
Avoid creating strong reference cycles (retain cycles) where two objects hold strong references to each other. Use weak or unsafe_unretained properties for back-references to break these cycles. This is especially important in delegate patterns and parent-child relationships.
Use properties instead of direct instance variable access and manual getter/setter methods. Properties provide automatic memory management, KVC/KVO compliance, and make your code more concise and less error-prone.
Use modern Objective-C syntax features like literals for NSArray, NSDictionary, and NSNumber, subscripting for collections, and boxed expressions. These make your code more concise, readable, and less error-prone.
Avoid creating massive view controllers that handle too many responsibilities. Use patterns like MVVM (Model-View-ViewModel), MVP (Model-View-Presenter), or VIPER to separate concerns and make your code more modular, testable, and maintainable.
Use blocks (closures) for asynchronous callbacks instead of delegate patterns for simple operations. Blocks make the code more concise and keep related code together, improving readability and maintainability.
Use the designated initializer pattern to ensure proper object initialization. Have one primary initializer that all other initializers call. This ensures consistent initialization and makes your code more maintainable.
Use class extensions (anonymous categories) in implementation files to declare private methods and properties. This keeps your public interface clean and focused on what clients of your class need to know, while hiding implementation details.
Use nullability annotations (nullable, nonnull, null_unspecified, null_resettable) to clarify whether properties and method parameters/return values can be nil. This improves code clarity, enables better Swift interoperability, and helps catch nil-related bugs at compile time.
Use lightweight generics for collection types (NSArray, NSDictionary, NSSet) to specify the types they contain. This improves type checking, enables better autocompletion, and makes your code more self-documenting.
Use categories and extensions appropriately. Use categories to add methods to existing classes without subclassing. Keep core functionality in the main class, and use categories for grouping related utility methods. Be aware that categories cannot add instance variables (only properties backed by associative references).
Use NS_ENUM or NS_OPTIONS for enumerated constants instead of #define or global constants. Enumerations provide type safety, better autocompletion, and make your code more readable and maintainable.
Use instancetype instead of id as the return type for initializers and factory methods. This provides better type checking and enables the compiler to know the actual return type, improving autocompletion and catching errors at compile time.
Use appropriate memory management semantics for properties. Use copy for immutable objects like NSString to prevent unexpected changes, weak for delegates and other references that shouldn’t own the object, and expose mutable collections as readonly with private mutable versions.
Use appropriate error handling mechanisms. For methods that can fail, use the NSError pattern (taking an NSError** parameter) to provide detailed error information. This allows callers to understand why an operation failed and take appropriate action.
Use appropriate concurrency patterns. Don’t block the main thread with long-running operations. Use Grand Central Dispatch (GCD) or NSOperationQueue for concurrent operations. Always update the UI on the main thread. Consider using higher-level abstractions like NSURLSession for network operations.
Use proper documentation for your classes and methods. Include information about parameters, return values, exceptions, and any side effects. Good documentation makes your code more maintainable and easier for others (and your future self) to understand and use correctly.
Use appropriate testing patterns like dependency injection to make your code testable. Avoid singletons or provide a way to inject dependencies. Write code that can be easily mocked and tested in isolation.