Objective-C & Cocoa the language, the runtime and the frameworks · 593 entries indexed

C with a Smalltalk message layer bolted on, and forty years of framework built on top of it. This sheet assumes you know what a vtable, a reference count and a trampoline are, and spends its space on what is actually peculiar: dispatch through objc_msgSend, the ownership contract ARC infers from method names, the class clusters and two-stage initialisation that make Foundation look stranger than it is, toll-free bridging, and what still bites in a mixed Swift codebase. Cards 1–34 are the guide; the rest is a filterable index of directives, runtime functions, and the Foundation and AppKit/UIKit classes worth knowing. Press / to jump to the filter box; hover any name for its signature.

Where it lives: language & runtime Foundation AppKit / UIKit C API · Core Foundation · GCD deprecated / legacy
Source: Apple’s Objective-C runtime and Foundation / AppKit / UIKit reference documentation, the clang Objective-C language, ARC and literals specifications, and the Cocoa fundamentals and coding-guideline documents. Checked against clang on macOS 26, 64-bit modern runtime. Hover any name for its signature and notes.

An Objective-C Programmer’s Guide

The language, the runtime and the Cocoa frameworks — written for someone who already knows what a vtable is

What Objective-C Is

C + Smalltalk

A strict superset of C with one idea added: Smalltalk-style message passing, resolved by a runtime library rather than by the linker. Every C construct still works and means what it always meant; everything Objective-C adds is either a new punctuation mark (@, [ ], ^) or a call into libobjc.

FactConsequence
Brad Cox & Tom Love, 1984; licensed by NeXT 1988; Apple by acquisition, 1997The framework API is a 1990s NeXTSTEP design that has been kept source-compatible for thirty years. That explains the NS prefix and most of the odd corners.
Method calls are messages looked up by name at send timeNo vtables, no devirtualisation, no inlining across a send. You can add, swap and forward methods while the program runs.
Classes are themselves objects+ (class) methods are just messages to the class object; metaclasses close the loop.
Single inheritance, from NSObjectProtocols carry the multiple-inheritance load; categories carry the mix-in load.
No namespaces, no operator overloading, no templatesTwo-and-three-letter class prefixes (NS, UI, CA, AV) are the namespace mechanism.

Files and dialects

.hheader — interfaces, shared by C, ObjC and ObjC++.mObjective-C implementation.mmObjective-C++ — C++ and ObjC objects in one file.pchprefix header, precompiled; Xcode-era, now discouraged in favour of modules

The three runtimes, of which one matters

The legacy (fragile) runtime was 32-bit macOS: subclass ivar offsets were compiled in, so adding an ivar to a superclass broke every subclass binary. The modern (non-fragile) runtime — everything 64-bit, iOS from the start — fixes ivar offsets at load time. GNUstep’s libobjc2 is the third; nothing here is written for it.

Assume modern runtime + ARC throughout. If a snippet uses retain or declares ivars in the @interface braces, it predates 2011 and is being quoted for archaeology, not style.

Objective-C 2.0 (2007) added

Declared properties, fast enumeration, @optional protocol methods, garbage collection (dead — deprecated 10.8, removed 10.12), and the non-fragile ivar layout. ARC arrived in 2011, literals and subscripting in 2012, nullability and lightweight generics in 2015. Nothing substantial has been added to the language since — the investment went to Swift.

Why read it in 2026

Because AppKit, UIKit, Foundation and Core Data are still Objective-C underneath, every Swift crash report in those frameworks lands in Objective-C frames, and the runtime tricks that make KVO, Core Data faulting, NSUndoManager and every mocking library work have no Swift equivalent.

Building It

clang
clang -fobjc-arc -framework Foundation main.m -o mainthe whole toolchain for a command-line programclang -x objective-c ...force the dialect when the extension liesclang -ObjC -c a.mcompile onlyclang++ -fobjc-arc x.mmObjective-C++xcrun --sdk macosx clang ...pick an SDK explicitlyxcrun --show-sdk-pathwhere the frameworks areclang --analyze x.mrun the static analyser (it understands ARC and CF naming)

Flags that change the language

FlagEffect
-fobjc-arcAutomatic Reference Counting. Per file, so a mixed target is legal.
-fno-objc-arcTurn ARC off for one file that still hand-rolls retain/release.
-fobjc-weak__weak without full ARC (MRR files that still want zeroing weak refs).
-fobjc-arc-exceptionsMake ARC emit cleanup on exception unwind. Off by default in ObjC — an @throw past ARC code leaks.
-fmodulesEnables @import; also makes #import <Foundation/Foundation.h> compile as a module.
-fobjc-exceptionsAllow @try/@throw (on by default for ObjC).
-Wobjc-missing-super-callsHonours NS_REQUIRES_SUPER. Turn it on.
-fobjc-abi-version=2Modern runtime; implied on all Apple 64-bit targets.

#import, not #include

#import is #include with an implicit include-guard, keyed on the resolved file. It is idempotent by construction, so Cocoa headers carry no guards. @import Foundation; goes further: it loads a precompiled module, is order-independent, and links the framework for you.

#import <Foundation/Foundation.h> // umbrella for the whole framework#import "MyClass.h" // quoted = project header@import Foundation; // module (needs -fmodules); also links it@class MyOtherClass; // forward declaration — use in headers
Forward-declare in headers, import in implementations. @class in the .h and #import in the .m keeps compile times sane and breaks the circular imports that two classes referring to each other will otherwise create. @protocol Foo; does the same for protocols.

Frameworks

A framework is a versioned bundle: headers, a dylib, resources, a module map. -framework Foo links it; -F dir adds a search path. The umbrella header (Foo/Foo.h) imports the rest.

Types, id and nil

the vocabulary
TypeIs
idstruct objc_object * — any object. No compile-time checking of messages sent to it; dot syntax is not allowed on it.
instancetypeA contextual keyword meaning “this class”. The correct return type for every initialiser and factory method.
ClassA class object (struct objc_class *). Nil is its null.
SELA uniqued selector — effectively an interned string, not a function pointer. Compare with ==, build with @selector() or NSSelectorFromString().
IMPid (*)(id, SEL, ...) — the actual function that implements a method.
Method, Ivar, Protocol *, objc_property_tOpaque runtime handles.
BOOLbool on arm64 and modern 64-bit; signed char historically. Values YES/NO.

The four kinds of nothing

nilnull object pointer — messaging it is legalNilnull Class pointer; same bits, different documentationNULLnull C pointer — for void *, char *, CFTypeRef[NSNull null]a real singleton object that stands for "nothing" inside collections
Cocoa collections cannot hold nil. [array addObject:nil] raises NSInvalidArgumentException, and @[a, b, c] throws if any element is nil. Use [NSNull null] as the placeholder — and remember it is truthy, so if (obj) does not screen it out. NSDictionary uses setObject:forKey: vs setValue:forKey: differently: the latter removes the key when the value is nil.

Scalars you should use

NSInteger / NSUIntegerlong / unsigned long on 64-bit; the width of a pointerCGFloatdouble on 64-bit, float on 32-bitNSTimeIntervaldouble, secondsNSNotFoundNSIntegerMax — the "no index" sentinel, NOT -1NSRange{NSUInteger location, length}NSComparisonResultNSOrderedAscending / Same / Descending = -1 / 0 / 1unicharuint16_t — one UTF-16 code unit, not one character
NSNotFound is not -1. It is NSIntegerMax. Testing if (idx >= 0) after indexOfObject: is always true and is a classic bug; test if (idx != NSNotFound).

Boxing scalars into objects

@42, @3.14, @YES, @'a'NSNumber literals@(expr)box any scalar or enum expression[NSValue valueWithRange:r]box an arbitrary struct; also CGPoint/CGRect helpers[NSValue valueWithBytes:&s objCType:@encode(MyStruct)]the general formn.intValue, n.doubleValueand back out again

Declaring a Class

@interface / @implementation
// Shape.hNS_ASSUME_NONNULL_BEGIN@interface Shape : NSObject <NSCopying>@property (nonatomic, copy) NSString *name;@property (nonatomic, assign) CGFloat area; // readonly in real life+ (instancetype)shapeWithName:(NSString *)name;- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;- (instancetype)init NS_UNAVAILABLE;- (CGFloat)scaledArea:(CGFloat)factor;@endNS_ASSUME_NONNULL_END
// Shape.m@interface Shape () // class extension: private surface@property (nonatomic, assign, readwrite) CGFloat area;@end@implementation Shape { NSUInteger _cacheGeneration; // private ivar, invisible to clients}+ (instancetype)shapeWithName:(NSString *)name { return [[self alloc] initWithName:name]; // self, not Shape}- (instancetype)initWithName:(NSString *)name { if (self = [super init]) { _name = [name copy]; } return self;}- (CGFloat)scaledArea:(CGFloat)f { return self.area * f; }@end

Reading a method declaration

- (instancetype)initWithName:(NSString *)name andAge:(NSInteger)age;

-instance method. + is a class method(instancetype)return type, in parenthesesinitWithName:andAge:the SELECTOR — the colons are part of the name(NSString *)nameeach argument is typed in parentheses and named

The selector is the concatenation of the keywords with their colons. initWithName:andAge: and initWithName: are unrelated methods. Arity is fixed by the number of colons.

Where ivars can live

PlacementVerdict
@implementation Foo { ... }Right. Private, invisible in the header, no fragile-base-class issue.
Class extension @interface Foo () { ... }Fine; equivalent.
@interface Foo : NSObject { ... }Legacy. Publishes your layout to every client.
Auto-synthesised _nameWhat you actually get from @property. Prefer it.

Visibility directives (ivars only)

@privatethis class only@protectedthis class and subclasses — the default@publicanyone, via obj->ivar. Do not@packagethis image / framework
Methods have no access control. “Private” means “not declared in the public header”; the runtime will still find and call it, and so will anyone with performSelector:. Prefix genuinely internal methods to avoid collisions, and never assume inaccessibility is a security boundary.

Sending Messages

objc_msgSend

Every [receiver selector] compiles to a call to objc_msgSend(receiver, @selector(...), ...) (or a sibling for struct/float returns on some ABIs). That function walks the receiver’s class for a method matching the selector, consults a per-class cache first, and jumps to the found IMP with the original arguments and stack frame intact. Failing that it climbs the superclass chain, then enters the forwarding machinery.

[obj doThing]objc_msgSend(obj, @selector(doThing))[obj setX:1 y:2]objc_msgSend(obj, @selector(setX:y:), 1, 2)[super doThing]objc_msgSendSuper2(&(struct objc_super){self, class}, ...)[Klass alloc]a message to the class object; same functionobj.name[obj name] — dot syntax is sugar for a message, nothing moreobj.name = @"x"[obj setName:@"x"]

The two hidden arguments

Every method really has the signature ret method(id self, SEL _cmd, ...). Inside any method body self and _cmd are in scope; _cmd is genuinely useful for logging and for generic implementations installed at runtime.

- (void)doThing { NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));}

Messaging nil

Legal, and defined. A message to nil returns zero of the return type: nil for objects, 0 for integers, 0.0 for floats, and a zero-filled struct. No exception, no crash.

[nil anything]→ nil / 0 / 0.0 / {0}NSUInteger n = [nilArray count];→ 0. Often exactly what you wantif ([nilStr isEqualToString:@"x"])→ NO. Silently, which is the danger
This is the language’s biggest ergonomic gift and its biggest debugging tax. Whole call chains evaporate silently when one link is nil. When a computation produces nothing and you cannot tell why, the first hypothesis is a nil receiver in the middle, not a wrong algorithm.

super is not an object

super is a compiler directive, not a pointer. [super foo] starts the method search at the superclass but still passes self as the receiver. So a [super description] inside an override reaches the parent implementation, while [self description] from the parent’s code reaches the override.

Dynamic dispatch by hand

[obj respondsToSelector:@selector(foo:)]ask before you send — the delegate idiom[obj performSelector:sel withObject:x]up to two object args; ARC warns (unknown ownership of the result)((void(*)(id,SEL,int))objc_msgSend)(o,s,3)the ARC-safe escape: cast msgSend to the real signatureIMP imp = [obj methodForSelector:sel];cache the implementation for a hot loop[obj isKindOfClass:[Foo class]]Foo or a subclass[obj isMemberOfClass:[Foo class]]exactly Foo
Cost. A cached send is a handful of instructions — more than a C call, far less than anything you will notice outside a tight inner loop. Micro-optimise it with a cached IMP only after Instruments says to. __attribute__((objc_direct)) on a method (or objc_direct_members on a category) compiles it as a plain C function: no dispatch, no swizzling, no overriding, invisible to the runtime.

When Dispatch Fails

forwarding

An unhandled selector is not an immediate crash. The runtime offers three escape hatches, in order, and only then raises. Each one is a supported extension point, and everything from NSProxy-based mocks to Core Data faulting lives here.

StageHookCost
1. Dynamic resolution+resolveInstanceMethod: / +resolveClassMethod: — add an IMP with class_addMethod and return YES; the send is retried and permanently fast afterwards.Once
2. Fast forwarding-forwardingTargetForSelector: — return another object and the message is re-sent to it. Cheap: no NSInvocation is built.Low
3. Full forwarding-methodSignatureForSelector: (must return non-nil) then -forwardInvocation:, which receives a reified NSInvocation you can inspect, mutate, store, or send anywhere.High
4. Failure-doesNotRecognizeSelector: raises NSInvalidArgumentException: unrecognized selector sent to instance 0x…
- (id)forwardingTargetForSelector:(SEL)sel { if ([_realThing respondsToSelector:sel]) return _realThing; return [super forwardingTargetForSelector:sel];}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [_realThing methodSignatureForSelector:sel] ?: [super methodSignatureForSelector:sel];}- (void)forwardInvocation:(NSInvocation *)inv { [self log:inv.selector]; [inv invokeWithTarget:_realThing]; // or setReturnValue:, or drop it}
Forwarding does not change respondsToSelector:. A proxy that forwards -length will still answer NO to [proxy respondsToSelector:@selector(length)] unless you override that too — and Cocoa asks that question constantly before it sends optional delegate messages. Override respondsToSelector: and conformsToProtocol: alongside.

NSProxy

The other root class. It implements almost nothing but forwardInvocation: and methodSignatureForSelector:, which are abstract — you must supply both. Subclass it, rather than NSObject, when you want every message forwarded, including the ones NSObject would otherwise answer itself.

NSInvocation

+invocationWithMethodSignature:build one from scratch-setTarget: -setSelector:who and what-setArgument:atIndex:index 0 is self, 1 is _cmd, 2 is the first real argument-retainArgumentsESSENTIAL if the invocation outlives the call; it does not retain by default-invoke / -invokeWithTarget:send it-getReturnValue:into a buffer of the right size

Properties

@property

A property is a declaration of accessors, not of storage. @property NSString *name; declares -name and -setName:; the compiler then auto-synthesises an ivar _name and both bodies unless you supply them.

@property (nonatomic, copy) NSString *title;the standard string property@property (nonatomic, strong) NSView *view;owning object reference@property (nonatomic, weak) id<FooDelegate> delegate;the delegate rule — always weak@property (nonatomic, assign) NSInteger count;scalar; assign means "just store the bits"@property (nonatomic, copy) void (^handler)(void);blocks are ALWAYS copy@property (nonatomic, readonly) CGFloat area;getter only in the header@property (class, nonatomic, readonly) Foo *shared;class property — never auto-synthesised

Every attribute

AttributeMeaning
strong (default for objects)Retains the new value, releases the old. Was retain.
weakDoes not retain; zeroed automatically when the target deallocates. Never for delegates’ storage duration guarantees, always for the delegate itself.
copyStores [value copy]. Mandatory for NSString, NSArray, NSDictionary and blocks.
assign (default for scalars)Raw store. On an object under ARC this is __unsafe_unretained: a dangling pointer waiting to happen.
unsafe_unretainedExplicit non-zeroing weak reference. For objects that do not support weak refs (a handful of classes) or for performance.
atomic (default) / nonatomicAtomic wraps the accessors in a spinlock so a read never sees a half-assigned pointer. Everyone writes nonatomic because atomic costs and buys almost nothing.
readonly / readwriteGetter only / both (default). Redeclare readwrite in the class extension for the internal setter.
getter= / setter=Rename the accessors. getter=isHidden is the Cocoa convention for BOOLs.
nullable / nonnull / null_resettableNullability, which Swift imports as optionality.
classClass-level property. You must write both accessors; there is no ivar to synthesise.
directNon-dispatched accessors (clang extension). Fast, unswizzlable, invisible to KVO.
atomic is not thread safety. It guarantees one accessor call is not torn. It says nothing about a read-modify-write across two calls, nor about the object’s own invariants: self.count = self.count + 1 races just as freely on an atomic property.
strong on an NSString or NSArray is a bug. The caller can hand you an NSMutableString, keep its own reference, and mutate your state behind your back. copy on an already-immutable object is just a retain, so it costs nothing when it is not needed.

@synthesize and @dynamic

@synthesize title = _title;explicit; only needed to rename the ivar or when you wrote BOTH accessors@dynamic title;"the accessors exist at runtime" — silences the compiler. Core Data's whole model

Auto-synthesis stops if you implement both accessors of a readwrite property (or the getter of a readonly one), or if the property comes from a protocol — in those cases the ivar is not created and you must declare it or @synthesize it yourself.

Do not use accessors in init or dealloc. A subclass override would run against a half-built or half-torn-down object. Assign the ivar directly: _title = [t copy];.

Memory: The Contract

ARC and the naming rule

Objective-C has always been reference counted. ARC did not change the semantics; it made the compiler write the retain/release calls, and it inferred the rules from a naming convention that predates it by twenty years. That convention is still the contract, and you must obey it in your own method names or ARC will get the counts wrong.

Method name begins withReturns
alloc, new, copy, mutableCopy (and init)+1 — the caller owns it
anything else+0 — autoreleased or otherwise not owned
A method called newToken or copyStrategy that returns an autoreleased object will be over-released by ARC and by the static analyser. Either rename it, or annotate: NS_RETURNS_NOT_RETAINED / NS_RETURNS_RETAINED. This is why Cocoa has -description and not -newDescription.

The manual rules, for reading old code

[obj retain]+1[obj release]-1; deallocs at zero[obj autorelease]-1, deferred to the pool drain[obj retainCount]a lie. Never useful, never trust it

You own what you alloc/new/copy/retain; you must release or autorelease exactly that many times; you must never release what you do not own. Under ARC all four messages are compile errors.

Ownership qualifiers

__strongthe default for every object variable; retains__weakno retain, becomes nil when the target dies (needs runtime support)__unsafe_unretainedno retain, no zeroing — a raw pointer that will dangle__autoreleasingfor out-parameters: (NSError * __autoreleasing *)error

Autorelease pools

@autoreleasepool { for (NSURL *u in thousandsOfFiles) { @autoreleasepool { // inner pool: drains every iteration NSData *d = [NSData dataWithContentsOfURL:u]; // +0, pooled [self process:d]; } }}

Every thread the run loop owns drains a pool per event cycle. A thread you create yourself has none until you make one — a tight loop of +0 factory calls on a bare NSThread or GCD block will grow without bound until it ends. @autoreleasepool is a scope, not an object; the old NSAutoreleasePool is forbidden under ARC.

Retain cycles

ARC is reference counting, not tracing: cycles leak. The three that account for nearly all of them:

parent ↔ childthe child's back-pointer must be weakdelegatealways weak; the delegate usually owns the delegatorblock captures selfand self holds the block — see the weak/strong dance
__weak typeof(self) weakSelf = self;self.handler = ^{ __strong typeof(self) self = weakSelf; // pin it for the duration if (!self) return; // it may already be gone [self doWork];};

dealloc

- (void)dealloc { [NSNotificationCenter.defaultCenter removeObserver:self]; [_timer invalidate]; // no [super dealloc] under ARC — it is a compile error}
dealloc runs on whatever thread released the last reference. It may not be the main thread; do not touch UI in it. Under ARC you do not release ivars — the compiler does — but you must still tear down observers, timers, KVO registrations and CF objects.

Creating Objects

two-stage init

Allocation and initialisation are separate messages, and both can be overridden. +alloc zeroes the instance and sets its isa; -init… establishes the invariants and may return a different object than it was sent to — which is why you always assign the result.

Foo *f = [[Foo alloc] init];the canonical formFoo *f = [Foo new];exactly [[Foo alloc] init]; fine, but cannot pass argumentsFoo *f = [Foo fooWithName:@"x"];convenience factory: +0, autoreleased

The initialiser pattern

- (instancetype)initWithName:(NSString *)name { // DESIGNATED self = [super init]; // may return nil, or a substitute object if (self) { _name = [name copy]; // ivars directly, never self.name } return self;}- (instancetype)init { // CONVENIENCE return [self initWithName:@"untitled"]; // self, not super}
RuleWhy
A class has one (rarely two) designated initialiser; every other initialiser calls it via self.One place establishes the invariants.
The designated initialiser calls the superclass’s designated initialiser via super.Otherwise the inherited state is not set up.
Mark it NS_DESIGNATED_INITIALIZER.The compiler then enforces both rules and warns on violations.
Mark inherited initialisers you do not support NS_UNAVAILABLE.Stops callers reaching -init on a class that needs arguments.
Always self = [super init].The superclass may substitute a different instance (class clusters do this constantly).

Class clusters

NSString, NSArray, NSDictionary, NSNumber and NSData are abstract public faces over private concrete subclasses. [NSString alloc] hands you a placeholder; the initialiser throws it away and returns something whose real class is __NSCFString or NSTaggedPointerString.

You cannot usefully subclass a class cluster by adding ivars — there is no inherited storage and no designated initialiser you can chain to. Either implement the small set of “primitive methods” and provide your own storage (a real and documented, if painful, option), or do what everyone does: compose, wrapping an instance, or add a category. Never write isKindOfClass:[NSString class]-driven logic that assumes the concrete class.

Tagged pointers

Small NSNumbers, short NSStrings and NSDates are not heap objects at all: the value is packed into the pointer, with a low-bit tag telling the runtime to decode rather than dereference. Consequences: retain/release are no-ops on them, object_getClass() is not *(Class *)obj, they never appear in the heap tools, and their addresses look like nonsense in the debugger. Everything through the API still behaves normally.

+load and +initialize

+load+initialize
WhenImage load, before mainFirst message to the class, ever
Inherited?No — every implementation runsYes — a subclass without one runs the parent’s, again
Safe toAlmost nothing; other classes may not be loadedMost things; it runs lazily and thread-safely

Guard +initialize with if (self == [MyClass class]), or a subclass will re-run your setup. +load is where swizzling is traditionally installed, and is a measurable part of app launch time — use it sparingly.

Equality, Hashing, Copying

the value-object contract
a == bpointer identity. For objects, almost never what you mean[a isEqual:b]value equality — the overridable one[a isEqualToString:b]typed fast path; skips the class check. Nil-unsafe in the usual way[a hash]must agree with isEqual:[a compare:b]NSOrderedAscending / Same / Descending

The contract

If [a isEqual:b] then a.hash == b.hash. The converse need not hold. Equality must be reflexive, symmetric and transitive, and hash must not change while the object is in a collection — which is the real reason dictionary keys are copied.

- (BOOL)isEqual:(id)other { if (other == self) return YES; if (![other isKindOfClass:[Point class]]) return NO; Point *p = other; return _x == p->_x && _y == p->_y;}- (NSUInteger)hash { return (NSUInteger)_x ^ ((NSUInteger)_y << 16); }
Override hash whenever you override isEqual:. Forget it and your objects behave correctly with ==, correctly in arrays, and wrongly in every NSSet and NSDictionary — two equal objects land in different buckets and both survive de-duplication. It is the single most common silent bug in hand-written value types.
isKindOfClass: in isEqual: makes equality asymmetric across a subclass boundary (parent equals child, child does not equal parent). isMemberOfClass: or a [self class] == [other class] test is the symmetric choice. Pick deliberately.

NSCopying

- (id)copyWithZone:(NSZone *)zone { // zone is ignored; it is vestigial Point *p = [[[self class] allocWithZone:zone] init]; p->_x = _x; p->_y = _y; return p; // +1: the method is named "copy"}

An immutable class implements copyWithZone: as return self; (a retain) — this is why copy on an NSString is nearly free. A class with a mutable variant implements NSMutableCopying too; -copy then returns the immutable flavour and -mutableCopy the mutable one.

[mutableArray copy]→ an immutable NSArray. NOT an NSMutableArray[array mutableCopy]→ NSMutableArray, always +1, always a real copy[array copy]→ the same object, retained
All Foundation copies are shallow. [arrayOfMutableStrings copy] gives you a new array holding the same mutable strings. For depth: initWithArray:copyItems:YES (one level deep), or archive and unarchive, which is the only genuinely deep copy Foundation offers.

description and debugDescription

-description backs %@ and po; -debugDescription backs po when it exists and defaults to description. Overriding description on your model objects is the highest-return five minutes in this language.

Protocols

@protocol
@protocol Drawable <NSObject> // nearly always inherit NSObject- (void)drawInRect:(CGRect)rect; // @required by default@property (nonatomic, readonly) CGRect bounds;@optional- (void)drawShadow;@required+ (instancetype)defaultInstance;@end
@interface Foo : NSObject <Drawable, NSCopying>adopt, comma-separatedid<Drawable> thing;"any object that draws" — the common formNSView<Drawable> *view;an NSView that also drawsProtocol *p = @protocol(Drawable);the protocol object, for runtime queries[obj conformsToProtocol:@protocol(Drawable)]declared conformance, not behavioural
Always send respondsToSelector: before an @optional method. conformsToProtocol: only says the class declared the protocol; it says nothing about which optional members exist. This is exactly why every Cocoa delegate call site is wrapped in a respondsToSelector: check.

Why protocols carry so much weight here

There is no multiple inheritance, no abstract class keyword, and no generics beyond the lightweight kind. Protocols are therefore the only way to express “these unrelated types share an interface”, and Cocoa leans on them for delegation, data sources, archiving (NSCoding), copying, comparison and collection behaviour (NSFastEnumeration).

The delegate pattern, correctly

@protocol DownloaderDelegate <NSObject>@optional- (void)downloader:(Downloader *)d didFinish:(NSData *)data;- (void)downloader:(Downloader *)d didFailWithError:(NSError *)error;@end@interface Downloader : NSObject@property (nonatomic, weak) id<DownloaderDelegate> delegate; // WEAK@end// at the call site:if ([self.delegate respondsToSelector:@selector(downloader:didFinish:)]) { [self.delegate downloader:self didFinish:data];}

Note the conventions the whole framework follows: the first argument is always the sender, the selector reads as a sentence about what happened, and will…/did…/should… distinguish notice-before, notice-after and permission-asking.

Formal, informal, and class conformance

formal@protocol — checkable, the only kind you should writeinformala category on NSObject; pre-2.0 way of declaring optional methods. ArchaeologyClass<Proto>a class object that conforms — for protocols with + methods@protocol Foo;forward declaration, like @class
Protocols may declare @property. That imposes accessors on the adopter but synthesises nothing — the adopting class must declare a matching property or write the accessors itself.

Categories & Extensions

open classes

A category adds methods to an existing class — including one whose source you do not have. The methods are installed into the real class at load time, so every instance in the process gains them, including ones created by Apple’s code.

// NSString+Trim.h@interface NSString (Trim)- (NSString *)gc_trimmed;@end// NSString+Trim.m@implementation NSString (Trim)- (NSString *)gc_trimmed { return [self stringByTrimmingCharactersInSet: NSCharacterSet.whitespaceAndNewlineCharacterSet];}@end
FormNamedCan add ivarsCompiler checks implementation
Category @interface NSString (Trim)YesNoWarns if missing
Class extension @interface Foo ()NoYesMust be in the class’s own @implementation

The extension is the anonymous category, and it is a genuinely different mechanism: it is compiled as part of the class, so it may add ivars and properties and may promote a readonly property to readwrite. It only works in the class’s own compilation unit — which is precisely why it is the private-interface idiom.

Prefix every category method on a framework class. Two categories that define the same selector on the same class produce a silent, load-order-dependent winner — and Apple may add a method with your name in the next OS release, at which point you have overridden system behaviour by accident. gc_trimmed, not trimmed.
Never override an existing method from a category. There is no super to reach the original, and the outcome across multiple categories is undefined. Subclass, or swizzle deliberately.

Associated objects — the missing ivar

#import <objc/runtime.h>static char kBadgeKey;- (void)setGc_badge:(NSString *)b { objc_setAssociatedObject(self, &kBadgeKey, b, OBJC_ASSOCIATION_COPY_NONATOMIC);}- (NSString *)gc_badge { return objc_getAssociatedObject(self, &kBadgeKey); }
OBJC_ASSOCIATION_ASSIGNunsafe unretainedOBJC_ASSOCIATION_RETAIN[_NONATOMIC]strongOBJC_ASSOCIATION_COPY[_NONATOMIC]copyobjc_removeAssociatedObjects(obj)clears them ALL — including other people's. Almost never right

Associations are released automatically when the object deallocates. The key is compared by pointer, so use the address of a static, never a string literal.

Swizzling

+ (void)load { static dispatch_once_t once; dispatch_once(&once, ^{ Method a = class_getInstanceMethod(self, @selector(viewDidAppear:)); Method b = class_getInstanceMethod(self, @selector(gc_viewDidAppear:)); method_exchangeImplementations(a, b); });}- (void)gc_viewDidAppear:(BOOL)a { [self gc_viewDidAppear:a]; }// that inner call is NOT recursion — it now reaches the original
Swizzling is a debugging and instrumentation tool, not an architecture. It is global, invisible at the call site, order-dependent between libraries, and breaks when Apple changes an implementation. If the method is inherited rather than defined on the class you are swizzling, class_addMethod first or you will silently patch the superclass for every one of its subclasses.

Blocks

closures in C

Blocks are closures with a C ABI: a stack-allocated struct holding a function pointer, a descriptor, and the captured variables. They exist at the C level (clang extension, also usable from plain C), which is why the declaration syntax is C’s function-pointer syntax with ^ for *.

^{ ... }no arguments, inferred void return^(int x) { return x*2; }return type inferred^double(double x) { return x*2; }explicit return typevoid (^handler)(NSError *)a VARIABLE named handlertypedef void (^Completion)(NSData *, NSError *);do this. Always- (void)fetch:(void (^)(NSData *))donea block parameter@property (copy) Completion done;block properties are copy
Read it the way you read C declarators: void (^h)(NSError *) is “h is a block taking NSError * and returning void”. The typedef exists because the nested form — a method returning a block that takes a block — is unreadable.

Capture semantics

VariableCaptured as
Local scalarconst copy at block-creation time. Assigning to it inside is a compile error.
Local object pointerRetained (ARC), const copy of the pointer.
__block variableShared box, promoted to the heap with the block. Readable and writable from both sides.
Ivar (_foo)Captures self, not the ivar. The classic accidental cycle.
static / globalNot captured — referenced directly.
__block NSUInteger total = 0;[array enumerateObjectsUsingBlock:^(NSNumber *n, NSUInteger i, BOOL *stop) { total += n.unsignedIntegerValue; // legal only because of __block if (total > 1000) *stop = YES; // stop is an out-parameter, not a return}];

Three storage classes

_NSConcreteGlobalBlockcaptures nothing — a static singleton, copy is a no-op_NSConcreteStackBlockthe default; DIES when its scope exits_NSConcreteMallocBlockthe result of a copy; heap, reference counted
A block stored beyond its scope must be copied. Under ARC this happens automatically when you assign it to a __strong variable, a copy property, or pass it to a method that retains it — but not when you stash it in a C struct or an array of raw pointers, and not for blocks assigned into __weak or __unsafe_unretained variables. Non-ARC: Block_copy() / Block_release().

The cycle, and the two escapes

// LEAKS: self owns the block, the block retains selfself.completion = ^{ [self reload]; };self.completion = ^{ NSLog(@"%@", _name); }; // same — _name captures self
// weak/strong dance — for blocks that outlive the call__weak __typeof(self) weakSelf = self;self.completion = ^{ __strong __typeof(self) self = weakSelf; [self reload]; };// or, when the object must survive the call, capture strongly on purpose// and break the cycle explicitly: self.completion = nil;
Not every block needs weakSelf. A block passed to dispatch_async, enumerateObjectsUsingBlock: or a one-shot completion handler is released when it runs, so the retain of self is temporary and correct — and a weakSelf there can let the object die mid-flight. The cycle only forms when the object itself holds the block.

Literals & Enumeration

the 2012 sugar
@"text"NSString constant — compiled into the binary, never deallocated@42 @3.14 @YES @'c'NSNumber@(x + y)box any scalar expression, including enums@[a, b, c]NSArray — THROWS if any element is nil@{@"k": v, @"j": w}NSDictionary — key first, C-style colon@[] @{}empty immutable collections@selector(foo:)SEL constant@protocol(Foo)Protocol * constant@encode(CGRect)the type-encoding C string: "{CGRect={CGPoint=dd}{CGSize=dd}}"
Literal collections are immutable. [@[] mutableCopy] or [NSMutableArray array] when you need to mutate. And @[a, b] raises NSInvalidArgumentException the moment one of them is nil — a common crash when building an array from optional values.

Subscripting

arr[3][arr objectAtIndexedSubscript:3]arr[3] = x[arr setObject:x atIndexedSubscript:3]dict[@"k"][dict objectForKeyedSubscript:@"k"]dict[@"k"] = x[dict setObject:x forKeyedSubscript:@"k"]

These are plain selectors: implement them on your own class and it becomes subscriptable. Note that dict[@"k"] = nil removes the key rather than throwing — unlike setObject:forKey:.

Fast enumeration

for (NSString *key in dictionary) { ... } // keys, in unspecified orderfor (id obj in array) { ... } // NSFastEnumeration protocolfor (NSNumber *n in set) { ... }

Backed by -countByEnumeratingWithState:objects:count:, which hands out batches of raw pointers — it is substantially faster than objectAtIndex: in a loop, and adopting the protocol makes your own collections work with it.

Mutating a collection while enumerating it raises NSGenericException: collection was mutated while being enumerated. Collect the changes and apply them after, or enumerate a copy, or iterate an index range backwards when removing.

Block-based enumeration

-enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)-enumerateObjectsWithOptions:usingBlock:NSEnumerationConcurrent | NSEnumerationReverse-enumerateKeysAndObjectsUsingBlock:dictionaries, both at once-indexOfObjectPassingTest:^BOOL(id o, NSUInteger i, BOOL *stop)-indexesOfObjectsPassingTest:→ NSIndexSet-objectEnumerator / -reverseObjectEnumeratorthe older NSEnumerator route

NSEnumerationConcurrent is a genuine parallel-for over the collection — the block must be thread-safe and the ordering of side effects is undefined.

Errors, Not Exceptions

NSError **
The Cocoa rule: exceptions are for programmer error, NSError is for everything that can go wrong at runtime. A file that is missing, a server that is down, a malformed document — those are NSError. An index out of bounds, a nil inserted into an array, an unrecognised selector — those are exceptions, and they mean the program is wrong.

The out-parameter convention

- (BOOL)saveTo:(NSURL *)url error:(NSError **)error;// the ** is really NSError * __autoreleasing *NSError *err = nil;if (![doc saveTo:url error:&err]) { // TEST THE RETURN VALUE, not err NSLog(@"%@ / %@", err.localizedDescription, err.userInfo);}
RuleDetail
The return value signals failureNO or nil. The error pointer is only meaningful then — a method may write to it on success too.
NULL must be acceptedCallers pass NULL when they do not care. Always if (error) *error = ....
The parameter is __autoreleasingImplicit for NSError **. Explicit when you build the type yourself.
Never over-releaseThe caller owns nothing; the error comes back autoreleased.
if (error) { *error = [NSError errorWithDomain:GCErrorDomain code:GCErrorNoDisk userInfo:@{ NSLocalizedDescriptionKey: @"The document could not be saved.", NSLocalizedFailureReasonErrorKey: @"The volume is full.", NSLocalizedRecoverySuggestionErrorKey: @"Free space and retry.", NSUnderlyingErrorKey: posixError, }];}

Standard domains: NSCocoaErrorDomain, NSPOSIXErrorDomain, NSOSStatusErrorDomain, NSMachErrorDomain, NSURLErrorDomain. Define your own as a string constant; codes are an NS_ERROR_ENUM.

Exceptions, for completeness

@try { [self risky];} @catch (NSException *e) { NSLog(@"%@: %@", e.name, e.reason);} @finally { [handle close];}@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"index out of range" userInfo:nil];
ARC does not emit cleanup on the unwind path by default. Without -fobjc-arc-exceptions, every object owned by a frame an exception passes through is leaked. Apple assumes exceptions are fatal and this is a deliberate size/speed trade. So: never use exceptions for control flow, and never assume a caught exception left the process in a usable state — framework code between you and the @throw is not exception-safe either.

Assertions

NSAssert(cond, @"msg %@", x)in a method — uses self and _cmdNSCAssert(cond, @"msg")in a C functionNSParameterAssert(x != nil)the argument-checking idiomNS_BLOCK_ASSERTIONSdefine it to compile them all out (Xcode does this in Release)

A failed NSAssert raises NSInternalInconsistencyException, so it goes through the same machinery. NSSetUncaughtExceptionHandler() installs a last-chance logger before the process dies.

Key-Value Coding

KVC

Indirect access to properties by string name. It is not reflection bolted on for convenience — it is load-bearing infrastructure: Cocoa Bindings, KVO, Core Data, NSPredicate, NSSortDescriptor, scripting support and nib outlet connection are all built on it.

[obj valueForKey:@"name"]→ id; scalars are boxed automatically[obj setValue:@"x" forKey:@"name"]unboxes automatically[obj valueForKeyPath:@"door.lock.serial"]traverse a chain[obj setValuesForKeysWithDictionary:d]bulk set — the JSON-to-model shortcut[obj dictionaryWithValuesForKeys:@[...]]and back[obj mutableArrayValueForKey:@"items"]a PROXY whose mutations fire KVO notifications

How valueForKey:@"name" resolves

In order: -getName, -name, -isName, -_name; then the collection accessors (-countOfName + -objectInNameAtIndex:, which synthesise an array proxy); then, if +accessInstanceVariablesDirectly returns YES (it does by default), the ivars _name, _isName, name, isName; then -valueForUndefinedKey:, which raises NSUnknownKeyException.

KVC reaches ivars directly. Encapsulation is not enforced against it, and a typo in a key string is a runtime exception, not a compile error — which is the price of the whole mechanism. Use NSStringFromSelector(@selector(name)) instead of @"name" and the compiler will at least check the selector exists somewhere.

nil, scalars and undefined keys

setValue:nil forKey:for a SCALAR property calls -setNilValueForKey: — which raises unless you override it-valueForUndefinedKey:override to return nil instead of raising-setValue:forUndefinedKey:the setter side+accessInstanceVariablesDirectlyreturn NO to close the ivar back door

Collection operators

@"@count"[items valueForKeyPath:@"@count"]@"@sum.price"NSNumber, over the whole collection@"@avg.price" @"@min.date" @"@max.date"the rest of the aggregates@"@unionOfObjects.name"flatten to an array of names@"@distinctUnionOfObjects.city"…deduplicated@"@unionOfArrays.tags"flatten an array-of-arrays one level@"@distinctUnionOfSets.tags"same for sets

These compose with ordinary key paths: @"employees.@avg.salary", @"@distinctUnionOfObjects.department.name". NSPredicate and NSExpression speak the same language, which is why a predicate string can aggregate.

KVC is the reason NSDictionary answers valueForKey: with the value for that key: it overrides the whole search. Hence the setValue:forKey: / setObject:forKey: asymmetry — setValue: with a nil value calls removeObjectForKey:.

Key-Value Observing

KVO

Register for change notifications on a KVC-compliant property of any object, without its cooperation. The implementation is the language’s most audacious trick: on first observation the runtime creates a hidden subclass NSKVONotifying_Foo, overrides the setters to bracket them with change notifications, and swizzles the object’s isa pointer to point at it. The object’s class silently changes underneath it, and -class is overridden to lie about it.

static void *kCtx = &kCtx; // a unique address, not a string[account addObserver:self forKeyPath:@"balance" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:kCtx];- (void)observeValueForKeyPath:(NSString *)kp ofObject:(id)obj change:(NSDictionary *)ch context:(void *)ctx { if (ctx != kCtx) { // not ours — pass it up [super observeValueForKeyPath:kp ofObject:obj change:ch context:ctx]; return; } NSLog(@"%@ -> %@", ch[NSKeyValueChangeOldKey], ch[NSKeyValueChangeNewKey]);}- (void)dealloc { [account removeObserver:self forKeyPath:@"balance" context:kCtx];}

Options and change keys

OptionNew / OptionOldinclude the values in the change dictionaryOptionInitialfire once immediately — removes the "set up, then observe" duplicationOptionPriorfire before as well as after (needed for ordered undo)NSKeyValueChangeKindKeySetting / Insertion / Removal / ReplacementNSKeyValueChangeIndexesKeyNSIndexSet, for to-many changes

Making your own properties observable

Setters synthesised by @property are automatically compliant. Anything else you notify by hand:

[self willChangeValueForKey:@"total"];_total = t;[self didChangeValueForKey:@"total"];
// derived property: recompute observers when its inputs change+ (NSSet<NSString *> *)keyPathsForValuesAffectingFullName { return [NSSet setWithObjects:@"firstName", @"lastName", nil];}// or opt a key out of automatic notification entirely:+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { ... }
Removing an observer is mandatory, and removing one twice raises. An object that deallocates while still observed crashes the next time the observed property changes, with a message about a deallocated observer — far from the code that caused it. Always pass a unique context, always remove in dealloc, and never call removeObserver: for a registration you are not sure you made.
The modern alternative. -[NSObject observeValueForKeyPath:…] has no block form in Objective-C; Swift’s NSKeyValueObservation token (auto-removing on deinit) does not bridge back. In Objective-C the practical mitigations are a small wrapper object that owns the registration and unregisters in its own dealloc, or skipping KVO for NSNotificationCenter or an explicit delegate.

Other things KVO breaks on: properties mutated by direct ivar access (no notification), collections mutated without mutableArrayValueForKey:, objc_direct accessors, and objects whose class you also swizzle — the two isa tricks interact badly.

The Runtime API

<objc/runtime.h>

Everything the compiler emits is queryable and mutable at runtime through a plain C API. This is not a back door; it is the documented interface that KVO, Core Data, NSCoder, every mock framework and the debugger are built on.

Introspection

object_getClass(obj)the REAL class — unlike [obj class], KVO cannot lie to itclass_getName(cls)const char *class_getSuperclass(cls)up the chainclass_copyMethodList(cls, &n)Method * — you must free() itclass_copyIvarList / class_copyPropertyListsame shape, same free()class_copyProtocolListdeclared conformancemethod_getName(m)→ SELmethod_getTypeEncoding(m)"v@:i" — return type, self, _cmd, argsivar_getOffset(iv)byte offset; fixed at load under the modern runtimeclass_getInstanceSize(cls)sizeof the instance

Modification

class_addMethod(cls, sel, imp, types)NO if it already exists on THIS classclass_replaceMethod(cls, sel, imp, types)add or replace; returns the old IMPmethod_setImplementation(m, imp)returns the previous onemethod_exchangeImplementations(a, b)swizzling, atomicallyimp_implementationWithBlock(^(id self, ...){ })a block as an IMP — no C function neededobjc_allocateClassPair(super, "Name", 0)build a class at runtime…objc_registerClassPair(cls)…and publish it. No ivars after this pointobject_setClass(obj, cls)reassign isa. This is how KVO works

Type encodings

@encode() and method_getTypeEncoding() speak the same compact language: return type first, then @ (self) and : (_cmd), then the arguments.

c i s l qchar int short long long long (upper case = unsigned)f d B v *float double bool void char *@ # :id, Class, SEL^type [n type]pointer to, array of n{Name=fields} (Name=fields)struct, unionr n o N R Vconst, in, out, inout, bycopy, oneway qualifiers

Names to and from strings

NSClassFromString(@"NSString")→ Class or Nil — the weak-linking idiom for optional frameworksNSStringFromClass([obj class])→ NSString *NSSelectorFromString(@"setX:")→ SEL, registering it if newNSStringFromSelector(sel)→ NSString *NSProtocolFromString / NSStringFromProtocolthe protocol pair
The copy in class_copy…List is literal. Those functions return malloc’d C arrays that ARC knows nothing about — free() them. And a class you create with objc_allocateClassPair can never gain another ivar after objc_registerClassPair.
Undocumented but universally used in lldb: _ivarDescription and _methodDescription on NSObject, and _shortMethodDescription, which prints just the class’s own methods. Debugger only — shipping a call to them is a rejection.

Foundation Value Types

NSNumber to NSURL

Foundation’s value classes are immutable by default with a mutable sibling, are mostly class clusters, and are toll-free bridged to Core Foundation where a CF equivalent exists.

ClassNotes
NSString / NSMutableStringUTF-16 backing store, cluster, bridged to CFStringRef. See the strings card.
NSNumberBoxed scalar. isEqual: compares numeric value across types: @1 equals @1.0 equals @YES.
NSDecimalNumberBase-10, 38 significant digits, exact for money. Arithmetic by message (decimalNumberByAdding:), with an NSDecimalNumberBehaviors rounding policy.
NSValueBoxes any C struct or pointer. NSNumber is a subclass.
NSData / NSMutableDataByte buffer. bytes is the raw pointer; memory-mapped with NSDataReadingMappedIfSafe.
NSDateAn absolute instant: a double of seconds since 2001-01-01 GMT. No calendar, no time zone.
NSUUID, NSNull, NSURLIdentity, the nothing-singleton, and RFC 1808 URLs — which are also the correct type for file paths.
NSLocale, NSMeasurement, NSUnitFormatting culture; typed physical quantities with conversion.

Dates are harder than they look

NSDatean instant. Comparable, arithmetic in secondsNSTimeZoneoffset + DST rulesNSCalendarthe RULES: Gregorian, Hebrew, Islamic…NSDateComponentsa broken-down y/m/d/h/m/s, meaningless without a calendarNSDateFormattertext ↔ date, locale-aware. EXPENSIVE to create — cache itNSISO8601DateFormattermachine formats; do not use NSDateFormatter for thoseNSDateInterval, NSDateComponentsFormatterspans, and "3 hours ago"
NSCalendar *cal = NSCalendar.currentCalendar;NSCalendarUnit u = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;NSDateComponents *c = [cal components:u fromDate:NSDate.date];NSDate *tomorrow = [cal dateByAddingUnit:NSCalendarUnitDay value:1 toDate:NSDate.date options:0];
Never do date arithmetic by adding seconds. Days are not 86400 seconds across a DST boundary, months are not 30 days, and years are not 365. Use NSCalendar, always. And never build a user-facing date string with a fixed format — set dateStyle/timeStyle and let the locale decide; use a fixed format only for machine-readable output, and then set locale to en_US_POSIX or the user’s 12/24-hour preference will corrupt it.

NSNumber equality is looser than you expect

[@1 isEqual:@1.0]YES — numeric comparison across types[@YES isEqual:@1]YES. BOOL is not distinguishable once boxed[@1 isEqualToNumber:@1.0]also YES@(0.1+0.2) vs @0.3NO — it is still IEEE-754 underneath

NSURL, not NSString, for paths

[NSURL fileURLWithPath:p]the conversion; isDirectory: avoids a staturl.path url.lastPathComponent url.pathExtensionthe decomposition[url URLByAppendingPathComponent:@"x"]correct escaping, unlike string concatenation[url getResourceValue:&v forKey:NSURLFileSizeKey error:&e]metadata without a second statbookmarkDataWithOptions:…a path that survives the file being moved or renamed

Strings

NSString is UTF-16

NSString is a sequence of UTF-16 code units, not characters. -length counts code units; a character outside the BMP counts two, and a family emoji built from ZWJ sequences counts a dozen. Indices and NSRanges are in the same units.

@"café".length4 — or 5, if the source is decomposed (e + combining acute)-rangeOfComposedCharacterSequenceAtIndex:expand an index to a whole user-perceived character-enumerateSubstringsInRange:options:usingBlock:NSStringEnumerationByComposedCharacterSequences / ByWords / ByLines-precomposedStringWithCanonicalMappingNFC — normalise before comparing or hashing-decomposedStringWithCanonicalMappingNFD, which is what the file system hands you
Comparing strings from different sources without normalising is a real bug on Apple platforms, because HFS+/APFS historically hand back NFD filenames while your literals are NFC. Same characters, different bytes, isEqualToString: says NO. Normalise, or use compare:options:NSDiacriticInsensitiveSearch-style comparison deliberately.

Building and formatting

[NSString stringWithFormat:@"%@ has %ld", name, (long)n]the workhorse[NSString localizedStringWithFormat:…]same, with locale-aware numbersNSLocalizedString(@"key", @"comment")the .strings lookup; genstrings scans for it[arr componentsJoinedByString:@", "]join[s componentsSeparatedByString:@","]split — and …ByCharactersInSet: for a real tokeniser[s stringByAppendingString:t]O(n); in a loop use NSMutableString

Format specifiers that actually matter

SpecFor
%@Any object — calls -description. Also the only correct one for NSString.
%ld / %lu + a castNSInteger / NSUInteger. Cast to long, or the code breaks on 32-bit.
%zdThe cast-free alternative for NSInteger on Apple platforms.
%d %u %f %g %s %c %%Plain C. %s is a char *, never an NSString.
%pPointer, hex.
%1$@ %2$@Positional — required in localisable strings, where translation reorders the arguments.
NSLog(str) with a non-literal str is a format-string vulnerability exactly as in C. Write NSLog(@"%@", str). Clang’s -Wformat-security catches it; leave it on.

Searching, comparing, transforming

-rangeOfString:options:range:locale:NSCaseInsensitiveSearch, NSBackwardsSearch, NSAnchoredSearch, NSNumericSearch-compare:options:→ NSComparisonResult; localizedStandardCompare: is the Finder ordering-hasPrefix: -hasSuffix: -containsString:the cheap tests-stringByTrimmingCharactersInSet:with NSCharacterSet.whitespaceAndNewlineCharacterSet-stringByReplacingOccurrencesOfString:withString:…options:range: for regex or case-insensitive-stringByApplyingTransform:reverse:ICU transforms: NSStringTransformToLatin, StripDiacritics-uppercaseStringWithLocale:Turkish dotless i is why the locale argument exists

The neighbours

NSMutableStringappendString:, appendFormat:, replaceCharactersInRange:withString:NSAttributedStringstring + ranged attribute dictionaries; the text-system currencyNSRegularExpressionICU regex; NSTextCheckingResult carries the groupsNSScannerhand-rolled parsing; scanUpToString:, scanDouble:NSCharacterSetclass sets and custom ones; NSMutableCharacterSet to buildNSDataDetectoran NSRegularExpression subclass that finds dates, URLs and addresses

Collections

NSArray and friends
ClassSemantics
NSArray / NSMutableArrayOrdered, duplicates allowed, O(1) index. Backed by a circular buffer — insertion at either end is cheap.
NSDictionary / NSMutableDictionaryHash map. Keys are copied and must be NSCopying + hash/isEqual:. Values are retained.
NSSet / NSMutableSetUnordered, unique. containsObject: is O(1) — the reason to prefer it over an array for membership.
NSCountedSetA bag: adds a multiplicity countForObject:.
NSOrderedSet / mutableUnique and ordered. Not a subclass of either parent.
NSIndexSetA compressed set of NSUIntegers stored as ranges. What table views speak.
NSCacheDictionary-like, but evicts under memory pressure and is thread-safe. Does not copy keys.
NSMapTable, NSHashTable, NSPointerArrayConfigurable weak/strong, copy/no-copy, object/pointer. The way to hold weak references in a collection.
Ordinary collections hold strong references. An array of delegates or observers is a retain cycle generator. Use [NSHashTable weakObjectsHashTable] or [NSMapTable strongToWeakObjectsMapTable].

Sorting

[a sortedArrayUsingComparator:^NSComparisonResult(id x, id y){...}]the block form[a sortedArrayUsingSelector:@selector(compare:)]when the objects know how[a sortedArrayUsingDescriptors:@[d1, d2]]NSSortDescriptor: key path + ascending, multi-level[m sortUsingComparator:…]in place, on the mutable variantsNSSortConcurrent / NSSortStableoptions on the …WithOptions: forms

Filtering and predicates

[a filteredArrayUsingPredicate:p]→ new array[m filterUsingPredicate:p]in place[NSPredicate predicateWithFormat:@"age > %@ AND name BEGINSWITH %@", n, s]%@ substitutes a value, %K a key path[NSPredicate predicateWithBlock:^BOOL(id o, NSDictionary *b){...}]arbitrary code; NOT usable by Core Data[a indexesOfObjectsPassingTest:…]the block route without predicates

Predicate format understands == >= BETWEEN IN LIKE MATCHES (regex) BEGINSWITH CONTAINS, the modifiers [c] and [d] for case- and diacritic-insensitivity, ANY/ALL over to-many keys, and the KVC collection operators.

Never build a predicate by string interpolation — it is injection, exactly as in SQL, and Core Data turns predicates into SQL. Use the %@/%K substitutions.

Set algebra and array staples

[s1 unionSet:s2] [s1 intersectSet:s2] [s1 minusSet:s2]mutating; isSubsetOfSet:, intersectsSet: to test[a arrayByAddingObjectsFromArray:b]concatenation, immutable[a subarrayWithRange:r]slice[a valueForKey:@"name"]KVC map: an array of the names. The idiomatic map[a firstObject] [a lastObject]nil-safe, unlike a[0] on an empty array[m removeObjectsAtIndexes:indexSet]the safe bulk remove; removing in a forward loop is not[d objectsForKeys:keys notFoundMarker:NSNull.null]bulk lookup
a[0] on an empty array raises NSRangeException; a.firstObject returns nil. Cocoa collections are consistently fail-fast on out-of-range access and consistently forgiving of nil receivers — the opposite of the C intuition.

Concurrency

GCD and NSOperation

Grand Central Dispatch

dispatch_get_main_queue()serial, bound to the main run loop. ALL UI work goes heredispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)concurrent, system-widedispatch_queue_create("cloud.geocam.io", DISPATCH_QUEUE_SERIAL)your own; reverse-DNS label shows in crash logsdispatch_async(q, ^{ ... })enqueue and returndispatch_sync(q, ^{ ... })enqueue and WAIT — see the warningdispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC), q, ^{})deferreddispatch_barrier_async(q, ^{})on a CONCURRENT queue: exclusive access. The reader/writer lockdispatch_apply(n, q, ^(size_t i){ })parallel for, synchronousdispatch_once(&token, ^{ })exactly once per process; the singleton idiomdispatch_group_async / _enter / _leave / _notify / _waitfan out, joindispatch_semaphore_create/wait/signalcounting semaphore, when you must block
// QoS classes, highest to lowest — they drive scheduling AND energyQOS_CLASS_USER_INTERACTIVE // frame-rate work; almost never yoursQOS_CLASS_USER_INITIATED // the user is waiting, visiblyQOS_CLASS_DEFAULTQOS_CLASS_UTILITY // progress bar, downloadsQOS_CLASS_BACKGROUND // invisible; may be deferred for minutes
dispatch_sync to the queue you are already on deadlocks immediately. The common form is dispatch_sync(dispatch_get_main_queue(), ...) from code that is already on the main thread. There is no re-entrancy check and no diagnostic — the app simply stops. Guard with NSThread.isMainThread, or restructure so the call site knows its queue.
Thread explosion. Blocking (a lock, a semaphore, a synchronous read) inside a block on a concurrent queue makes GCD spin up another thread to keep the queue busy — up to 64 per QoS, then everything stalls. Never block inside a global-queue block; use a serial queue, or async chaining.

NSOperationQueue

[NSOperationQueue mainQueue]the main-thread queue, object-flavouredq.maxConcurrentOperationCount = 1makes it serial — the easy throttle[NSBlockOperation blockOperationWithBlock:^{}]the quick case[op addDependency:other]a DAG of work; GCD has no equivalentop.queuePriority, op.qualityOfServiceordering and QoS[op cancel] / op.isCancelledcooperative — long operations must poll itNSOperation subclassoverride -main, or -start plus the isExecuting/isFinished KVO dance for async work

Choose NSOperationQueue when you need cancellation, dependencies, priorities or KVO on progress; choose GCD for everything smaller and cheaper.

Locking, in descending order of preference

a serial dispatch queuethe modern answer; no lock at allos_unfair_lockthe fast one. NOT recursive, must not be copiedNSLock, NSRecursiveLock, NSConditionobject-flavoured, slower, fine@synchronized(obj) { }recursive, keyed on the object, uses a global table. Convenient, slowOSSpinLockDEPRECATED — priority inversion deadlocks on Apple silicon. Never

The main-thread rule

AppKit and UIKit are not thread-safe. Every view, window and controller message must be sent on the main thread, and violations manifest as corrupted drawing or a crash minutes later rather than an immediate error. Foundation value objects are safe to read from many threads; mutable collections are not safe to mutate concurrently at all.

dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ NSData *d = [self expensiveWork]; dispatch_async(dispatch_get_main_queue(), ^{ self.label.text = d.description; });});

Run Loops, Timers, Notices

the event machinery

The run loop

An NSRunLoop is a per-thread event loop: it blocks in mach_msg until an input source fires, dispatches, and blocks again. The main thread’s is started for you by NSApplicationMain/UIApplicationMain; a thread you create has one but it does not run until you tell it to.

[NSRunLoop currentRunLoop]the calling thread's; created on demand[[NSRunLoop currentRunLoop] run]run forever — needs at least one source or it exitsrunMode:beforeDate:one pass; the way to pump a loop by handNSDefaultRunLoopModeordinary operationNSRunLoopCommonModesa set including tracking modes — what you almost always wantNSEventTrackingRunLoopMode / UITrackingRunLoopModewhile dragging or scrolling
Why your timer stops while the user scrolls. A timer added to NSDefaultRunLoopMode does not fire in tracking mode. Add it to NSRunLoopCommonModes. Same for NSURLConnection-era networking and any performSelector:withObject:afterDelay:.

NSTimer

[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer *t){}]the block form — does not retain a target…target:self selector:@selector(tick:) userInfo:nil repeats:YESRETAINS the target[timer invalidate]the only way to stop it and release the targettimer.tolerance = 0.1let the system coalesce wakeups. Set it; it is real battery[[NSRunLoop currentRunLoop] addTimer:t forMode:NSRunLoopCommonModes]for a non-scheduled timer
A repeating NSTimer retains its target and the run loop retains the timer, so self can never deallocate and dealloc can never invalidate it. Use the block form with a weak self, or invalidate from an explicit teardown method (viewWillDisappear:, windowWillClose:), not from dealloc.

NSNotificationCenter

NSNotificationCenter *nc = NSNotificationCenter.defaultCenter;[nc addObserver:self selector:@selector(changed:) name:GCThingChanged object:thing];[nc postNotificationName:GCThingChanged object:self userInfo:@{@"delta": @1}];// block form — returns a TOKEN you must keep and removeid tok = [nc addObserverForName:n object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification *note) { ... }];[nc removeObserver:tok];
PropertyConsequence
Synchronouspost does not return until every observer has run. It is a fan-out function call, not a queue.
Same thread as the posterA notification posted from a background thread runs your handler there. UI code needs an explicit hop — or the queue: parameter of the block form.
Observers are unretainedUnder the modern runtime the selector form zeroes automatically, but the block form retains everything the block captures and never cleans up. That token must be removed.
object:nilMeans “from any sender”, not “from no sender”. Passing a specific object is the cheap filter.

Deferred and cross-process

NSNotificationQueuecoalesce and post at the end of the run loop passNSDistributedNotificationCentermacOS only, cross-process, string userInfo only, unreliable under load[self performSelector:@selector(x) withObject:nil afterDelay:0]"run at the end of this run loop pass" — the classic layout-timing fix[NSObject cancelPreviousPerformRequestsWithTarget:self]the debounce

Files, Bundles, Defaults

the process’s world

NSFileManager

NSFileManager.defaultManagerthe shared one; make your own for a delegate or a background thread-fileExistsAtPath:isDirectory:racy by nature — prefer just trying the operation-contentsOfDirectoryAtURL:includingPropertiesForKeys:options:error:one level, prefetching metadata-enumeratorAtURL:…recursive; -skipDescendants to prune-createDirectoryAtURL:withIntermediateDirectories:YESmkdir -p-copyItemAtURL:toURL:error: / -moveItemAtURL: / -removeItemAtURL:the verbs-trashItemAtURL:resultingItemURL:error:the polite delete on macOS-URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMaskthe sandbox-correct way to find anywhere-attributesOfItemAtPath:error:the NSFileAttributeKey dictionary
Never hard-code ~/Library/…. Under App Sandbox that path is redirected into a container and the literal is wrong. URLsForDirectory:inDomains: (or NSSearchPathForDirectoriesInDomains) always answers correctly.

NSBundle

NSBundle.mainBundlethe running application[NSBundle bundleForClass:self.class]the RIGHT one inside a framework or a test bundle-URLForResource:@"data" withExtension:@"json"find a resource-objectForInfoDictionaryKey:@"CFBundleShortVersionString"Info.plist, localised variant-localizedStringForKey:value:table:what NSLocalizedString expands to-bundleIdentifier -bundlePath -executablePathidentity

NSUserDefaults

[NSUserDefaults.standardUserDefaults registerDefaults:@{...}]the FACTORY layer — not persisted, always re-register at launch-objectForKey: -stringForKey: -integerForKey: -boolForKey: -dataForKey:typed readers, all nil/0-safe-setObject:forKey: / -removeObjectForKey:writes; synchronize is deprecated and unnecessaryNSUserDefaultsDidChangeNotificationlocal changes; KVO on the key path also works

Defaults are a layered search: argument domain (-key value on the command line, which is a superb debugging affordance), then application, global, language, and finally your registered defaults.

Processes and the environment

NSProcessInfo.processInfoarguments, environment, hostName, processIdentifier.operatingSystemVersionand isOperatingSystemAtLeastVersion:.thermalState .lowPowerModeEnabledback off when the device asks[info beginActivityWithOptions:reason:]stop App Nap killing your background workNSTask / NSPipefork+exec a child, macOS only, unavailable in the sandbox without entitlementsNSFileHandlea file descriptor wrapper; readabilityHandler for async reads
NSTask *t = [NSTask new];t.executableURL = [NSURL fileURLWithPath:@"/usr/bin/sw_vers"];t.arguments = @[@"-productVersion"];NSPipe *p = NSPipe.pipe; t.standardOutput = p;[t launchAndReturnError:NULL];NSData *out = [p.fileHandleForReading readDataToEndOfFile];[t waitUntilExit];

Archiving & Serialization

NSCoding, JSON, plists

NSSecureCoding

@interface Note : NSObject <NSSecureCoding>@end@implementation Note+ (BOOL)supportsSecureCoding { return YES; }- (void)encodeWithCoder:(NSCoder *)c { [c encodeObject:_title forKey:@"title"]; [c encodeInteger:_rank forKey:@"rank"];}- (instancetype)initWithCoder:(NSCoder *)c { if (self = [super init]) { // [super initWithCoder:c] if the parent codes _title = [c decodeObjectOfClass:NSString.class forKey:@"title"]; _rank = [c decodeIntegerForKey:@"rank"]; } return self;}@end
decodeObjectForKey: instantiates whatever class the archive names. That is a remote code execution primitive on untrusted data, and it is why NSSecureCoding exists: decodeObjectOfClass:forKey: checks the class before allocating. Never unarchive data you did not write with the insecure API.

The modern archiver API

NSData *d = [NSKeyedArchiver archivedDataWithRootObject:note requiringSecureCoding:YES error:&err];Note *n = [NSKeyedUnarchiver unarchivedObjectOfClass:Note.class fromData:d error:&err];// collections need the whole set of classes they may contain:NSSet *cls = [NSSet setWithObjects:NSArray.class, Note.class, nil];id a = [NSKeyedUnarchiver unarchivedObjectOfClasses:cls fromData:d error:&e];

The old +archiveRootObject:toFile: and +unarchiveObjectWithData: are deprecated because they cannot be made secure. Archives preserve object graphs including cycles and identity; +setClassName:forClass: handles renamed classes.

JSON

[NSJSONSerialization JSONObjectWithData:d options:0 error:&e]→ NSDictionary / NSArray of NSString, NSNumber, NSNulloptions:NSJSONReadingMutableContainers…if you must mutate the result[NSJSONSerialization dataWithJSONObject:o options:NSJSONWritingPrettyPrinted error:&e]the other way+isValidJSONObject:check before writing, or it raises rather than erroringNSJSONWritingSortedKeysdeterministic output — essential for tests and checksums
JSON null becomes NSNull, not nil. Every field of a decoded payload needs an NSNull check as well as a type check — [obj isKindOfClass:NSString.class] is the honest guard, because a hostile or merely careless server can put a number where you expected a string and NSNumber answers length with an exception.

Property lists

[NSPropertyListSerialization propertyListWithData:…format:&fmt error:&e]reads XML and binaryNSPropertyListBinaryFormat_v1_0write this; XML only for humans[dict writeToURL:url error:&e]the shortcut on NSDictionary / NSArray

Plist types are exactly: NSString, NSNumber, NSDate, NSData, NSArray, NSDictionary (string keys only) and booleans. Anything else fails at write time.

XML

NSXMLParserSAX — delegate callbacks, everywhere including iOSNSXMLDocumentfull DOM with XPath and XSLT. macOS ONLY

Networking

NSURLSession
NSURLSession *s = NSURLSession.sharedSession; // no delegate, no configNSURLSessionDataTask *t = [s dataTaskWithURL:url completionHandler:^(NSData *d, NSURLResponse *r, NSError *e) { // BACKGROUND thread. Hop to the main queue for UI. if (e) { return; } NSInteger code = ((NSHTTPURLResponse *)r).statusCode; }];[t resume]; // tasks are created SUSPENDED — the classic bug
PieceRole
NSURLSessionConfigurationdefaultSessionConfiguration (cached, cookies, credentials), ephemeralSessionConfiguration (nothing on disk), backgroundSessionConfigurationWithIdentifier: (continues after the app exits; delegate-only, no completion handlers).
NSURLSessionDataTaskResponse into memory.
NSURLSessionDownloadTaskResponse to a temp file; resumable; the only kind allowed in a background session alongside upload.
NSURLSessionUploadTaskBody from data, file or stream.
NSURLSessionWebSocketTaskRFC 6455, since iOS 13 / macOS 10.15.

Requests

[NSMutableURLRequest requestWithURL:url]then set HTTPMethod, HTTPBody, allHTTPHeaderFields[r setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]setValue REPLACES, addValue appendsr.cachePolicyNSURLRequestReloadIgnoringLocalCacheData etc.r.timeoutIntervalper-request; the config has its own two timeoutsNSURLComponentsbuild a URL with query items — correct percent-encoding, unlike string concatenation

Delegates, when you need them

URLSession:task:didCompleteWithError:always fires, even on success (error is nil)URLSession:dataTask:didReceiveData:incremental — append it yourselfURLSession:task:didSendBodyData:…upload progressURLSession:didReceiveChallenge:auth and server-trust evaluation. Pinning lives hereURLSession:task:willPerformHTTPRedirection:…pass nil to the completion to stop the redirect
A session with a delegate retains it until you call -finishTasksAndInvalidate or -invalidateAndCancel. A long-lived session whose delegate is a view controller is a permanent leak. And never mix: a task created with a completion handler ignores most delegate callbacks.
App Transport Security requires TLS 1.2+ with forward secrecy for every connection, and plain HTTP fails with NSURLErrorAppTransportSecurityRequiresSecureConnection. Exceptions go in Info.plist under NSAppTransportSecurity and need justification for App Store review. NSAllowsLocalNetworking is the right key for a lab device on the LAN.

The rest of the stack

NSURLCacheshared HTTP cache; obeys Cache-ControlNSHTTPCookieStorageper-configuration under NSURLSessionNSURLCredential / NSURLProtectionSpaceauth, keychain-backedNSURLProtocolintercept and fake requests — the standard test seamNSURLConnectionDEPRECATED since 2015. If you see it, it is old

Core Foundation

toll-free bridging

Core Foundation is the C-language sibling of Foundation: an opaque-pointer object model with manual reference counting and its own naming conventions. A dozen central types are toll-free bridged — the same object, castable in either direction with no conversion at all.

FoundationCore Foundation
NSString / NSMutableStringCFStringRef / CFMutableStringRef
NSArray, NSDictionary, NSSetCFArrayRef, CFDictionaryRef, CFSetRef
NSData, NSNumber, NSDateCFDataRef, CFNumberRef, CFDateRef
NSURL, NSError, NSTimeZone, NSLocaleCFURLRef, CFErrorRef, CFTimeZoneRef, CFLocaleRef
NSRunLoopCFRunLoopRefnot bridged; related, different objects

The CF memory rule

The Create Rule. A function with Create or Copy in its name returns an object you own and must CFRelease. A function with Get returns one you do not own and must not release — retain it if you need it to outlive the call. That is the whole convention, and it is the same rule as Objective-C’s, spelled differently.

Bridging casts under ARC

CastDoes
(__bridge CFTypeRef)objReinterpret, no ownership transfer. The common case — valid only while ARC keeps the object alive.
(__bridge_retained CFTypeRef)objARC hands ownership out; you must CFRelease. Same as CFBridgingRetain(obj).
(__bridge_transfer id)cfObjARC takes ownership in; do not release. Same as CFBridgingRelease(cfObj).
CFStringRef cf = CFStringCreateWithCString(NULL, "hi", kCFStringEncodingUTF8);NSString *s = CFBridgingRelease(cf); // ARC now owns it; no CFReleaseNSString *t = @"there";CFStringRef cf2 = (__bridge CFStringRef)t; // borrow; do NOT release
ARC does not manage CF objects, even bridged ones held in a CFTypeRef variable. Every Create/Copy needs a matching CFRelease, and passing NULL to CFRelease crashes (unlike messaging nil). Use CFAutorelease() when you must return a CF object without transferring ownership.

Geometry, and the NS/CG duality

CGPoint CGSize CGRectCore Graphics structs; NSPoint/NSSize/NSRect are typedefs of them on 64-bitCGRectMake / NSMakeRectthe same thingCGRectGetMinX/MidY/MaxX(r)use these — they handle negative widths correctlyCGRectIntersectsRect, CGRectContainsPoint, CGRectInset, CGRectIntegralthe algebraNSStringFromCGRect / CGRectFromStringlogging and plistsCGRectNull vs CGRectZero"no rectangle" vs a rectangle at the origin with no size. Different
Other C-level Apple APIs follow the same conventions but are not bridged: Core Graphics contexts, Core Text, Security, IOKit, SecKeychain. They still use Create/Get, CFRelease, and CFTypeRef.

Modern Annotations

nullability & generics

Everything on this card is metadata: it changes diagnostics and, crucially, how the API looks from Swift. None of it changes the generated code. Annotating a header is the cheapest possible improvement to a mixed codebase.

Nullability

nonnullnever nil → Swift non-optionalnullablemay be nil → Swift optional (T?)null_unspecifiedunknown → Swift implicitly-unwrapped (T!). The default without auditnull_resettablegetter never nil, setter accepts nil to reset (UIView.tintColor)NS_ASSUME_NONNULL_BEGIN / _ENDwrap the header; then only annotate the nullable ones
NS_ASSUME_NONNULL_BEGIN@interface Store : NSObject- (nullable Note *)noteWithID:(NSString *)ident;- (BOOL)save:(NSError *_Nullable *_Nullable)error; // out-params need both@property (nonatomic, copy, nullable) NSString *subtitle;@endNS_ASSUME_NONNULL_END
The keywords come in two spellings: nullable/nonnull for Objective-C pointer positions and property attributes, _Nullable/_Nonnull for C pointer positions like the double-indirect NSError **. Nothing is enforced at runtime — a nonnull parameter can still receive nil from an unannotated caller, and Swift will then get a non-optional that is nil, which crashes far away.

Lightweight generics

NSArray<NSString *> *names;checked at compile time, ERASED at runtimeNSDictionary<NSString *, NSNumber *> *counts;key then valueNSArray<id<Drawable>> *shapes;protocol-typed elements__kindof NSView *"this or a subclass" — removes the cast at every call site@interface Box<__covariant T> : NSObjectyour own generic class; T is only a type annotation
Erased means unenforced. An NSArray<NSString *> * will happily hold NSNumbers if they arrive through an unannotated API or a cast; you get a warning at the insertion site and a crash at the use site. The annotation is a promise to readers and to Swift, not a check.

Enumerations

typedef NS_ENUM(NSInteger, GCState) { GCStateIdle = 0, GCStateBusy, GCStateDone };typedef NS_OPTIONS(NSUInteger, GCFlags) { GCFlagNone = 0, GCFlagFast = 1 << 0, GCFlagSafe = 1 << 1,};typedef NS_ERROR_ENUM(GCErrorDomain, GCError) { GCErrorNoDisk = 1 };typedef NSString *GCKind NS_STRING_ENUM; // string constants as a Swift enum

NS_ENUM imports into Swift as an enum with a fixed backing type and gives you exhaustiveness warnings in switch; NS_OPTIONS imports as an OptionSet. A bare C enum gets neither.

Availability and contracts

API_AVAILABLE(macos(14.0), ios(17.0))on a declarationAPI_DEPRECATED("use -bar", macos(10.9, 12.0))introduced, then deprecatedAPI_UNAVAILABLE(tvos, watchos)excluded platformsif (@available(macOS 14.0, *)) { }the runtime check — the compiler requires itNS_DESIGNATED_INITIALIZER / NS_UNAVAILABLEinitialiser contractsNS_REQUIRES_SUPERwarn if an override forgets [super foo]NS_NOESCAPEthe block does not outlive the call — lets Swift drop the self. prefixobjc_subclassing_restrictedfinalNS_SWIFT_NAME(Thing.make(from:))rename for SwiftNS_SWIFT_UNAVAILABLE("use X")hide it from Swift entirely

AppKit in One Card

macOS
int main(int argc, const char *argv[]) { @autoreleasepool { return NSApplicationMain(argc, argv); }}

NSApplicationMain creates NSApp, loads the main nib named in Info.plist, connects the delegate, and runs the main run loop. Everything after that is event-driven.

ClassJob
NSApplication (NSApp)The singleton event pump. -run, -terminate:, -sendEvent:, the delegate.
NSResponderBase of the responder chain. NSView, NSWindow, NSViewController, NSApplication all descend from it.
NSWindow / NSWindowControllerA window and its lifetime owner. contentView, makeKeyAndOrderFront:, firstResponder.
NSViewRectangle, coordinate system (origin bottom-left unless isFlipped), -drawRect: or a CALayer, -setNeedsDisplay:.
NSViewControllerviewDidLoad, viewWillAppear, representedObject.
NSDocumentThe document architecture: NSDocumentController, undo, autosave, versions, all for free if you adopt readFromURL:/dataOfType:.
NSCellThe lightweight pre-2011 drawing object still lurking inside controls and table columns. Legacy, but visible in APIs.

Target-action

button.target = self;button.action = @selector(save:); // signature: - (IBAction)save:(id)sender// target = nil: send it down the responder chain until someone responds

A nil target is the mechanism behind menus: Copy is -copy: sent to nil, which walks first responder → its superviews → window → window delegate → NSApp → app delegate. Enable state comes from -validateUserInterfaceItem:.

Nibs and outlets

IBOutleta no-op macro; marks a property Interface Builder may connectIBActionvoid, in disguise, for the same reasonFile's Ownerthe object that loads the nib — usually the controllerawakeFromNibevery object gets it after all connections are made@property (weak) IBOutlet NSButton *ok;weak: the view hierarchy owns it

Auto Layout

view.translatesAutoresizingMaskIntoConstraints = NO;FIRST, always, for code-created views[NSLayoutConstraint activateConstraints:@[ ... ]]the batch form[a.leadingAnchor constraintEqualToAnchor:b.leadingAnchor constant:8]the anchor API — readable and type-checkedsetContentHuggingPriority: / setContentCompressionResistancePriority:how intrinsic size negotiatesNSStackViewthe layout you should reach for before writing constraints
Cocoa Bindings is macOS-only and has no UIKit equivalent: a binding connects a view property to a controller key path through KVC/KVO, so a table can display a model array with no glue code at all. It is powerful, it is entirely runtime-checked, and its failure mode is a console message about an unknown key.

UIKit in One Card

iOS
int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}
ClassJob
UIApplicationSingleton; openURL:, background task assertions, state transitions.
UIApplicationDelegateapplication:didFinishLaunchingWithOptions: and the lifecycle. Since iOS 13, scene lifecycle moved to UISceneDelegate.
UIWindow / UIWindowSceneOne per scene; rootViewController owns the hierarchy.
UIViewControllerThe unit of composition. Containment (addChildViewController:), navigation, presentation.
UIViewOrigin top-left, unlike AppKit. Always backed by a CALayer; frame vs bounds vs transform.
UIResponderTouch and press handling, becomeFirstResponder, the chain up to the app delegate.
UITableView / UICollectionViewData source + delegate, cell reuse by identifier. The single most important pattern in the framework.

View controller lifecycle, in order

-loadViewcreate the view manually; do NOT call super if you override-viewDidLoadonce, after the view exists. One-time setup-viewWillAppear:every appearance. Refresh state here-viewWillLayoutSubviews / -viewDidLayoutSubviewsgeometry is settled-viewDidAppear:on screen; start animations, analytics-viewWillDisappear: / -viewDidDisappear:tear down timers and observers HERE, not in dealloc-didReceiveMemoryWarningdrop caches
viewDidLoad runs once; viewWillAppear: runs every time. Putting a data refresh in the former means stale content after a push-and-pop; putting one-time setup in the latter means duplicate observers and duplicated subviews. This split accounts for a large share of UIKit bugs.

Cells and reuse

[tv registerClass:UITableViewCell.class forCellReuseIdentifier:@"cell"];- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)ip { UITableViewCell *c = [tv dequeueReusableCellWithIdentifier:@"cell" forIndexPath:ip]; c.textLabel.text = self.rows[ip.row]; // recycled: set EVERY field return c;}
A dequeued cell is a used cell. Any property you set conditionally in one row persists into another — the ghost-content bug. Set every field on every pass, or reset in -prepareForReuse.

Controls, gestures, navigation

[btn addTarget:self action:@selector(tap:) forControlEvents:UIControlEventTouchUpInside]target-action with an event maskUITapGestureRecognizer, UIPanGestureRecognizer…initWithTarget:action:; state machine in .state[nav pushViewController:vc animated:YES]the navigation stack[self presentViewController:vc animated:YES completion:nil]modal-prepareForSegue:sender:storyboard hand-off; the only place to pass data across a segueUIEdgeInsets, safeAreaLayoutGuidenotches and home indicators

Swift Interop

the mixed codebase

Objective-C and Swift share a runtime and a memory model, so bridging is real interop, not FFI. But the visibility is asymmetric: Swift sees nearly all of Objective-C; Objective-C sees only the part of Swift that can be expressed in the Objective-C runtime.

Which direction, which file

DirectionMechanism
Swift sees ObjC (app target)The bridging header, <Target>-Bridging-Header.h. Import your ObjC headers there.
Swift sees ObjC (framework)The framework’s umbrella header. No bridging header allowed.
ObjC sees Swift#import "<Target>-Swift.h" — generated, never edited, and only contains @objc-visible declarations.

What Swift must do to be visible

@objc class Foo: NSObjectmust inherit NSObject; a bare Swift class is invisible@objc func bar()per member@objcMembers class Fooexpose all of themdynamicforce message dispatch — required for KVO and swizzling@objc(GCFoo) class Foogive it an ObjC name (Swift names get module-mangled)@nonobjcopt one member out

What cannot cross

Swift generics, tuples, structs (other than the bridged value types), enums with associated values, protocol extensions with default implementations, existentials with associated types, top-level functions, optionals of non-object types, and anything using Swift-only features in its signature. If it will not bridge, wrap it in a class that will.

Automatic value bridging

NSString ↔ Stringand NSArray ↔ [Any], NSDictionary ↔ [AnyHashable: Any]NSNumber ↔ Int/Double/Boolwith the usual NSNumber loosenessNSError ↔ throwsa trailing NSError** becomes a Swift throwing functionnullable → Optionalunaudited pointers become T!, which crashes on nil useNS_ENUM → enumNS_OPTIONS → OptionSetcompletion handler → asyncthe last-parameter block becomes an await point automatically

Naming translation

- (void)setX:(int)x y:(int)y→ func setX(_ x: Int, y: Int)+ (instancetype)fooWithBar:→ Foo(bar:) — factory methods become initialisers- (BOOL)isHidden→ var isHidden: Bool — getter/setter pairs become propertiesNSString *const GCFooKey→ a member of an enum-like struct if NS_STRING_ENUMNS_SWIFT_NAME(…)override any of the above
Annotate before you bridge. An unannotated header imports every object as T! and every collection as [Any], which pushes the failure from a compile error to a runtime crash in Swift code that looks correct. Wrapping headers in NS_ASSUME_NONNULL_BEGIN/_END and adding lightweight generics is a few minutes per header and is the highest-value work in a mixed codebase.
Swift concurrency. Objective-C code is nonisolated by default; NS_SWIFT_UI_ACTOR (and the UIView/NSView hierarchy’s own annotations) mark main-actor APIs. A completion handler that fires on a background queue and is imported as async still resumes wherever it was called — the bridge does not add a hop.

Cocoa Patterns & Naming

why it looks like this
PatternWhere it shows up
DelegationA weak delegate plus an @optional protocol. Cocoa’s answer to subclassing for customisation — you configure an object rather than inherit from it.
Data sourceA second delegate, separated because it supplies content rather than policy. NSTableView, UITableView.
Target-actionOne selector plus one object; the nil-target form walks the responder chain. Menus and toolbars run on it.
NotificationAnonymous one-to-many. Use it when the sender must not know the receivers.
KVO / bindingsObservation without the observed object’s cooperation.
Class clusterOne public abstract face, many private concrete implementations chosen by the initialiser.
Two-stage creationalloc then init…, so the initialiser may substitute or fail.
Singleton+sharedThing with dispatch_once. Note the name: shared, not instance, and it does not forbid others.
Responder chainUnhandled events walk up a dynamic hierarchy. The same idea as forwarding, at the UI level.
+ (instancetype)sharedStore { static Store *shared = nil; static dispatch_once_t once; dispatch_once(&once, ^{ shared = [[self alloc] initPrivate]; }); return shared;}

The naming rules, which are not optional

Clarity beats brevity, and the method name is a sentence. -stringByAppendingPathComponent: is long because it tells you it returns a new string (…By…ing) rather than mutating (-appendPathComponent: would). That suffix convention runs through the whole framework and is worth imitating exactly.
-count, -namegetters have NO "get" prefix-getBytes:length:"get" ONLY when returning through pointer arguments-isHidden, -hasChanges, -canDeleteBOOL getters read as predicates-setName:setters, always-arrayByAddingObject:returns a new object-addObject:mutates the receiver-sortedArrayUsing… vs -sortUsing…the same distinction againalloc/new/copy/mutableCopyRESERVED prefixes — they mean +1. Never use them otherwiseGCUserDidLoginnotification names: prefix + subject + Did/Will + verb_privateIvarleading underscore is for ivars; Apple reserves it for methods
Prefix everything public. There are no namespaces. Two-letter prefixes are reserved by Apple, so use three or more for your own classes, categories, category methods, constants and notification names. A collision between your Document and a framework’s is a link-time duplicate-symbol error at best and silent misbehaviour at worst.

MVC, as Cocoa means it

Not the Smalltalk triangle: the controller sits between model and view, and neither knows the other. Views communicate upward by target-action and delegation; models communicate upward by notification and KVO. A view that imports your model header is the design error the framework is built to prevent.

Debugging

lldb and the tools

lldb, the Objective-C parts

po objprint object — calls -debugDescriptionp (int)[obj count]print with an explicit type; lldb needs it for non-id returnsexpr -- [view setHidden:NO]run code in the inspected process, then continuepo [obj _ivarDescription]EVERY ivar and value. Undocumented, invaluablepo [obj _shortMethodDescription]the class's own methods, not inheritedpo [[obj class] description]the REAL class when po lies (KVO subclasses)b -[NSException raise]catch every exception at the throw sitebreakpoint set -n "-[UIView setFrame:]"break on a framework method by selectorbt / bt allbacktrace, one thread or allimage lookup -a 0x10a2f3which symbol is that addressthread returnabandon a frame — skip past a crash to see what happenspo $arg1registers by ABI role: $arg1 is self, $arg2 is _cmd
The single most useful breakpoint: a symbolic breakpoint on objc_exception_throw. Cocoa exceptions unwind to a top-level handler that logs and continues, so by the time you see the console message the stack is gone. Breaking at the throw keeps it.

Environment switches

NSZombieEnabled=YESfreed objects become zombies that log the message instead of crashing randomlyMallocStackLogging=1records allocation backtraces; needed by leaks and by InstrumentsMallocScribble=1fill freed memory with 0x55 so use-after-free fails loudlyMallocGuardEdges=1guard pages around large allocationsNSShowAllViews=YESAppKit: colour every view frameOBJC_PRINT_REPLACED_METHODS=YESsee who swizzled whatNSDoubleLocalizedStrings / NSShowNonLocalizedStringslocalisation smoke tests
Zombies never free memory — the process grows without bound. Turn the setting on to find one bug, then turn it off; never ship a build with it, and never leave it on while profiling.

Sanitizers and the analyser

clang --analyze x.mthe static analyser: leaks, over-releases, nil dereferences, ARC naming violations-fsanitize=addressASan: use-after-free, overflow, double-free. ~2x slower-fsanitize=threadTSan: data races. Finds the ones that only show up on customers' machines-fsanitize=undefinedUBSan: the C-level undefined behaviourMain Thread CheckerXcode-only; catches UIKit off the main thread. Leave it on

Command-line tools

leaks <pid>cycle detection on a live processheap <pid>object counts by class — the fastest way to spot a leak's shapemalloc_history <pid> <addr>where that block came fromnm -m binary | grep objcwhat classes and categories it definesotool -oV binarydump the Objective-C metadata segmentclass-dumpthird-party; reconstruct headers from a binaryxcrun simctl / instruments -t "Time Profiler"simulator control, and profiling from a script

Logging

NSLog(@"%@", x)always on, always to the system log, no levels. Fine for a scratch buildos_log(OS_LOG_DEFAULT, "%{public}s", s)the real one: levels, subsystems, and %{private} redaction by defaultos_signpostintervals that show up in Instrumentslog stream --predicate 'subsystem == "cloud.geocam"'read them back

Traps

the ones that get everyone
The silent nil. Messaging nil is legal and returns zero. A whole chain can do nothing and report nothing. When a value is unexpectedly empty, suspect a nil receiver before suspecting the algorithm.
strong where copy belongs. An NSString or NSArray property declared strong can be mutated behind your back by whoever passed it in.
isEqual: without hash. Works everywhere except sets and dictionary keys, where it silently produces duplicates.
NSNotFound is NSIntegerMax, not −1. if (idx >= 0) after a search is always true.
Accessors in init/dealloc. A subclass override runs against a half-constructed or half-destroyed object. Touch the ivar directly.
Blocks capturing self implicitly. Referring to _ivar inside a block captures self just as surely as writing self. If the object owns the block, that is a cycle.
Forgetting [task resume]. NSURLSession tasks start suspended, and a forgotten resume looks exactly like a network that never answers.
dispatch_sync onto your own queue. Instant deadlock, no diagnostic. The main-queue variant from main-thread code is the usual form.
Timers and observers outliving their owner. A repeating NSTimer retains its target; a block-form notification observer retains its captures; a KVO registration must be removed before dealloc finishes. All three crash or leak later, somewhere else.
Mutating a collection while enumerating it. Raises immediately — which is the merciful case. Removing by forward index in a loop does not raise; it just skips elements.
%d for an NSInteger. Correct on 32-bit, truncating on 64-bit. Use %ld with a (long) cast, or %zd.
NSLog(str). A format-string vulnerability, exactly as in C.
Comparing unnormalised Unicode. The file system hands back NFD; your literals are NFC. Same text, different bytes, not equal.
CFRelease(NULL) crashes. Core Foundation has none of Objective-C’s nil tolerance, and ARC does not manage CF objects even when they are bridged.
JSON null is NSNull, which is truthy. if (value) does not screen it out; isKindOfClass: does.
Category method collisions. Two categories defining the same selector on the same class: last one loaded wins, silently, and the order is a link-order accident. Prefix every category method.
Subclassing a class cluster. NSString, NSArray and their relatives have no storage to inherit. Compose or add a category instead.
atomic mistaken for thread safety. It protects a single accessor call and nothing else.
ARC does not clean up on exception unwind unless you build with -fobjc-arc-exceptions. Treat a caught NSException as a message about a broken program, not as recoverable control flow.
Reusing a dequeued cell without resetting it. Anything you set conditionally on one row shows up on an unrelated one.

Language, Runtime & Framework Index

Every directive, message and class worth remembering, grouped by what it does — type in the filter box to narrow it

Directives & Keywords

73

Declarations

@interfaceopen a class, category or class-extension declaration
@implementationopen a class or category implementation
@endclose any @interface, @implementation or @protocol
@protocoldeclare a protocol; also @protocol(X) yields a Protocol *
@requiredfollowing protocol members must be implemented (the default)
@optionalfollowing protocol members may be omitted — test with respondsToSelector:
@classforward-declare a class; use in headers instead of #import
@propertydeclare accessors, and usually synthesise the ivar
@synthesizegenerate accessors explicitly, optionally renaming the ivar
@dynamicpromise the accessors exist at runtime; suppress synthesis
@publicivar visible everywhere via obj->ivar
@protectedivar visible to this class and subclasses — the default
@privateivar visible only to this class
@packageivar visible within the framework or image
@compatibility_aliasa second name for an existing class
@importload a semantic module and link its framework (needs -fmodules)
#import#include with an implicit guard — always use it over #include
@defssplice a class ivar layout into a struct; gone with the modern runtime

Literals & boxing

@"…"NSString constant, allocated in the binary and never freed
@42 @3.14 @YESNSNumber literals for integer, float and boolean constants
@'c'NSNumber from a char literal
@(expr)box any scalar, enum or C-string expression into an NSNumber/NSString
@[…]NSArray literal — raises if any element is nil
@{…}NSDictionary literal, key first
@selector(…)compile-time SEL constant
@encode(…)the type-encoding C string for any type
obj[i]objectAtIndexedSubscript: / setObject:atIndexedSubscript:
obj[@"k"]objectForKeyedSubscript: / setObject:forKeyedSubscript:

Control & concurrency

@try @catch @finallyexception handling; @finally always runs
@throwraise an NSException (or any object)
@synchronized(obj)recursive mutex keyed on an object pointer; convenient, slow
@autoreleasepoola scope whose autoreleased objects drain on exit
for (x in coll)fast enumeration via NSFastEnumeration
^{ … }block literal — a closure with a C ABI
self / _cmdthe two implicit method arguments, always in scope
supera directive, not an object: start the method search one class up

ARC qualifiers & bridging

__strongthe default for object variables; retains
__weakno retain, zeroed automatically when the target deallocates
__unsafe_unretainedno retain and no zeroing — will dangle
__autoreleasingfor out-parameters: (NSError * __autoreleasing *)error
__blocka variable shared with, and writable from, a block
__bridgecast between id and CFTypeRef with no ownership transfer
__bridge_retainedARC hands ownership out; you must CFRelease
__bridge_transferARC takes ownership in; do not CFRelease
NS_RETURNS_RETAINEDthis +0-named method really returns +1
NS_RETURNS_NOT_RETAINEDthis new/copy-named method really returns +0
NS_RETURNS_INNER_POINTERthe returned pointer is owned by the receiver; keep it alive
NS_CONSUMEDthis argument is released by the callee
objc_directcompile the method as a plain C call: no dispatch, no override

Nullability & generics

nonnull / _Nonnullnever nil; imports into Swift as non-optional
nullable / _Nullablemay be nil; imports as T?
null_unspecifiedunaudited; imports as the crash-prone T!
null_resettablegetter never nil, setter accepts nil to restore a default
NS_ASSUME_NONNULL_BEGINeverything in the region is nonnull unless marked
NSArray<T> *lightweight generics — checked at compile time, erased at run time
__kindof T *“T or any subclass”; removes the cast at the call site
__covariant / __contravariantvariance annotations on your own generic classes
instancetypereturn type meaning “this class”; use it for every initialiser

Contracts & availability

NS_DESIGNATED_INITIALIZERthe compiler then enforces the initialiser chain rules
NS_UNAVAILABLEmake an inherited method a compile error to call
NS_REQUIRES_SUPERwarn when an override forgets to call super
NS_NOESCAPEthe block does not outlive the call
NS_ENUM / NS_OPTIONStyped enum / bitmask; import as Swift enum and OptionSet
NS_ERROR_ENUMerror codes bound to a domain
NS_STRING_ENUMa family of NSString constants; imports as a Swift struct enum
NS_SWIFT_NAMErename the declaration for Swift
NS_SWIFT_UNAVAILABLEhide it from Swift entirely
API_AVAILABLEintroduced in these OS versions
API_DEPRECATEDintroduced then deprecated, with a replacement message
API_UNAVAILABLEnot present on these platforms
@available(…)the runtime version check the compiler demands
objc_subclassing_restrictedfinal: the class cannot be subclassed
IBOutlet / IBActionno-op markers Interface Builder looks for

Types & Constants

38

Object and runtime types

idany object pointer; no compile-time method checking, no dot syntax
Classa class object; classes are objects too
SELa uniqued selector — compare with ==, not strcmp
IMPthe C function implementing a method
Methodopaque runtime handle: selector + IMP + type encoding
Ivaropaque runtime handle for an instance variable
Protocol *a protocol object; get one with @protocol(X)
objc_property_topaque runtime handle for a declared property
instancetypecontextual return type meaning the receiver’s class
objc_AssociationPolicyASSIGN / RETAIN / COPY, each with a _NONATOMIC variant

The four kinds of nothing

nilnull object pointer; sending it a message is legal and returns zero
Nilnull Class pointer — same bits, different intent
NULLnull C pointer, for void *, char * and CFTypeRef
[NSNull null]a real singleton standing for nothing inside a collection

Scalars

BOOL / YES / NObool on modern 64-bit, signed char historically
NSInteger / NSUIntegerpointer-width signed / unsigned integer
CGFloatdouble on 64-bit, float on 32-bit
NSTimeIntervala double, in seconds
unicharuint16_t — one UTF-16 code unit, not one character
NSNotFoundNSIntegerMax, the “no index” sentinel — never −1
NSStringEncodingNSUTF8StringEncoding, NSASCIIStringEncoding, NSUTF16…
NSComparisonResultNSOrderedAscending / Same / Descending = −1 / 0 / 1
NSUIntegerMax / NSIntegerMaxthe limits of the pointer-width integer types

Structs

NSRangelocation and length, both NSUInteger
CGPoint / NSPointx, y — the same type on 64-bit
CGSize / NSSizewidth, height
CGRect / NSRectorigin and size
CGVector / CGAffineTransformdx dy; the 2D affine matrix
NSEdgeInsets / UIEdgeInsetstop, left, bottom, right
NSZone *vestigial allocation zone; every API that takes one ignores it

Common enumerations

NSStringCompareOptionsCaseInsensitive, Literal, Backwards, Anchored, Numeric, Diacritic…
NSEnumerationOptionsNSEnumerationConcurrent, NSEnumerationReverse
NSSortOptionsNSSortConcurrent, NSSortStable
NSKeyValueObservingOptionsNew, Old, Initial, Prior
NSDataReadingOptionsMappedIfSafe, Uncached — map big files, do not read them
NSJSONReadingOptionsMutableContainers, MutableLeaves, FragmentsAllowed
NSSearchPathDirectoryApplicationSupport, Documents, Caches, Library, Desktop…
qos_class_tUSER_INTERACTIVE, USER_INITIATED, DEFAULT, UTILITY, BACKGROUND

Property Attributes

17

Ownership

strongretain the new value, release the old — the object default
weakno retain; zeroed automatically when the target dies
copystore [value copy]; mandatory for NSString, collections and blocks
assignraw store — the scalar default; on an object it dangles
unsafe_unretainedexplicit non-zeroing weak reference
retainthe pre-ARC spelling of strong; still accepted

Atomicity and access

atomicthe DEFAULT: accessors take a spinlock. Not thread safety
nonatomicwhat almost everyone writes; no lock
readonlygetter only; redeclare readwrite in the class extension
readwritegetter and setter — the default
classa class-level property; you must write both accessors yourself
directnon-dispatched accessors: fast, unswizzlable, invisible to KVO

Naming and nullability

getter=isFoorename the getter — the Cocoa convention for BOOLs
setter=setFoo:rename the setter
nullable / nonnullnullability, which becomes Swift optionality
null_resettablesetting nil restores a default rather than storing nil
NS_NONATOMIC_IOSONLYlegacy portability macro seen in old headers

NSObject & NSProxy

56

Creating and destroying

+allocallocate and zero an instance; returns +1
+allocWithZone:the zone argument is ignored on every modern system
-initthe root initialiser; always assign its result to self
+newexactly [[self alloc] init]
-deallocteardown; never call it directly, no [super dealloc] under ARC
-copy / -mutableCopycalls copyWithZone: / mutableCopyWithZone:; returns +1
-copyWithZone:NSCopying; immutable classes return self retained
-retain -release -autoreleasemanual reference counting; compile errors under ARC
-retainCountnever meaningful, never trustworthy

Identity and testing

-isEqual:value equality; override with hash or sets misbehave
-hashmust be equal for equal objects and stable while in a collection
-selfreturns self — useful in KVC key paths and as a no-op
-isKindOfClass:this class or any subclass
-isMemberOfClass:exactly this class
-conformsToProtocol:declared conformance only; says nothing about @optional members
-respondsToSelector:ask before sending an optional or dynamic message
+instancesRespondToSelector:the same question about a class
-isProxyYES for NSProxy subclasses, which are not NSObjects

Introspection

-class / +classthe class object — KVO overrides this to hide itself
-superclassone step up the chain
+isSubclassOfClass:class-level ancestry test
-descriptionbacks %@ and po; override it on every model class
-debugDescriptionwhat po prefers when it exists
-methodForSelector:the IMP — cache it for a hot loop
-methodSignatureForSelector:NSMethodSignature; required for full forwarding
-_ivarDescriptionundocumented: every ivar and its value. Debugger only
-_shortMethodDescriptionundocumented: the class’s own methods. Debugger only

Sending and scheduling

-performSelector:…withObject:withObject: — at most two object arguments
-performSelector:withObject:afterDelay:run at the end of a later run-loop pass
-performSelectorOnMainThread:…waitUntilDone:the pre-GCD main-thread hop
-performSelectorInBackground:withObject:spawns a thread with no autorelease pool
+cancelPreviousPerformRequestsWithTarget:the classic debounce
-doesNotRecognizeSelector:raise the unrecognised-selector exception yourself

Forwarding hooks

+resolveInstanceMethod:add an IMP on demand, then the send is retried
+resolveClassMethod:the same for + methods
-forwardingTargetForSelector:cheap redirection: return another receiver
-forwardInvocation:full forwarding with a reified NSInvocation
NSProxythe other root class: forwards everything, implements almost nothing
NSInvocationa message as an object: target, selector, arguments, return value
NSMethodSignatureargument and return types of a method

Class setup and KVC

+loadruns at image load, before main; every implementation runs
+initializeruns before the first message to the class; guard with self ==
-awakeAfterUsingCoder:substitute a different object after unarchiving
-valueForKey: / -setValue:forKey:KVC by name, with automatic boxing
-valueForKeyPath:traverse a dotted chain, with @-operators
-mutableArrayValueForKey:a proxy whose mutations fire KVO notifications
-setValuesForKeysWithDictionary:bulk set — the JSON-to-model shortcut
-valueForUndefinedKey:override to return nil instead of raising
-setNilValueForKey:called when nil is set on a scalar property
+accessInstanceVariablesDirectlyreturn NO to close KVC’s ivar back door
-addObserver:forKeyPath:options:context:register for KVO
-removeObserver:forKeyPath:context:mandatory; removing twice raises
-observeValueForKeyPath:ofObject:change:context:the KVO callback
-willChangeValueForKey: / -didChangeValueForKey:manual KVO notification
+keyPathsForValuesAffecting<Key>declare a derived property’s inputs
+automaticallyNotifiesObserversForKey:opt a key out of automatic notification

Runtime C API

47

Sending

objc_msgSendwhat every [receiver message] compiles to
objc_msgSendSuper / _super2what [super message] compiles to
objc_msgSend_stret / _fpretlarge-struct and long-double returns on some ABIs
method_invokecall a Method directly

Classes

objc_getClass("Name")look a class up by name
objc_lookUpClass / objc_getRequiredClasswithout and with a fatal error if absent
objc_getClassListevery class in the process
object_getClass(obj)the REAL class — KVO cannot lie to this one
object_setClass(obj, cls)reassign isa; this is how KVO works
class_getName / class_getSuperclassidentity and ancestry
class_getInstanceSizebytes per instance
class_respondsToSelectorwithout walking up the chain
class_conformsToProtocoldeclared conformance
objc_allocateClassPairbuild a class at runtime
objc_registerClassPairpublish it; no more ivars after this
objc_disposeClassPairdestroy an unregistered or unused pair

Methods

class_getInstanceMethodsearches superclasses too
class_getClassMethodthe + side
class_copyMethodList(cls, &n)this class’s own methods — you must free() it
class_addMethodreturns NO if the method already exists on this class
class_replaceMethodadd or replace; returns the previous IMP
method_exchangeImplementationsswizzling, atomically
method_setImplementationreplace one IMP; returns the old
method_getName / _getImplementationthe SEL and the IMP
method_getTypeEncodingthe @encode string for the whole signature
method_getNumberOfArgumentsincluding self and _cmd

Ivars, properties, protocols

class_getInstanceVariableby name
class_copyIvarListmust free()
ivar_getName / ivar_getTypeEncoding / ivar_getOffsetthe three questions
object_getIvar / object_setIvarread and write around the accessors
class_addIvaronly between allocate and register
class_copyPropertyListdeclared properties; must free()
property_getName / property_getAttributesname and the T@,&,N,V_x attribute string
objc_getProtocol / objc_copyProtocolListprotocols by name and in bulk
protocol_copyMethodDescriptionListrequired vs optional, instance vs class
class_addProtocoldeclare conformance at runtime

Selectors, IMPs, associations

sel_registerName / sel_getUidintern a selector from a C string
sel_getNameback to a C string
imp_implementationWithBlockuse a block as a method implementation
imp_getBlock / imp_removeBlockthe inverse operations
objc_setAssociatedObjectattach state to any object; key compared by POINTER
objc_getAssociatedObjectread it back
objc_removeAssociatedObjectsclears ALL of them, including other people’s
objc_setAssociationPolicy valuesASSIGN, RETAIN, COPY, and the _NONATOMIC pair
objc_enumerationMutationwhat raises when you mutate while enumerating
objc_autoreleasePoolPush / Popwhat @autoreleasepool compiles to
objc_retainAutoreleasedReturnValuethe ARC fast path that elides a retain/release pair

Functions & Macros

35

Logging and assertions

NSLog(fmt, …)always-on system log; no levels, no subsystems
os_log / os_log_error / os_log_debugthe real logging API: levels, subsystems, %{public}s
os_signpost_interval_begin / _endintervals that appear in Instruments
NSAssert(cond, fmt, …)in a method; uses self and _cmd
NSCAssert(cond, fmt)in a C function
NSParameterAssert(cond)the argument-checking idiom
NS_BLOCK_ASSERTIONSdefine it to compile every assertion out
NSSetUncaughtExceptionHandlerlast-chance logging before the process dies

Names to and from strings

NSStringFromClass / NSClassFromStringthe weak-linking idiom for optional frameworks
NSStringFromSelector / NSSelectorFromStringsafer than a bare @"key" in KVC
NSStringFromProtocol / NSProtocolFromStringthe protocol pair
NSStringFromRange / NSRangeFromStringfor logging and plists
NSStringFromCGRect / CGRectFromStringand the Point and Size variants

Ranges and geometry

NSMakeRange(loc, len)construct
NSMaxRange(r)location + length — one past the end
NSLocationInRange(loc, r)membership
NSEqualRanges / NSUnionRange / NSIntersectionRangerange algebra
CGRectMake / CGPointMake / CGSizeMakeconstruct
CGRectGetMinX / MidY / MaxX …use these — they normalise negative sizes
CGRectContainsPoint / CGRectIntersectsRectthe tests
CGRectInset / CGRectOffset / CGRectIntegralderive a new rectangle
CGRectDividesplit a rectangle into a slice and a remainder
CGRectNull / CGRectZero / CGRectInfinite“no rectangle” is not the same as an empty one
CGRectEqualToRect / CGSizeEqualToSizestruct comparison, not ==

Paths, user, process

NSSearchPathForDirectoriesInDomainsthe older string-path form
NSTemporaryDirectorya writable scratch directory, sandbox-correct
NSHomeDirectory / NSHomeDirectoryForUserthe container root under App Sandbox
NSUserName / NSFullUserNamethe account
NSLocalizedString(key, comment)genstrings scans for exactly this macro
NSLocalizedStringFromTable…when the .strings file is not Localizable
NSGetSizeAndAlignmentparse an @encode string
NSHashInsert / NSMapGet …the pre-Foundation-collections C hash tables. Archaeology

Application entry points

NSApplicationMain(argc, argv)macOS: build NSApp, load the main nib, run
UIApplicationMain(argc, argv, nil, delegateName)iOS: the same job
NSFoundationVersionNumberthe old runtime version check; use @available instead

Format Specifiers

20

Objects

%@any object — calls -description. The only one for NSString
%Ka key path, in NSPredicate format strings only
%1$@ %2$@positional — REQUIRED in localisable strings

Integers

%d %i %uint, unsigned int
%ld %lulong — use with a (long) cast for NSInteger/NSUInteger
%lld %llulong long
%zd %zussize_t / size_t — the cast-free choice for NSInteger
%x %X %ohex lower, hex upper, octal
%ca character as an int

Floating point and pointers

%f %Fdecimal notation, six places by default
%e %gexponential; shortest of %e and %f
%ahexadecimal float — exact, for debugging IEEE-754
%.3f %8.2fprecision and field width, as in C
%pa pointer, in hex
%sa C string (char *), NEVER an NSString
%S %Cunichar string and single unichar
%%a literal per cent sign

os_log only

%{public}sdo not redact this value in the system log
%{private}sredact it — the default for dynamic strings
%{time_t}d %{bytes}d %{errno}dvalue decorators that format the number for you

NSString

45

Creating

+stringWithFormat:the workhorse; +localizedStringWithFormat: for user-facing text
+stringWithUTF8String:from a char *; +stringWithCString:encoding: for anything else
+stringWithContentsOfURL:encoding:error:read a whole text file
-initWithData:encoding:bytes to text; returns nil on invalid input
-dataUsingEncoding:text to bytes; allowLossyConversion: to force it
-UTF8Stringa char * owned by the receiver — do not free, do not outlive
-writeToURL:atomically:encoding:error:write it back out

Inspecting

-lengthUTF-16 CODE UNITS, not characters
-characterAtIndex:one unichar
-substringWithRange: / -substringFromIndex: / -substringToIndex:slicing
-rangeOfString:options:range:locale:search; NSNotFound in .location means absent
-rangeOfCharacterFromSet:first character in a set
-rangeOfComposedCharacterSequenceAtIndex:expand to a whole user-visible character
-containsString: / -hasPrefix: / -hasSuffix:the cheap tests
-enumerateSubstringsInRange:options:usingBlock:ByWords, ByLines, ByComposedCharacterSequences
-enumerateLinesUsingBlock:line-by-line without splitting the whole string

Comparing

-isEqualToString:typed fast path; nil receiver returns NO silently
-compare:options:range:locale:returns NSComparisonResult
-localizedStandardCompare:the Finder ordering: numeric, case- and diacritic-aware
-caseInsensitiveCompare:the common shortcut
-hashconsistent with isEqualToString: — and with byte-equal strings only

Transforming

-stringByAppendingString: / -stringByAppendingFormat:O(n); use NSMutableString in a loop
-stringByReplacingOccurrencesOfString:withString:…options:range: adds regex and case options
-stringByTrimmingCharactersInSet:usually with whitespaceAndNewlineCharacterSet
-componentsSeparatedByString:split; …ByCharactersInSet: for real tokenising
-uppercaseStringWithLocale: / -lowercaseString / -capitalizedStringthe locale argument exists because of Turkish
-stringByApplyingTransform:reverse:ICU: ToLatin, StripDiacritics, LatinToKatakana
-precomposedStringWithCanonicalMappingNFC — normalise before comparing
-decomposedStringWithCanonicalMappingNFD, which is what the file system gives you
-stringByAddingPercentEncodingWithAllowedCharacters:URL escaping; the old method was removed

Converting and paths

-intValue -integerValue -doubleValue -boolValuelenient parsing: no error, 0 on failure
-lastPathComponent -pathExtension -stringByDeletingLastPathComponentpath surgery on strings
-stringByAppendingPathComponent:the “By…ing” suffix means it returns a new string
-stringByStandardizingPath / -stringByExpandingTildeInPathresolve . .. and ~

NSMutableString and neighbours

-appendString: / -appendFormat:the reason NSMutableString exists
-replaceCharactersInRange:withString: / -deleteCharactersInRange:in-place edits
-replaceOccurrencesOfString:withString:options:range:returns the number replaced
NSAttributedStringstring plus ranged attribute dictionaries
NSMutableAttributedString-addAttribute:value:range:, -setAttributes:range:
NSRegularExpressionICU regex; results are NSTextCheckingResult
NSTextCheckingResult-range, -rangeAtIndex: for capture groups
NSDataDetectoran NSRegularExpression that finds dates, links and addresses
NSScannerhand-rolled parsing: -scanUpToString:, -scanDouble:
NSCharacterSetwhitespace, alphanumeric, newline, punctuation, illegal…
NSCharacterSet.URLQueryAllowedCharacterSetand the four other URL component sets

Collections

50

NSArray

+arrayWithObjects: / @[…]construction; the literal raises on a nil element
-count -objectAtIndex: -firstObject -lastObjectfirstObject is nil-safe, a[0] is not
-containsObject: -indexOfObject:linear, using isEqual:. NSNotFound when absent
-indexOfObjectIdenticalTo:by pointer, not by value
-subarrayWithRange: -arrayByAddingObject:slice and concatenate, immutably
-componentsJoinedByString:join into an NSString
-valueForKey:KVC map: an array of that property from every element
-enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
-enumerateObjectsWithOptions:usingBlock:NSEnumerationConcurrent or NSEnumerationReverse
-indexOfObjectPassingTest: / -indexesOfObjectsPassingTest:find one, or an NSIndexSet of many
-sortedArrayUsingComparator:^NSComparisonResult(id a, id b)
-sortedArrayUsingDescriptors:multi-level sort by key path
-filteredArrayUsingPredicate:NSPredicate filtering
-makeObjectsPerformSelector:send one message to every element

NSMutableArray

-addObject: -insertObject:atIndex:nil raises NSInvalidArgumentException
-removeObject: -removeObjectAtIndex: -removeAllObjectsremoveObject: uses isEqual:
-removeObjectsAtIndexes:the safe bulk remove — a forward loop skips elements
-replaceObjectAtIndex:withObject: -exchangeObjectAtIndex:withObjectAtIndex:in place
-sortUsingComparator: / -sortUsingDescriptors:in-place sorting
-filterUsingPredicate:in-place filtering
-addObjectsFromArray:append another array

NSDictionary

+dictionaryWithObjectsAndKeys: / @{…}note the literal is key-first, the old API value-first
-objectForKey: / dict[key]lookup; nil when absent
-allKeys -allValues -countthe bulk accessors; order is unspecified
-objectsForKeys:notFoundMarker:bulk lookup with NSNull as the hole filler
-enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
-keysOfEntriesPassingTest:returns an NSSet of keys
-keysSortedByValueUsingComparator:sort the keys by their values
-setObject:forKey:mutable; KEYS ARE COPIED, values retained
-setValue:forKey:KVC-flavoured: a nil value REMOVES the key
-removeObjectForKey: -removeAllObjectsmutable removal
-addEntriesFromDictionary:merge, overwriting collisions

Sets and index sets

NSSet: -containsObject:O(1) — the reason to use a set over an array
-member: -anyObject -allObjectsretrieve; anyObject is unordered, not random
-isSubsetOfSet: -intersectsSet: -isEqualToSet:the predicates
-unionSet: -intersectSet: -minusSet:mutating set algebra
NSCountedSet: -countForObject:a bag; -addObject: increments
NSOrderedSetunique AND ordered; a subclass of neither NSSet nor NSArray
NSIndexSetranges of NSUInteger, compressed. What table views speak
-indexSetWithIndexesInRange: -firstIndex -enumerateIndexesUsingBlock:the index-set API

Specialised containers

NSCacheevicts under memory pressure, thread-safe, does NOT copy keys
NSMapTableconfigurable key/value strength
NSHashTablea set with configurable strength
NSPointerArrayan array that may hold NULL and non-object pointers
NSEnumerator-nextObject; the pre-block iteration protocol
NSFastEnumerationadopt -countByEnumeratingWithState:objects:count: for for-in
NSPredicate+predicateWithFormat: uses %@ for values and %K for key paths
NSCompoundPredicate / NSComparisonPredicatebuild predicates structurally
NSSortDescriptorkey path + ascending + optional selector or comparator
NSExpressionthe value side of a predicate; also does the @-aggregates

Foundation Classes

55

Values

NSNumberboxed scalar; isEqual: compares numeric value across types
NSDecimalNumberbase-10, 38 digits, exact for money
NSValueboxes any struct or pointer; +valueWithRange:, +valueWithBytes:objCType:
NSData / NSMutableDatabyte buffer; NSDataReadingMappedIfSafe for large files
NSNullthe singleton placeholder for nothing inside collections
NSUUIDRFC 4122; -UUIDString
NSURL / NSURLComponentsthe correct type for a file path; components for query building
NSMeasurement / NSUnittyped physical quantities with conversion
NSIndexPatha path of indexes; section/row and section/item in the UI kits

Dates and formatting

NSDatean instant: seconds since 2001-01-01 GMT. No calendar, no zone
NSCalendarthe rules; -components:fromDate:, -dateByAddingUnit:value:toDate:options:
NSDateComponentsbroken-down time; meaningless without a calendar
NSTimeZoneoffset plus DST rules
NSDateIntervala start and a duration, with intersection tests
NSDateFormatterEXPENSIVE to create — cache it. Set en_US_POSIX for fixed formats
NSISO8601DateFormatterthe right class for machine-readable timestamps
NSNumberFormatterdecimal, currency, percent, spelled-out
NSDateComponentsFormatter“2 hours, 5 minutes”
NSRelativeDateTimeFormatter“3 days ago”, localised
NSByteCountFormatter / NSMeasurementFormatterfile sizes and units
NSLocale+currentLocale, +autoupdatingCurrentLocale

Files and the process

NSFileManagerthe file system verbs; make your own instance for a delegate
NSFileHandlea file descriptor; -readabilityHandler for async reads
NSBundle+mainBundle, and +bundleForClass: inside a framework
NSUserDefaultslayered preferences; -registerDefaults: is the factory layer
NSProcessInfoarguments, environment, OS version, thermal state, low power
NSTask / NSPipefork and exec a child. macOS only, entitlement-gated in a sandbox
NSInputStream / NSOutputStreamstreamed I/O with run-loop or block scheduling
NSFileVersion / NSFileCoordinatordocument versions and cross-process coordination
NSMetadataQuerylive Spotlight queries, including iCloud

Serialisation

NSCoder / NSCoding-encodeWithCoder:, -initWithCoder:
NSSecureCoding+supportsSecureCoding and -decodeObjectOfClass:forKey:
NSKeyedArchiver+archivedDataWithRootObject:requiringSecureCoding:error:
NSKeyedUnarchiver+unarchivedObjectOfClass:fromData:error:
NSJSONSerialization+JSONObjectWithData: / +dataWithJSONObject:; null becomes NSNull
NSPropertyListSerializationreads XML and binary; write binary
NSXMLParserSAX, available everywhere
NSXMLDocumentDOM with XPath and XSLT. macOS only

Events and errors

NSErrordomain, code, userInfo; the runtime-failure currency
NSExceptionname, reason, userInfo; programmer error only
NSNotification / NSNotificationCentersynchronous fan-out on the poster’s thread
NSNotificationQueuecoalesce and defer to the end of the run loop pass
NSDistributedNotificationCentercross-process, macOS, string payloads only
NSTimerthe target form retains its target until -invalidate
NSRunLoopper-thread event loop; NSRunLoopCommonModes is usually what you want
NSUndoManagerregisters invocations or block actions; grouped by run-loop pass
NSProgressa tree of progress objects, observable by KVO

Networking

NSURLSessionthe whole networking stack; tasks start SUSPENDED
NSURLSessionConfigurationdefault, ephemeral, background
NSURLRequest / NSMutableURLRequestmethod, body, headers, cache policy, timeout
NSHTTPURLResponse-statusCode, -allHeaderFields
NSURLCache / NSHTTPCookieStorageHTTP caching and cookies
NSURLCredential / NSURLProtectionSpaceauthentication and server trust
NSURLProtocolintercept requests — the standard test seam
NSURLConnectiondeprecated since 2015; if you see it, the code is old

Concurrency

31

Queues

dispatch_get_main_queue()serial, on the main run loop. All UI work belongs here
dispatch_get_global_queue(qos, 0)the shared concurrent queues, one per QoS class
dispatch_queue_create(label, attr)DISPATCH_QUEUE_SERIAL or _CONCURRENT; label in reverse DNS
dispatch_queue_attr_make_with_qos_classpin a QoS onto a queue you create
dispatch_set_target_queuefunnel one queue into another
dispatch_async(q, block)enqueue and return
dispatch_sync(q, block)enqueue and WAIT — deadlocks if q is the current queue
dispatch_barrier_async / _syncexclusive access on a concurrent queue: the writer half
dispatch_after(when, q, block)with dispatch_time(DISPATCH_TIME_NOW, n*NSEC_PER_SEC)
dispatch_apply(n, q, block)parallel for; synchronous, so it blocks the caller
dispatch_async_and_waitlike sync but keeps the target queue’s attributes

Coordination

dispatch_once(&token, block)exactly once per process; the singleton idiom
dispatch_group_createfan out and join
dispatch_group_async / _enter / _leavethe block form and the manual form
dispatch_group_notify / _waitrun when all done, or block until then
dispatch_semaphore_create/_wait/_signalcounting semaphore, when you must block
dispatch_source_createtimers, file descriptors, signals, memory pressure, VFS
dispatch_suspend / _resumesources are created suspended
dispatch_data_timmutable, potentially discontiguous byte buffer
os_unfair_lockthe fast lock. Not recursive, must not be copied
OSSpinLockdeprecated: priority inversion deadlocks on Apple silicon

Objective-C level

NSOperationQueuedependencies, cancellation, priorities, KVO on progress
+mainQueue / maxConcurrentOperationCountthe main-thread queue; set 1 to serialise
NSOperationoverride -main, or -start plus the isExecuting/isFinished KVO dance
NSBlockOperation+blockOperationWithBlock: for the simple case
-addDependency: / -cancel / .isCancelledcancellation is cooperative — you must poll
NSThread+currentThread, +isMainThread, +sleepForTimeInterval:
NSLock / NSRecursiveLockobject-flavoured mutexes
NSCondition / NSConditionLockwait and signal on a predicate
NSLockingthe -lock/-unlock protocol they all adopt
atomic propertiesprotect one accessor call. Never mistake this for thread safety

AppKit

29

Application and windows

NSApplication / NSAppthe event pump singleton; -run, -terminate:, -sendEvent:
NSApplicationDelegate-applicationDidFinishLaunching:, -applicationShouldTerminate:
NSWindow-makeKeyAndOrderFront:, .contentView, .firstResponder
NSWindowControllerowns a window and its nib
NSPanel / NSAlert / NSSavePanelutility windows, alerts and the file panels
NSScreen / NSWorkspacedisplays; launching apps and opening URLs
NSDocument / NSDocumentControllerthe document architecture: undo, autosave, versions

Views and controllers

NSResponderthe base of the responder chain
NSVieworigin BOTTOM-LEFT unless -isFlipped; -drawRect:, -setNeedsDisplay:
NSViewController-viewDidLoad, -viewWillAppear, .representedObject
NSStackView / NSSplitView / NSScrollViewreach for the stack view before writing constraints
NSLayoutConstraint / NSLayoutAnchor+activateConstraints:; the anchor API is the readable one
NSTableView / NSOutlineViewdata source plus delegate; view-based since 10.7
NSCollectionViewthe grid; items are view controllers
NSCellthe pre-2011 lightweight drawing object still inside old controls

Controls and events

NSControl.target and .action — a nil target walks the responder chain
NSButton / NSTextField / NSSlider / NSPopUpButtonthe usual suspects
NSMenu / NSMenuItemmenus are target-action with nil targets
NSEventtype, modifierFlags, locationInWindow
-validateUserInterfaceItem:where menu and toolbar enable state comes from
NSGestureRecognizerclick, pan, magnify, rotate, press
NSPasteboard / NSDraggingInfocopy-paste and drag and drop
-awakeFromNibevery nib object gets it once all connections are made

Drawing and text

NSBezierPath / NSColor / NSImagethe drawing primitives
NSGraphicsContext+currentContext; bridges to CGContextRef
NSTextView / NSTextStorage / NSLayoutManagerthe TextKit stack
NSFont / NSParagraphStyleattributed-string attributes
Cocoa Bindingsview property to controller key path via KVC/KVO. macOS only
NSArrayController / NSObjectControllerthe controller layer bindings talk to

UIKit

33

Application and scenes

UIApplicationthe singleton; -openURL:options:completionHandler:
UIApplicationDelegate-application:didFinishLaunchingWithOptions:
UISceneDelegate / UIWindowScenethe per-window lifecycle since iOS 13
UIWindow.rootViewController owns the hierarchy
UIApplicationMainthe entry point; the delegate class is passed by name

View controllers

UIViewControllerthe unit of composition and of containment
-viewDidLoadonce. One-time setup only
-viewWillAppear: / -viewDidAppear:every appearance. Refresh state here
-viewWillDisappear: / -viewDidDisappear:tear down timers and observers HERE, not in dealloc
-viewWillLayoutSubviews / -viewDidLayoutSubviewsgeometry is settled
-loadViewbuild the view by hand; do not call super
UINavigationController-pushViewController:animated:, the stack
UITabBarController / UISplitViewControllerthe other containers
-presentViewController:animated:completion:modal presentation
-prepareForSegue:sender:the only place to hand data across a storyboard segue

Views and layout

UIVieworigin TOP-LEFT; always CALayer-backed
frame / bounds / center / transformframe is in the superview’s space, bounds in its own
-setNeedsLayout / -layoutIfNeededrequest and force a layout pass
-setNeedsDisplay / -drawRect:request and perform drawing
safeAreaLayoutGuide / UIEdgeInsetsnotches, home indicators and insets
NSLayoutConstraint / NSLayoutAnchorset translatesAutoresizingMaskIntoConstraints = NO first
UIStackViewthe layout to reach for before writing constraints
UIViewPropertyAnimator / +animateWithDuration:animation, interruptible and not

Content and input

UITableView / UITableViewCell-dequeueReusableCellWithIdentifier:forIndexPath:
UICollectionViewthe same pattern with a layout object
UITableViewDataSource / UITableViewDelegatecontent versus behaviour, separated
-prepareForReusereset a recycled cell here
UIControl-addTarget:action:forControlEvents:
UIButton / UILabel / UITextField / UISwitchthe common controls
UIGestureRecognizerTap, Pan, Pinch, LongPress; drive off .state
UIResponder-becomeFirstResponder, -touchesBegan:withEvent:
UIScrollViewcontentOffset, contentSize, contentInset
UIImage / UIColor / UIFont / UIBezierPaththe drawing types

Core Foundation

26

Memory and types

CFTypeRefthe base opaque pointer type
CFRetain / CFReleaseCFRelease(NULL) CRASHES — no nil tolerance here
CFAutoreleasereturn a CF object without transferring ownership
CFGetRetainCount / CFEqual / CFHashthe CFType protocol
CFCopyDescriptionthe CF answer to -description
The Create RuleCreate or Copy in the name means you own it and must release
The Get RuleGet in the name means you do not own it; retain to keep it
CFBridgingRetain / CFBridgingReleasethe function spellings of the __bridge casts
kCFAllocatorDefaultthe allocator argument you always pass (or NULL)

Bridged types

CFStringRef ↔ NSStringtoll-free: the same object, castable either way
CFArrayRef ↔ NSArrayand the mutable variants
CFDictionaryRef ↔ NSDictionaryCF dictionaries can hold non-object values; NS ones cannot
CFSetRef ↔ NSSet
CFDataRef ↔ NSData
CFNumberRef / CFBooleanRef ↔ NSNumberkCFBooleanTrue is @YES
CFDateRef ↔ NSDateCFAbsoluteTime shares NSDate’s 2001 epoch
CFURLRef ↔ NSURL
CFErrorRef ↔ NSError
CFLocaleRef / CFTimeZoneRef↔ NSLocale, NSTimeZone
CFRunLoopRefrelated to NSRunLoop but NOT toll-free bridged

Core Graphics

CGContextRefthe drawing destination; save and restore its state
CGColorRef / CGImageRef / CGPathRefthe drawing objects
CGAffineTransformMake, Translate, Scale, Rotate, Concat, Invert
CGContextSaveGState / RestoreGStatealways in pairs
CGColorSpaceCreateDeviceRGBand the sRGB and generic variants
CGBitmapContextCreatedraw into memory you own

Tooling & Diagnostics

38

clang

-fobjc-arc / -fno-objc-arcARC, per translation unit
-fobjc-weakzeroing weak references without full ARC
-fobjc-arc-exceptionsemit ARC cleanup on the unwind path. OFF by default
-fmodulesenable @import and module-based framework headers
-framework Foundationlink a framework; -F adds a search path
-x objective-cforce the dialect
--analyzethe static analyser: leaks, over-releases, ARC naming violations
-Wobjc-missing-super-callshonour NS_REQUIRES_SUPER
-Wformat-securitycatches NSLog(str)
-fsanitize=address / thread / undefinedASan, TSan, UBSan
xcrun --sdk macosx --show-sdk-pathfind the SDK the frameworks live in
xcodebuild -scheme X -destination …build and test from a script

lldb

po objprint object — calls -debugDescription
p (NSUInteger)[a count]lldb needs the cast for non-object returns
expr -- [v setHidden:NO]run code in the target, then continue
po [obj _ivarDescription]every ivar and its value. Undocumented, invaluable
po [obj _shortMethodDescription]this class’s own methods
b objc_exception_throwthe most useful breakpoint in the language
breakpoint set -n "-[UIView setFrame:]"break on a framework selector
bt / bt all / frame select nstacks and navigation
image lookup -a <addr>which symbol is at this address
thread returnabandon a frame to step past a crash
po $arg1 / $arg2self and _cmd, by the calling convention
watchpoint set variable _xbreak when a value changes

Environment and CLI tools

NSZombieEnabled=YESfreed objects log instead of crashing randomly. Leaks memory
MallocStackLogging=1allocation backtraces; needed by leaks and Instruments
MallocScribble=1fill freed memory with 0x55
MallocGuardEdges=1guard pages around large allocations
OBJC_PRINT_REPLACED_METHODS=YESsee who swizzled what
NSShowAllViews=YESAppKit: outline every view
leaks <pid>cycle detection on a live process
heap <pid>object counts by class — the shape of a leak
malloc_history <pid> <addr>where that block was allocated
otool -oV <binary>dump the Objective-C metadata segment
nm -m <binary> | grep objcclasses and categories the binary defines
class-dumpthird-party: reconstruct headers from a binary
log stream --predicate …read os_log output back
instruments -t "Time Profiler"profiling from a script; Leaks and Allocations too