Objective-C is a programming language with dynamic message dispatch, created by Brad Cox in the 1980s. Apple chose Objective-C as the primary language for NeXTSTEP, and later for the iOS SDK. Programming With Objective-C — an introduction to message syntax and memory management.
Key Takeaways
Objective-C is a programming language that extends C with object-oriented programming capabilities in the Smalltalk style. Objective-C code is compiled via LLVM or GCC into native machine code, maintaining full backward compatibility with C — any C code is valid in Objective-C.
Apple acquired NeXT (along with Objective-C) in 1997. The language became the foundation of Cocoa and Cocoa Touch — frameworks for macOS and iOS. Since 2014, Apple has been promoting Swift as a replacement, but Objective-C remains critically important for legacy projects and some system frameworks. According to Apple (WWDC 2024), about 35% of apps on the App Store still contain Objective-C code.
The key feature is the dynamic runtime. Unlike Swift, where method calls are resolved at compile time, Objective-C sends messages at runtime through the objc_msgSend function. This allows overriding methods on the fly (method swizzling), dynamically adding classes, and using forward invocation for non-existent selectors.
Any C code is valid in Objective-C. The object-oriented extension adds classes (@interface/@implementation), categories, protocols (@protocol), and dynamic typing (id). Files have .m (implementation) and .h (headers) extensions.
Message syntax is the main difference between Objective-C and C-like languages. Instead of object.method(argument), [object method:argument] is used. Each message passes through objc_msgSend, which dynamically looks up the method implementation at runtime.
// Message syntax with named parameters
NSString *greeting = [NSString stringWithFormat:@"Hello, %@", name];
// Nested messages
NSArray *sortedArray = [[array sortedArrayUsingSelector:@selector(compare:)] copy];
// Type checking via introspection
if ([object isKindOfClass:[UIView class]]) {
UIView *view = (UIView *)object;
view.backgroundColor = [UIColor redColor];
}Dynamic dispatch allows overriding methods at runtime (method swizzling) — a powerful but dangerous technique. For example, frameworks like AFNetworking and Aspects use swizzling to intercept URLSession calls. Apple warns: swizzling can break system frameworks if used incorrectly.
Categories are a unique feature of Objective-C that allows adding methods to existing classes (including system classes like NSString and UIView) without inheritance and without access to source code. Categories are declared using @interface ClassName (CategoryName).
// UIColor+Hex.h file — category for UIColor
@interface UIColor (Hex)
+ (instancetype)colorWithHex:NSUIntegerhex;
@end
// UIColor+Hex.m file — implementation
@implementation UIColor (Hex)
+ (instancetype)colorWithHex:NSUIntegerhex {
CGFloat r = ((hex >> 16) & 0xFF) / 255.0;
CGFloat g = ((hex >> 8) & 0xFF) / 255.0;
CGFloat b = (hex & 0xFF) / 255.0;
return [self colorWithRed:r green:g blue:b alpha:1.0];
}
@endExtensions (Class Extension) are a special type of category declared in a .m file without a name: @interface ClassName (). Unlike categories, extensions can add not only methods but also ivar (instance variables) and properties. Extensions are used to hide internal API from external modules.
Blocks are anonymous functions in Objective-C that capture variables from the outer scope. Block syntax: ^(int x) { return x * 2; }. Blocks are used in UIKit for callback handlers, in Grand Central Dispatch for asynchronous tasks, and in NSArray/NSDictionary collections for functional operations.
// Block type declaration
typedef void (^CompletionBlock)(BOOL success, NSError *error);
// Block as parameter
- (void)fetchDataWithCompletion:(CompletionBlock)completion {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL result = [weakSelf processData];
if (completion) {
completion(result, nil);
}
});
}Memory management in blocks is critically important. Blocks capture self with a strong reference, creating a retain cycle when used directly. The solution is __weak typeof(self) weakSelf = self, followed by a check inside the block. This problem is fully solved in Swift through capture lists [weak self].
Objective-C evolved from MRC (Manual Reference Counting) to ARC (Automatic Reference Counting). In MRC, the developer manually called retain (increment counter), release (decrement), and autorelease (deferred release). Errors in these calls led to memory leaks or crashes due to dangling pointers.
| Operation | MRC | ARC |
|---|---|---|
| Object creation | [[Object alloc] init] | [[Object alloc] init] |
| Reference holding | [object retain] | Automatically |
| Release | [object release] | Automatically |
| Autorelease | [object autorelease] | Automatically (not required) |
| Weak reference | __weak did not exist | __weak (automatic nil) |
ARC was introduced in Xcode 4.2 and LLVM 3.0 (2011). The compiler automatically inserts retain/release at compile time by analyzing object lifetimes. ARC is not garbage collection — it is static analysis with automatic insertions. Objective-C ARC is compatible with Swift ARC: both use the same reference counting system at the runtime level.
Using Objective-C and Swift together in one project is common practice for legacy projects. Apple provides the Bridging Header — an automatically generated file through which Swift sees Objective-C classes, and Objective-C sees Swift classes that inherit from NSObject.
// ProjectName-Bridging-Header.h
// Swift sees these headers automatically
#import "LegacyManager.h"
#import "NetworkClient.h"
#import "DataStore.h"Migration from Objective-C to Swift is a gradual process. New files are written in Swift, old ones are gradually refactored. The Bridging Header is automatically generated when the first Swift file is added to an Objective-C project. For reverse visibility (ObjC → Swift), Xcode generates a <ProjectName>-Swift.h file containing @interface declarations for Swift classes marked with @objc.
// Swift class visible from Objective-C
@objc class SwiftRouter: NSObject {
@objc func navigateToProfile(userId: Int) {
// implementation
}
}Limitations: Swift value types (struct, enum) are not directly visible from Objective-C — they need to be wrapped in a class with @objc. Swift generics have limited accessibility from Objective-C. The recommended approach is to write new code in Swift and refactor existing Objective-C code only when changing the corresponding functionality.
The main advantage of Objective-C over Swift is full access to the runtime. Method swizzling, replacing method implementations at runtime, allows overriding system methods without inheritance. Libraries like Aspects and JRSwizzle use this capability for AOP (aspect-oriented programming) — monitoring, analytics, and logging.
Forward invocation is another runtime feature: if an object does not respond to a selector, the system calls forwardInvocation:, allowing the message to be forwarded to another object. This is the foundation of the Proxy pattern in Objective-C (NSProxy), which is used for lazy initialization and distributed objects.
Apple recommends minimizing the use of runtime tricks in new code, preferring Swift's static typing. However, in legacy projects, knowledge of objc_msgSend, method_exchangeImplementations, and objc_getAssociatedObject is necessary for maintaining the existing codebase.
Objective-C supports properties as syntactic sugar over ivar with getters/setters. The atomic/nonatomic, strong/weak/copy, readonly/readwrite, and assign/retain modifiers determine memory behavior and thread safety. Key-Value Observing (KVO) is a mechanism for observing property changes, built into the runtime: any object can subscribe to changes in another object's properties via addObserver.
Frequently Asked Questions
A method call looks like [object selector:argument]. The message passes through objc_msgSend, which dynamically finds the implementation at runtime. A selector is a method name (@selector(methodName)), not a function pointer. This enables swizzling and forward invocation.
A Category (@interface ClassName (Name)) adds methods to any class (including system classes) without inheritance. An Extension (@interface ClassName ()) is declared in a .m file and can add properties and ivars. A Category cannot add ivars, but an Extension can.
Blocks are anonymous functions with the syntax ^(parameters) { body }, capturing variables from the context. They are analogous to lambdas in C++ and closures in Swift. They require __weak to prevent retain cycles when capturing self. They are used in UIKit, GCD, and Foundation.
MRC is manual reference counting: the programmer calls retain, release, autorelease. ARC is automatic: the compiler inserts retain/release based on static analysis. ARC is not GC — objects are released immediately when the counter reaches zero. ARC supports __weak and __strong modifiers.
Yes, via a Bridging Header. Swift sees Objective-C through this header, Objective-C sees Swift through <ProjectName>-Swift.h. Swift classes must inherit from NSObject and be marked with @objc. Swift structs and enums are not directly visible to Objective-C.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also