Classes and Objects
|
This section documents modern Objective-C as implemented by Apple Clang in the current Xcode release — Objective-C 2.0 plus ARC, literals and subscripting, This content was generated with the assistance of AI and should be verified against developer.apple.com before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
An Objective-C class is declared in two halves: an @interface that says what the class offers, conventionally
in a .h file, and an @implementation that says how, in the matching .m. Clients import the header and see
only the interface. This split is not merely stylistic — it is the language’s encapsulation mechanism, since
there are no access modifiers on methods.
@interface and @implementation
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
- (NSString *)greeting;
+ (instancetype)personWithName:(NSString *)name;
@end
// Person.m
#import "Person.h"
@implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
_name = [name copy];
_age = age;
}
return self;
}
- (NSString *)greeting {
return [NSString stringWithFormat:@"Hello, %@!", self.name];
}
+ (instancetype)personWithName:(NSString *)name {
return [[self alloc] initWithName:name age:0];
}
@end
The first line of the interface, @interface Person : NSObject, names the class and its superclass. Almost
every class descends — directly or transitively — from NSObject.
A method that appears only in the @implementation is effectively private: it exists and can be called, but
no client that imports the header knows its name. The compiler no longer requires such methods to be declared
ahead of use, so a "private methods" section in the header is an obsolete habit.
Instance Variables
Instance variables (ivars) hold an object’s state. In modern code you rarely declare them by hand — a
@property synthesises one automatically — but the syntax matters for reading existing code and for the
cases where a property would be misleading.
@interface Person : NSObject {
@private
NSMutableArray *_history; // visible only to Person
@protected
NSInteger _internalVersion; // visible to Person and its subclasses (the default)
@public
NSInteger publicCounter; // visible to anyone: person->publicCounter
@package
NSString *_frameworkOnly; // visible within the same framework/image
}
@end
| Directive | Scope |
|---|---|
|
The declaring class only. |
|
The declaring class and its subclasses. This is the default. |
|
Anyone, via the |
|
Anywhere within the same framework or executable image; |
Declare ivars in the @implementation block (or in a class extension) rather than the header whenever
possible, so they do not appear in the public interface at all:
// Person.m
@implementation Person {
NSMutableArray *_history; // genuinely private: not in the header
}
The leading underscore is a strong convention: it marks the name as the backing storage for a property and
keeps ivar access visually distinct from the self.name property access that should usually be preferred.
@class Forward Declarations
@class Foo; tells the compiler that Foo is a class name without pulling in its header. Use it in headers
whenever only the name is needed — a property type, a parameter type, a return type:
// Order.h
@class Customer; // no #import needed here
@interface Order : NSObject
@property (nonatomic, strong) Customer *customer;
@end
// Order.m
#import "Customer.h" // the real declaration, needed to send messages
This shortens build times and, crucially, breaks circular imports: if Customer.h also refers to Order,
importing both headers from each other would not compile. @protocol Foo; does the same for protocols.
Class and Instance Methods
A leading - declares an instance method, sent to an instance. A leading + declares a class method, sent
to the class object itself:
- (NSString *)greeting; // [person greeting]
+ (instancetype)personWithName:(NSString *)n; // [Person personWithName:@"Ada"]
Inside a class method, self is the class object, not an instance — which is why
[[self alloc] initWithName:…] in a factory method correctly allocates a subclass when the message is sent to
a subclass. Writing [[Person alloc] …] there would hard-code the base class and break subclassing.
Class methods are used for factory ("convenience") constructors, for singletons, and for functionality with no per-instance state. They are inherited like instance methods.
Method Declaration Syntax
- (void)insertObject:(id)object atIndex:(NSUInteger)index;
Reading left to right: - (instance method), (void) (return type), then alternating keywords and
arguments. The method’s name — its selector — is the concatenation of the keywords with their colons:
insertObject:atIndex:. Both of these are also complete selectors:
| Declaration | Selector |
|---|---|
|
|
|
|
|
|
|
|
Because the selector carries only names and arity, there is no overloading by argument type. Two methods
whose selectors are identical are the same method; distinguish them by naming them differently
(initWithName: and initWithData:), which is why Objective-C selectors read so verbosely.
Methods may also be variadic, in the C sense, terminated by nil
([NSArray arrayWithObjects:a, b, nil]) — a pattern largely superseded by the @[…] literal.
Creating Objects: alloc and init
Object creation is always two steps: allocate, then initialise.
Person *p = [[Person alloc] initWithName:@"Ada" age:36];
-
+alloc(fromNSObject) allocates zero-filled memory for the instance and sets itsisapointer. It does not run any of your initialisation. -
-init…populates the instance and returns it — possibly returning a different object than it was sent to, which is why the result must always be used rather than discarded.
Never send init twice to the same object, and never use an object between alloc and init.
Designated and Convenience Initializers
A class picks exactly one designated initializer: the one that does the real work and is the only one that calls `super’s designated initializer. Every other initializer is a convenience initializer that funnels into it.
// Person.h
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithName:(NSString *)name; // convenience
- (instancetype)init NS_UNAVAILABLE; // this class requires a name
// Person.m
// Designated: calls super's designated initializer, then sets up all state.
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
_name = [name copy];
_age = age;
}
return self;
}
// Convenience: calls *this* class's designated initializer, not super's.
- (instancetype)initWithName:(NSString *)name {
return [self initWithName:name age:0];
}
The self = [super init]; if (self) { … } return self; shape is not ceremony. [super init] may return nil
(initialisation failed) or a substituted object (as class clusters do), and assigning the result is what keeps
the rest of the method operating on the right instance.
Marking the designated initializer with NS_DESIGNATED_INITIALIZER turns the rule into a compiler-checked
one: Clang then warns if a subclass’s designated initializer fails to chain to it, or if a convenience
initializer calls super instead of self. NS_UNAVAILABLE removes an inherited initializer that would
leave the object in an invalid state.
+new is exactly [[Class alloc] init]. It is fine for classes with a meaningful plain init, but cannot
pass arguments, so most classes use alloc/init… or a factory method instead.
Initialisers may fail by returning nil — but release nothing and do nothing else first:
- (instancetype)initWithURL:(NSURL *)url {
self = [super init];
if (self) {
if (!url.isFileURL) {
return nil; // under ARC, self is cleaned up automatically
}
_url = url;
}
return self;
}
self and super
self is the receiver of the current message — an implicit parameter of every method, along with _cmd, the
selector being executed. super is not an object: it is a directive meaning "start the method lookup at my
superclass instead of at my own class", with self unchanged as the receiver.
- (NSString *)description {
return [NSString stringWithFormat:@"%@ (name=%@)",
[super description], // NSObject's implementation, still on this instance
self.name];
}
Inside an initializer, prefer direct ivar assignment (_name = …) to property access (self.name = …): a
subclass may have overridden the setter, and calling an override on a half-initialised object is a well-known
hazard. The same applies in dealloc.
dealloc
dealloc is sent when an object’s last strong reference goes away. Under ARC you implement it only to release
resources ARC does not know about, and you never call [super dealloc] — the compiler inserts it.
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_connection invalidate];
CFRelease(_cfObject); // CoreFoundation object: still manual
// no [super dealloc] under ARC
}
Never resurrect self from dealloc, and do not rely on it running at a predictable moment. Memory
management proper is covered in
Automatic Reference Counting.
NSObject, the isa Pointer and description
NSObject is the root class of virtually every Objective-C class hierarchy (the only common alternative is
NSProxy). It supplies allocation, lifetime, equality, hashing, introspection and message forwarding — the
machinery that makes an instance a first-class participant in the run time rather than a bare struct.
Every instance begins with an isa pointer to its class object; the class object holds the method list, the
ivar layout and a pointer to the superclass. That single indirection is what objc_msgSend follows on every
message send, and what makes class introspection possible at run time:
Person *p = [[Person alloc] initWithName:@"Ada" age:36];
[p class]; // Person -- the class object
[p isKindOfClass:[NSObject class]]; // YES -- this class or any ancestor
[p isMemberOfClass:[Person class]]; // YES -- exactly this class
[p respondsToSelector:@selector(greeting)]; // YES
NSStringFromClass([p class]); // @"Person"
The details of isa, metaclasses and the dispatch path are in
The Objective-C Runtime.
description
NSObject’s `description returns <ClassName: 0xaddress>. Override it to make %@, NSLog and the
debugger’s po useful:
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, name=%@, age=%ld>",
NSStringFromClass([self class]), self, self.name, (long)self.age];
}
A companion method, debugDescription, is what po prefers; by default it calls description. Override it
separately when the debugger should show more detail than a log line.
See Also
-
Properties and Encapsulation —
@property, autosynthesis and the accessor conventions. -
Messaging and Selectors — what
[receiver message]actually does. -
Inheritance and Polymorphism — subclassing,
isEqual:/hashand class clusters. -
Categories and Extensions — adding methods and declaring truly private interfaces.