Claude Code Plugins

Community-maintained marketplace

Feedback

lang-objc-dev

@aRustyDev/ai
0
0

Foundational Objective-C patterns covering classes, protocols, categories, memory management (ARC/retain-release), blocks, GCD, and Foundation framework. Use when writing Objective-C code, working with Cocoa/Cocoa Touch APIs, bridging to Swift, or needing guidance on Apple platform development patterns. This is the entry point for Objective-C development.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name lang-objc-dev
description Foundational Objective-C patterns covering classes, protocols, categories, memory management (ARC/retain-release), blocks, GCD, and Foundation framework. Use when writing Objective-C code, working with Cocoa/Cocoa Touch APIs, bridging to Swift, or needing guidance on Apple platform development patterns. This is the entry point for Objective-C development.

Objective-C Fundamentals

Foundational Objective-C patterns and core language features. This skill serves as both a reference for common patterns and an index to specialized Objective-C skills.

Overview

┌─────────────────────────────────────────────────────────────────┐
│                  Objective-C Skill Hierarchy                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                  ┌─────────────────────┐                        │
│                  │   lang-objc-dev     │ ◄── You are here       │
│                  │   (foundation)      │                        │
│                  └──────────┬──────────┘                        │
│                             │                                   │
│            ┌────────────────┼────────────────┐                  │
│            │                │                │                  │
│            ▼                ▼                ▼                  │
│    ┌──────────────┐  ┌──────────┐   ┌──────────────┐          │
│    │   patterns   │  │ library  │   │    swift     │          │
│    │     -dev     │  │   -dev   │   │   -bridge    │          │
│    └──────────────┘  └──────────┘   └──────────────┘          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

This skill covers:

  • Class declaration and implementation
  • Protocols (formal and informal)
  • Categories and extensions
  • Memory management (ARC, retain/release cycles)
  • Properties and accessors
  • Blocks and closures
  • Grand Central Dispatch (GCD) basics
  • Foundation framework essentials
  • Common Objective-C idioms

This skill does NOT cover (see specialized skills):

  • Advanced design patterns → lang-objc-patterns-dev
  • Framework/library development → lang-objc-library-dev
  • Swift/Objective-C interoperability → lang-swift-objc-bridge-dev
  • iOS/macOS UI development → platform-specific skills
  • Core Data patterns → ios-coredata-dev

Quick Reference

Task Syntax
Declare class @interface ClassName : Superclass
Define method - (ReturnType)methodName:(Type)param
Class method + (ReturnType)methodName
Property @property (attributes) Type *name;
Protocol @protocol ProtocolName
Category @interface ClassName (CategoryName)
Block type ReturnType (^blockName)(ParamTypes)
Strong reference @property (strong) Type *obj;
Weak reference @property (weak) Type *obj;
Copy property @property (copy) NSString *str;

Skill Routing

Use this table to find the right specialized skill:

When you need to... Use this skill
Implement MVC, Singleton, Delegate patterns lang-objc-patterns-dev
Build reusable frameworks, static libraries lang-objc-library-dev
Bridge Objective-C code to Swift lang-swift-objc-bridge-dev
Work with UIKit/AppKit Platform-specific UI skills
Implement Core Data models ios-coredata-dev

Classes and Objects

Class Declaration

// Header file (.h)
#import <Foundation/Foundation.h>

@interface Person : NSObject

// Properties
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;

// Instance methods
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
- (void)greet;
- (NSString *)description;

// Class methods
+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age;

@end

Class Implementation

// Implementation file (.m)
#import "Person.h"

@implementation Person

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    if (self) {
        _name = name;
        _age = age;
    }
    return self;
}

+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age {
    return [[self alloc] initWithName:name age:age];
}

- (void)greet {
    NSLog(@"Hello, my name is %@", self.name);
}

- (NSString *)description {
    return [NSString stringWithFormat:@"Person(name: %@, age: %ld)",
            self.name, (long)self.age];
}

@end

Creating Instances

// Using designated initializer
Person *person1 = [[Person alloc] initWithName:@"Alice" age:30];

// Using convenience class method
Person *person2 = [Person personWithName:@"Bob" age:25];

// Calling methods
[person1 greet];
NSLog(@"%@", person1.description);

// Accessing properties
NSString *name = person1.name;
person1.age = 31;

Properties and Accessors

Property Attributes

@interface User : NSObject

// Memory management
@property (strong) NSObject *strongRef;      // Retains object (default)
@property (weak) id<Delegate> delegate;      // Weak reference, no retain
@property (assign) NSInteger count;          // Simple assignment (scalars)
@property (copy) NSString *username;         // Creates copy on assignment

// Atomicity
@property (atomic) NSString *threadSafe;     // Thread-safe (default, slower)
@property (nonatomic) NSString *fast;        // Not thread-safe (faster)

// Read/write
@property (readonly) NSString *identifier;   // No setter generated
@property (readwrite) NSString *editable;    // Both getter/setter (default)

// Custom accessors
@property (getter=isEnabled) BOOL enabled;   // Custom getter name
@property (setter=setCustomValue:) id value; // Custom setter name

@end

Custom Getters and Setters

@implementation User {
    NSString *_computedProperty;
}

// Custom getter
- (NSString *)computedProperty {
    if (!_computedProperty) {
        _computedProperty = [self calculateValue];
    }
    return _computedProperty;
}

// Custom setter with validation
- (void)setUsername:(NSString *)username {
    if (username.length > 0) {
        _username = [username copy];
    }
}

@end

Property Synthesis

@implementation User

// Automatic synthesis (Xcode does this automatically)
// @synthesize username = _username;

// Manual backing variable name
@synthesize customName = _backingVariable;

// Dynamic property (implemented at runtime)
@dynamic runtimeProperty;

@end

Protocols

Defining Protocols

// Formal protocol
@protocol Drawable <NSObject>

@required  // Must be implemented
- (void)draw;
- (CGRect)bounds;

@optional  // Can be implemented
- (void)setColor:(UIColor *)color;
- (UIColor *)color;

@end

Adopting Protocols

// Header: Adopt protocol
@interface Circle : NSObject <Drawable>
@property (nonatomic, assign) CGFloat radius;
@end

// Implementation
@implementation Circle

- (void)draw {
    // Drawing implementation
    NSLog(@"Drawing circle with radius %f", self.radius);
}

- (CGRect)bounds {
    return CGRectMake(0, 0, self.radius * 2, self.radius * 2);
}

// Optional method
- (void)setColor:(UIColor *)color {
    // Optional color implementation
}

@end

Protocol Types and Conformance

// Variable can hold any object conforming to Drawable
id<Drawable> drawable = [[Circle alloc] init];

// Check protocol conformance
if ([object conformsToProtocol:@protocol(Drawable)]) {
    [(id<Drawable>)object draw];
}

// Check if optional method is implemented
if ([drawable respondsToSelector:@selector(setColor:)]) {
    [drawable setColor:[UIColor blueColor]];
}

Multiple Protocol Adoption

@interface AdvancedShape : NSObject <Drawable, Serializable, Comparable>
@end

Categories and Extensions

Categories (Public)

// Header: NSString+Validation.h
@interface NSString (Validation)
- (BOOL)isValidEmail;
- (BOOL)isValidURL;
@end

// Implementation: NSString+Validation.m
@implementation NSString (Validation)

- (BOOL)isValidEmail {
    NSString *pattern = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
    return [predicate evaluateWithObject:self];
}

- (BOOL)isValidURL {
    return [NSURL URLWithString:self] != nil;
}

@end

// Usage
#import "NSString+Validation.h"
NSString *email = @"user@example.com";
if ([email isValidEmail]) {
    NSLog(@"Valid email");
}

Class Extensions (Private)

// Class extension in .m file (private interface)
@interface Person ()
// Private properties
@property (nonatomic, strong) NSMutableArray *privateData;

// Private methods
- (void)internalMethod;
@end

@implementation Person

- (void)internalMethod {
    // Implementation only visible in this file
}

@end

Categories with Properties

#import <objc/runtime.h>

@interface UIView (CustomTag)
@property (nonatomic, strong) NSString *customTag;
@end

@implementation UIView (CustomTag)

static char kCustomTagKey;

- (void)setCustomTag:(NSString *)customTag {
    objc_setAssociatedObject(self, &kCustomTagKey, customTag,
                            OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)customTag {
    return objc_getAssociatedObject(self, &kCustomTagKey);
}

@end

Memory Management

ARC (Automatic Reference Counting)

// Strong reference (default for objects)
@property (strong) NSString *name;  // Keeps object alive

// Weak reference (doesn't prevent deallocation)
@property (weak) id<Delegate> delegate;  // Becomes nil when deallocated

// Unretained (like weak but doesn't nil out)
@property (unsafe_unretained) id observer;  // Dangerous: can dangle

// Copy (for immutable/mutable pairs)
@property (copy) NSString *title;  // Always creates immutable copy

Retain Cycles and Solutions

// PROBLEM: Retain cycle
@interface Parent : NSObject
@property (strong) Child *child;
@end

@interface Child : NSObject
@property (strong) Parent *parent;  // Creates cycle!
@end

// SOLUTION 1: Weak reference
@interface Child : NSObject
@property (weak) Parent *parent;  // Breaks cycle
@end

// SOLUTION 2: Block retain cycles
@interface MyClass : NSObject
@property (copy) void (^completion)(void);
@end

@implementation MyClass

- (void)setupCompletion {
    // WRONG: Captures self strongly
    self.completion = ^{
        [self doSomething];  // Retain cycle!
    };

    // CORRECT: Use weak-strong dance
    __weak typeof(self) weakSelf = self;
    self.completion = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            [strongSelf doSomething];
        }
    };
}

@end

Manual Memory Management (Pre-ARC)

// Only relevant for legacy code or when ARC is disabled

// Ownership rules (retain, release, autorelease)
- (void)legacyMemoryManagement {
    // alloc/init creates object with retain count 1
    NSString *owned = [[NSString alloc] initWithString:@"owned"];

    // retain increases retain count
    [owned retain];

    // release decreases retain count
    [owned release];
    [owned release];  // Final release, object deallocated

    // autorelease defers release until later
    NSString *temp = [[[NSString alloc] init] autorelease];

    // copy creates new object with retain count 1
    NSString *copied = [owned copy];
    [copied release];
}

// dealloc method
- (void)dealloc {
    [_property release];
    [super dealloc];  // Required in MRC
}

Blocks (Closures)

Block Syntax

// Block type definition
typedef void (^CompletionBlock)(BOOL success, NSError *error);
typedef NSInteger (^MathBlock)(NSInteger a, NSInteger b);

// Block variable
CompletionBlock completion = ^(BOOL success, NSError *error) {
    if (success) {
        NSLog(@"Success!");
    } else {
        NSLog(@"Error: %@", error);
    }
};

// Calling block
completion(YES, nil);

// Block as parameter
- (void)performOperationWithCompletion:(CompletionBlock)completion {
    // ... do work
    completion(YES, nil);
}

Block Captures

- (void)demonstrateCaptures {
    NSInteger count = 0;

    // Captures count by value
    void (^block1)(void) = ^{
        NSLog(@"Count: %ld", (long)count);  // Prints captured value
    };

    count = 10;
    block1();  // Still prints 0

    // Mutable capture with __block
    __block NSInteger mutableCount = 0;
    void (^block2)(void) = ^{
        mutableCount++;  // Can modify
        NSLog(@"Mutable count: %ld", (long)mutableCount);
    };

    block2();  // Prints 1
    NSLog(@"After block: %ld", (long)mutableCount);  // Prints 1
}

Common Block Patterns

// Enumeration with blocks
NSArray *numbers = @[@1, @2, @3, @4, @5];
[numbers enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL *stop) {
    NSLog(@"Index %lu: %@", (unsigned long)idx, num);
    if ([num integerValue] == 3) {
        *stop = YES;  // Stop enumeration
    }
}];

// Filtering with predicates
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(NSNumber *num, NSDictionary *bindings) {
    return [num integerValue] > 2;
}];
NSArray *filtered = [numbers filteredArrayUsingPredicate:predicate];

// Sorting with comparator
NSArray *sorted = [numbers sortedArrayUsingComparator:^NSComparisonResult(NSNumber *a, NSNumber *b) {
    return [a compare:b];
}];

// Completion handlers
- (void)fetchDataWithCompletion:(void (^)(NSData *data, NSError *error))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // Fetch data
        NSData *data = [self loadData];

        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion) {
                completion(data, nil);
            }
        });
    });
}

Grand Central Dispatch (GCD)

Dispatch Queues

// Main queue (UI updates)
dispatch_async(dispatch_get_main_queue(), ^{
    self.label.text = @"Updated on main thread";
});

// Global concurrent queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    // Background work
    NSData *data = [self heavyComputation];

    // Update UI on main queue
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateUIWithData:data];
    });
});

// Custom serial queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
    // Serial execution
});

// Custom concurrent queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

Synchronous vs Asynchronous

// Asynchronous (doesn't block)
dispatch_async(queue, ^{
    NSLog(@"Async work");
});

// Synchronous (blocks until complete)
dispatch_sync(queue, ^{
    NSLog(@"Sync work");
});
// WARNING: Never call dispatch_sync on current queue (deadlock!)

// Dispatch after delay
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
               dispatch_get_main_queue(), ^{
    NSLog(@"Executed after 2 seconds");
});

Dispatch Groups

// Wait for multiple operations
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// Add tasks to group
dispatch_group_async(group, queue, ^{
    NSLog(@"Task 1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"Task 2");
});

// Notify when all complete
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"All tasks complete");
});

// Or wait synchronously
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

Dispatch Barriers

// Ensure exclusive access in concurrent queue
dispatch_barrier_async(concurrentQueue, ^{
    // This executes exclusively (no other tasks run concurrently)
    [self.mutableArray addObject:object];
});

Foundation Framework Essentials

NSString

// Creating strings
NSString *literal = @"Hello";
NSString *formatted = [NSString stringWithFormat:@"Value: %d", 42];
NSString *fromFile = [NSString stringWithContentsOfFile:path
                                                encoding:NSUTF8StringEncoding
                                                   error:&error];

// String operations
NSString *upper = [str uppercaseString];
NSString *lower = [str lowercaseString];
NSRange range = [str rangeOfString:@"substring"];
BOOL contains = [str containsString:@"text"];
NSString *replaced = [str stringByReplacingOccurrencesOfString:@"old" withString:@"new"];

// Mutable strings
NSMutableString *mutable = [NSMutableString stringWithString:@"Hello"];
[mutable appendString:@" World"];
[mutable insertString:@"Beautiful " atIndex:6];
[mutable deleteCharactersInRange:NSMakeRange(0, 5)];

NSArray and NSMutableArray

// Creating arrays
NSArray *array = @[@"one", @"two", @"three"];
NSArray *array2 = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];

// Array operations
NSUInteger count = array.count;
id firstObject = array.firstObject;
id lastObject = array.lastObject;
id objectAtIndex = array[1];  // Modern syntax
BOOL contains = [array containsObject:@"two"];
NSUInteger index = [array indexOfObject:@"two"];

// Iteration
for (NSString *item in array) {
    NSLog(@"%@", item);
}

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    NSLog(@"%lu: %@", (unsigned long)idx, obj);
}];

// Mutable arrays
NSMutableArray *mutable = [NSMutableArray arrayWithArray:array];
[mutable addObject:@"four"];
[mutable insertObject:@"zero" atIndex:0];
[mutable removeObject:@"two"];
[mutable removeObjectAtIndex:0];

NSDictionary and NSMutableDictionary

// Creating dictionaries
NSDictionary *dict = @{
    @"name": @"Alice",
    @"age": @30,
    @"city": @"NYC"
};

// Dictionary operations
id value = dict[@"name"];  // Modern syntax
id value2 = [dict objectForKey:@"age"];
NSArray *keys = dict.allKeys;
NSArray *values = dict.allValues;
NSUInteger count = dict.count;

// Iteration
for (NSString *key in dict) {
    NSLog(@"%@: %@", key, dict[key]);
}

[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    NSLog(@"%@: %@", key, obj);
}];

// Mutable dictionaries
NSMutableDictionary *mutable = [NSMutableDictionary dictionaryWithDictionary:dict];
mutable[@"email"] = @"alice@example.com";
[mutable setObject:@31 forKey:@"age"];
[mutable removeObjectForKey:@"city"];

NSSet and NSMutableSet

// Creating sets (unordered, unique elements)
NSSet *set = [NSSet setWithObjects:@"a", @"b", @"c", nil];
NSSet *set2 = [NSSet setWithArray:array];

// Set operations
BOOL contains = [set containsObject:@"a"];
NSUInteger count = set.count;

// Set algebra
NSSet *union = [set setByAddingObjectsFromSet:set2];
NSMutableSet *mutable = [set mutableCopy];
[mutable intersectSet:set2];

// Iteration
for (id object in set) {
    NSLog(@"%@", object);
}

NSNumber and Boxing

// Boxing primitives
NSNumber *intNumber = @42;
NSNumber *floatNumber = @3.14f;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'A';

// Boxing expressions
NSNumber *result = @(1 + 2 * 3);

// Unboxing
NSInteger intValue = [intNumber integerValue];
float floatValue = [floatNumber floatValue];
BOOL boolValue = [boolNumber boolValue];

// Using in collections
NSArray *numbers = @[@1, @2, @3, @4, @5];

Common Patterns

Singleton Pattern

@interface NetworkManager : NSObject
+ (instancetype)sharedManager;
@end

@implementation NetworkManager

+ (instancetype)sharedManager {
    static NetworkManager *shared = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shared = [[self alloc] init];
    });
    return shared;
}

@end

// Usage
NetworkManager *manager = [NetworkManager sharedManager];

Delegate Pattern

// Define protocol
@protocol DataSourceDelegate <NSObject>
@required
- (NSInteger)numberOfItems;
@optional
- (void)didSelectItemAtIndex:(NSInteger)index;
@end

// Class with delegate
@interface DataSource : NSObject
@property (weak, nonatomic) id<DataSourceDelegate> delegate;
@end

@implementation DataSource

- (void)loadData {
    if ([self.delegate respondsToSelector:@selector(numberOfItems)]) {
        NSInteger count = [self.delegate numberOfItems];
        NSLog(@"Items: %ld", (long)count);
    }
}

- (void)selectItem:(NSInteger)index {
    if ([self.delegate respondsToSelector:@selector(didSelectItemAtIndex:)]) {
        [self.delegate didSelectItemAtIndex:index];
    }
}

@end

KVO (Key-Value Observing)

// Add observer
[person addObserver:self
         forKeyPath:@"name"
            options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
            context:NULL];

// Observe changes
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        NSString *oldValue = change[NSKeyValueChangeOldKey];
        NSString *newValue = change[NSKeyValueChangeNewKey];
        NSLog(@"Name changed from %@ to %@", oldValue, newValue);
    }
}

// Remove observer (important!)
- (void)dealloc {
    [person removeObserver:self forKeyPath:@"name"];
}

Notifications

// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"DataUpdated"
                                                    object:self
                                                  userInfo:@{@"count": @5}];

// Observe notification
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(handleDataUpdated:)
                                             name:@"DataUpdated"
                                           object:nil];

- (void)handleDataUpdated:(NSNotification *)notification {
    NSDictionary *userInfo = notification.userInfo;
    NSNumber *count = userInfo[@"count"];
    NSLog(@"Data updated, count: %@", count);
}

// Remove observer (important!)
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Error Handling

NSError Pattern

// Method that can fail
- (BOOL)loadDataFromFile:(NSString *)path error:(NSError **)error {
    NSData *data = [NSData dataWithContentsOfFile:path
                                          options:0
                                            error:error];
    if (!data) {
        return NO;  // error is populated by the framework
    }

    // Process data
    return YES;
}

// Creating custom errors
- (BOOL)validateUser:(User *)user error:(NSError **)error {
    if (user.name.length == 0) {
        if (error) {
            *error = [NSError errorWithDomain:@"com.example.validation"
                                         code:100
                                     userInfo:@{
                                         NSLocalizedDescriptionKey: @"Name is required",
                                         NSLocalizedFailureReasonErrorKey: @"User name cannot be empty"
                                     }];
        }
        return NO;
    }
    return YES;
}

// Using error methods
NSError *error = nil;
BOOL success = [self loadDataFromFile:@"data.txt" error:&error];
if (!success) {
    NSLog(@"Error: %@", error.localizedDescription);
    NSLog(@"Reason: %@", error.localizedFailureReason);
}

Exception Handling

// Try-catch (use sparingly in Objective-C)
@try {
    NSArray *array = @[@1, @2, @3];
    id value = array[10];  // Out of bounds
}
@catch (NSException *exception) {
    NSLog(@"Exception: %@, reason: %@", exception.name, exception.reason);
}
@finally {
    NSLog(@"Cleanup code");
}

// Assertions
NSAssert(count > 0, @"Count must be positive");
NSParameterAssert(user != nil);

Troubleshooting

Unrecognized Selector

Problem: -[ClassName selectorName:]: unrecognized selector sent to instance

// Cause: Calling method that doesn't exist
[object methodThatDoesntExist];

// Fix 1: Check method exists
if ([object respondsToSelector:@selector(optionalMethod)]) {
    [object optionalMethod];
}

// Fix 2: Add missing method implementation
- (void)methodThatDoesntExist {
    // Implementation
}

Memory Leaks and Retain Cycles

Problem: Objects not being deallocated

// Check for retain cycles with weak references
@property (weak) id<Delegate> delegate;  // Not strong!

// Use weak-strong dance in blocks
__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        [strongSelf doWork];
    }
};

// Remove observers
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [self.observedObject removeObserver:self forKeyPath:@"property"];
}

Nil Messaging

Problem: Sending messages to nil

// In Objective-C, sending messages to nil is safe (returns nil/0)
NSString *str = nil;
NSUInteger length = [str length];  // Returns 0, doesn't crash

// But can hide bugs
id result = [nil someMethod];  // Returns nil, might mask issue

// Check for nil when necessary
if (object) {
    [object doSomething];
}

Property Attribute Mistakes

Problem: Incorrect memory management

// WRONG: Strong reference to delegate (retain cycle)
@property (strong) id<Delegate> delegate;

// CORRECT: Weak reference
@property (weak) id<Delegate> delegate;

// WRONG: Mutable string without copy
@property (strong) NSString *name;
// Caller could pass NSMutableString and mutate it

// CORRECT: Use copy for strings
@property (copy) NSString *name;

Thread Safety Issues

Problem: Accessing shared state from multiple threads

// WRONG: Direct access from multiple threads
self.mutableArray = [NSMutableArray array];
dispatch_async(queue1, ^{ [self.mutableArray addObject:obj1]; });
dispatch_async(queue2, ^{ [self.mutableArray addObject:obj2]; });

// CORRECT: Synchronize access
@synchronized(self.mutableArray) {
    [self.mutableArray addObject:obj];
}

// BETTER: Use serial queue or barrier
dispatch_barrier_async(self.concurrentQueue, ^{
    [self.mutableArray addObject:obj];
});

Module System

Header and Implementation Files

// Header file (.h) - Public interface
#import <Foundation/Foundation.h>

// Forward declaration (avoid importing in header)
@class OtherClass;
@protocol SomeProtocol;

NS_ASSUME_NONNULL_BEGIN

@interface MyClass : NSObject

// Public API
@property (nonatomic, readonly) NSString *publicName;
- (instancetype)initWithName:(NSString *)name;
- (void)publicMethod;

@end

NS_ASSUME_NONNULL_END
// Implementation file (.m) - Private interface and implementation
#import "MyClass.h"
#import "OtherClass.h"  // Import here, not in header

// Private class extension
@interface MyClass ()
@property (nonatomic, readwrite) NSString *publicName;  // Make writable internally
@property (nonatomic, strong) NSMutableArray *privateData;
- (void)privateMethod;
@end

@implementation MyClass

- (instancetype)initWithName:(NSString *)name {
    self = [super init];
    if (self) {
        _publicName = [name copy];
        _privateData = [NSMutableArray array];
    }
    return self;
}

- (void)publicMethod {
    [self privateMethod];
}

- (void)privateMethod {
    // Private implementation
}

@end

Import Types

// Framework import (system or SDK frameworks)
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

// Local header import
#import "MyClass.h"

// Umbrella header (for frameworks)
#import <MyFramework/MyFramework.h>

// Module import (modern, preferred)
@import Foundation;
@import UIKit;

// Conditional import
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#else
#import <AppKit/AppKit.h>
#endif

Visibility and Encapsulation

// Public: Declared in .h file
// In MyClass.h
- (void)publicMethod;

// Private: Declared in class extension in .m
@interface MyClass ()
- (void)privateMethod;
@end

// Protected: Use conventions (no language enforcement)
// By convention, prefix with underscore or document as internal
- (void)_internalMethod;  // Signals internal use

// Package visibility (via categories in separate header)
// MyClass+Internal.h - Only import where needed
@interface MyClass (Internal)
- (void)internalMethod;
@end

Framework Structure

MyFramework.framework/
├── Headers/
│   ├── MyFramework.h           # Umbrella header
│   ├── PublicClass.h           # Public headers
│   └── AnotherPublicClass.h
├── PrivateHeaders/             # Private headers (not exposed)
│   └── InternalClass.h
├── Modules/
│   └── module.modulemap        # Module definition
└── MyFramework                 # Binary
// Umbrella header (MyFramework.h)
#import <MyFramework/PublicClass.h>
#import <MyFramework/AnotherPublicClass.h>
// Do NOT import private headers here

Zero and Default Values

Nil and NULL

// nil: Null pointer to Objective-C object
NSString *string = nil;

// NULL: Null pointer to C data (primitive pointers)
int *pointer = NULL;
void (*functionPointer)(void) = NULL;

// Nil: Null pointer to Objective-C class (rarely used)
Class cls = Nil;

// NSNull: Object representing null (for collections)
NSArray *array = @[@"one", [NSNull null], @"three"];

Safe Nil Messaging

// Sending messages to nil is safe in Objective-C
NSString *string = nil;

// All return 0/nil/NO (doesn't crash)
NSUInteger length = [string length];         // Returns 0
NSString *upper = [string uppercaseString];  // Returns nil
BOOL empty = [string isEqualToString:@""];   // Returns NO

// Chaining with nil
NSArray *items = nil;
NSNumber *first = items.firstObject;  // nil
NSString *desc = [first stringValue]; // nil

Instance Variable Defaults

@implementation MyClass {
    NSString *_string;      // Initialized to nil
    NSInteger _integer;     // Initialized to 0
    CGFloat _float;         // Initialized to 0.0
    BOOL _boolean;          // Initialized to NO
    id _object;             // Initialized to nil
}

// Verify defaults in init
- (instancetype)init {
    self = [super init];
    if (self) {
        NSAssert(_string == nil, @"Should be nil");
        NSAssert(_integer == 0, @"Should be 0");
        NSAssert(_boolean == NO, @"Should be NO");
    }
    return self;
}

@end

Default/Empty Values by Type

Type Default Value Empty/Zero Value
id, objects nil nil
NSInteger 0 0
CGFloat, double 0.0 0.0
BOOL NO NO
NSString * nil @""
NSArray * nil @[]
NSDictionary * nil @{}
NSNumber * nil @0

Handling Optional Values

// Check before use
if (string != nil && string.length > 0) {
    [self processString:string];
}

// Provide defaults
NSString *name = user.name ?: @"Unknown";

// NSNull checking (from JSON)
id value = json[@"key"];
if (value && value != [NSNull null]) {
    NSString *stringValue = (NSString *)value;
}

// Safe casting
NSString *string = [object isKindOfClass:[NSString class]] ? object : nil;

Metaprogramming

Objective-C's runtime enables powerful metaprogramming capabilities. For cross-language comparison, see patterns-metaprogramming-dev.

Runtime Introspection

#import <objc/runtime.h>

// Class introspection
Class cls = [MyClass class];
NSLog(@"Class name: %s", class_getName(cls));
NSLog(@"Superclass: %@", [cls superclass]);

// Check class and protocol conformance
if ([object isKindOfClass:[NSString class]]) {
    NSLog(@"Object is a string");
}
if ([object conformsToProtocol:@protocol(NSCopying)]) {
    NSLog(@"Object is copyable");
}

// Check method implementation
if ([object respondsToSelector:@selector(optionalMethod)]) {
    [object optionalMethod];
}

Listing Methods and Properties

// List all methods
unsigned int methodCount;
Method *methods = class_copyMethodList([MyClass class], &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
    SEL selector = method_getName(methods[i]);
    NSLog(@"Method: %@", NSStringFromSelector(selector));
}
free(methods);

// List all properties
unsigned int propCount;
objc_property_t *properties = class_copyPropertyList([MyClass class], &propCount);
for (unsigned int i = 0; i < propCount; i++) {
    const char *name = property_getName(properties[i]);
    const char *attrs = property_getAttributes(properties[i]);
    NSLog(@"Property: %s, Attrs: %s", name, attrs);
}
free(properties);

// List protocols
unsigned int protocolCount;
Protocol * __unsafe_unretained *protocols = class_copyProtocolList([MyClass class], &protocolCount);
for (unsigned int i = 0; i < protocolCount; i++) {
    NSLog(@"Protocol: %s", protocol_getName(protocols[i]));
}
free(protocols);

Method Swizzling

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewDidAppear:);
        SEL swizzledSelector = @selector(tracking_viewDidAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class, originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector,
                               method_getImplementation(originalMethod),
                               method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)tracking_viewDidAppear:(BOOL)animated {
    // This calls the original viewDidAppear (methods are swapped)
    [self tracking_viewDidAppear:animated];

    // Add tracking
    NSLog(@"View appeared: %@", NSStringFromClass([self class]));
}

@end

Dynamic Method Resolution

@implementation DynamicClass

// Called when method not found
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(dynamicMethod)) {
        class_addMethod([self class], sel,
                       (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@"Dynamic method called on %@", self);
}

@end

Message Forwarding

// Called when no implementation found
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([self.helper respondsToSelector:aSelector]) {
        return self.helper;  // Forward to helper
    }
    return [super forwardingTargetForSelector:aSelector];
}

// Full forwarding (if forwardingTargetForSelector returns nil)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
    if (!sig) {
        sig = [self.helper methodSignatureForSelector:aSelector];
    }
    return sig;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([self.helper respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self.helper];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

Serialization

For cross-language serialization patterns and comparison, see patterns-serialization-dev.

NSCoding (Archive/Unarchive)

@interface User : NSObject <NSSecureCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSDate *createdAt;
@end

@implementation User

// Required for NSSecureCoding
+ (BOOL)supportsSecureCoding {
    return YES;
}

// Encode object
- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.name forKey:@"name"];
    [coder encodeInteger:self.age forKey:@"age"];
    [coder encodeObject:self.createdAt forKey:@"createdAt"];
}

// Decode object
- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        _name = [coder decodeObjectOfClass:[NSString class] forKey:@"name"];
        _age = [coder decodeIntegerForKey:@"age"];
        _createdAt = [coder decodeObjectOfClass:[NSDate class] forKey:@"createdAt"];
    }
    return self;
}

@end

// Archive to data
User *user = [[User alloc] init];
user.name = @"Alice";
user.age = 30;

NSError *error;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:user
                                     requiringSecureCoding:YES
                                                     error:&error];

// Unarchive from data
User *restored = [NSKeyedUnarchiver unarchivedObjectOfClass:[User class]
                                                   fromData:data
                                                      error:&error];

JSON Serialization

// Dictionary to JSON
NSDictionary *dict = @{
    @"name": @"Alice",
    @"age": @30,
    @"email": @"alice@example.com"
};

NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict
                                                   options:NSJSONWritingPrettyPrinted
                                                     error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData
                                             encoding:NSUTF8StringEncoding];

// JSON to Dictionary
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *parsed = [NSJSONSerialization JSONObjectWithData:data
                                                       options:0
                                                         error:&error];

// Handle NSNull in JSON
id value = parsed[@"nullable_field"];
if (value && value != [NSNull null]) {
    // Use value
}

Manual JSON Mapping

@interface User : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSDate *createdAt;

+ (instancetype)userFromDictionary:(NSDictionary *)dict;
- (NSDictionary *)toDictionary;
@end

@implementation User

+ (instancetype)userFromDictionary:(NSDictionary *)dict {
    User *user = [[User alloc] init];
    user.name = dict[@"name"];
    user.age = [dict[@"age"] integerValue];

    // Parse date
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
    user.createdAt = [formatter dateFromString:dict[@"created_at"]];

    return user;
}

- (NSDictionary *)toDictionary {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";

    return @{
        @"name": self.name ?: [NSNull null],
        @"age": @(self.age),
        @"created_at": self.createdAt ? [formatter stringFromDate:self.createdAt] : [NSNull null]
    };
}

@end

Property Lists

// Save to plist file
NSDictionary *config = @{
    @"version": @"1.0.0",
    @"settings": @{
        @"darkMode": @YES,
        @"fontSize": @14
    }
};

NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]
                  stringByAppendingPathComponent:@"config.plist"];
[config writeToFile:path atomically:YES];

// Load from plist
NSDictionary *loaded = [NSDictionary dictionaryWithContentsOfFile:path];

// UserDefaults (plist-based)
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"value" forKey:@"key"];
[defaults setBool:YES forKey:@"boolKey"];
[defaults synchronize];

NSString *value = [defaults stringForKey:@"key"];
BOOL boolValue = [defaults boolForKey:@"boolKey"];

Build and Dependencies

Xcode Project Structure

MyProject/
├── MyProject.xcodeproj/      # Xcode project file
├── MyProject/                # Main target source
│   ├── AppDelegate.h/m
│   ├── Models/
│   ├── Views/
│   ├── Controllers/
│   └── Info.plist
├── MyProjectTests/           # Unit test target
│   └── MyProjectTests.m
├── MyProjectUITests/         # UI test target
│   └── MyProjectUITests.m
└── Podfile                   # CocoaPods config (if used)

CocoaPods

# Podfile
platform :ios, '13.0'
use_frameworks!

target 'MyApp' do
  pod 'AFNetworking', '~> 4.0'
  pod 'Masonry'
  pod 'SDWebImage'

  target 'MyAppTests' do
    inherit! :search_paths
    pod 'OCMock'
  end
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
    end
  end
end
# CocoaPods commands
pod init                  # Create Podfile
pod install              # Install dependencies
pod update               # Update dependencies
pod outdated             # Check for updates
pod deintegrate          # Remove CocoaPods

Swift Package Manager (SPM)

// Package.swift (for library development)
// swift-tools-version:5.5
import PackageDescription

let package = Package(
    name: "MyLibrary",
    platforms: [.iOS(.v13), .macOS(.v10_15)],
    products: [
        .library(name: "MyLibrary", targets: ["MyLibrary"]),
    ],
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0"),
    ],
    targets: [
        .target(name: "MyLibrary", dependencies: ["Alamofire"]),
        .testTarget(name: "MyLibraryTests", dependencies: ["MyLibrary"]),
    ]
)

In Xcode: File → Add Packages → Enter repository URL

Carthage

# Cartfile
github "Alamofire/Alamofire" ~> 5.0
github "ReactiveX/RxSwift" ~> 6.0
# Carthage commands
carthage update --platform iOS          # Build frameworks
carthage bootstrap --platform iOS       # Build from resolved versions
carthage outdated                       # Check for updates

Build Settings

// Common build settings (Build Settings tab)

// Architectures
ARCHS = arm64
VALID_ARCHS = arm64

// Deployment
IPHONEOS_DEPLOYMENT_TARGET = 13.0
TARGETED_DEVICE_FAMILY = 1,2  // iPhone and iPad

// Code Signing
CODE_SIGN_IDENTITY = Apple Development
DEVELOPMENT_TEAM = XXXXXXXXXX

// Compilation
GCC_WARN_UNINITIALIZED_AUTOS = YES
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
CLANG_ENABLE_OBJC_ARC = YES

// Linking
OTHER_LDFLAGS = -ObjC  // For static libraries with categories

Testing

XCTest Basics

// MyClassTests.m
#import <XCTest/XCTest.h>
#import "MyClass.h"

@interface MyClassTests : XCTestCase
@property (nonatomic, strong) MyClass *sut;  // System Under Test
@end

@implementation MyClassTests

- (void)setUp {
    [super setUp];
    self.sut = [[MyClass alloc] init];
}

- (void)tearDown {
    self.sut = nil;
    [super tearDown];
}

- (void)testExample {
    // Arrange
    NSString *input = @"test";

    // Act
    NSString *result = [self.sut processInput:input];

    // Assert
    XCTAssertNotNil(result);
    XCTAssertEqualObjects(result, @"expected");
}

@end

XCTest Assertions

// Boolean assertions
XCTAssertTrue(condition);
XCTAssertFalse(condition);

// Nil assertions
XCTAssertNil(object);
XCTAssertNotNil(object);

// Equality assertions
XCTAssertEqual(actual, expected);           // Primitive equality
XCTAssertNotEqual(actual, notExpected);
XCTAssertEqualObjects(actual, expected);    // Object equality (isEqual:)
XCTAssertNotEqualObjects(actual, notExpected);

// Numeric assertions
XCTAssertEqualWithAccuracy(3.14, 3.14159, 0.01);
XCTAssertGreaterThan(5, 3);
XCTAssertLessThanOrEqual(3, 5);

// Exception assertions
XCTAssertThrows([object methodThatThrows]);
XCTAssertNoThrow([object safeMethod]);
XCTAssertThrowsSpecific([object method], NSInvalidArgumentException);

// Custom failure
XCTFail(@"Test failed for reason: %@", reason);

Async Testing

// Using expectations
- (void)testAsyncOperation {
    XCTestExpectation *expectation = [self expectationWithDescription:@"Async completed"];

    [self.sut performAsyncOperationWithCompletion:^(NSData *data, NSError *error) {
        XCTAssertNotNil(data);
        XCTAssertNil(error);
        [expectation fulfill];
    }];

    [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
        if (error) {
            XCTFail(@"Timeout: %@", error);
        }
    }];
}

// Multiple expectations
- (void)testMultipleAsync {
    XCTestExpectation *exp1 = [self expectationWithDescription:@"First"];
    XCTestExpectation *exp2 = [self expectationWithDescription:@"Second"];

    [self.service fetchFirst:^{ [exp1 fulfill]; }];
    [self.service fetchSecond:^{ [exp2 fulfill]; }];

    [self waitForExpectations:@[exp1, exp2] timeout:10.0];
}

// Inverted expectation (should NOT be called)
- (void)testCallbackNotCalled {
    XCTestExpectation *expectation = [self expectationWithDescription:@"Should not call"];
    expectation.inverted = YES;

    [self.sut methodThatShouldNotCallback:^{
        [expectation fulfill];  // This would fail the test
    }];

    [self waitForExpectationsWithTimeout:1.0 handler:nil];
}

Mocking with OCMock

#import <OCMock/OCMock.h>

- (void)testWithMock {
    // Create mock
    id mockService = OCMClassMock([NetworkService class]);

    // Stub method
    OCMStub([mockService fetchDataWithCompletion:OCMOCK_ANY])
        .andDo(^(NSInvocation *invocation) {
            void (^completion)(NSData *, NSError *);
            [invocation getArgument:&completion atIndex:2];
            completion([@"mock data" dataUsingEncoding:NSUTF8StringEncoding], nil);
        });

    // Inject mock
    self.sut.networkService = mockService;

    // Test
    [self.sut loadData];

    // Verify
    OCMVerify([mockService fetchDataWithCompletion:OCMOCK_ANY]);
}

// Partial mock
- (void)testPartialMock {
    id partialMock = OCMPartialMock(self.sut);
    OCMStub([partialMock expensiveOperation]).andReturn(@"cached");

    NSString *result = [self.sut methodThatCallsExpensiveOperation];
    XCTAssertEqualObjects(result, @"cached");
}

// Protocol mock
- (void)testProtocolMock {
    id mockDelegate = OCMProtocolMock(@protocol(MyDelegate));
    self.sut.delegate = mockDelegate;

    [self.sut triggerDelegate];

    OCMVerify([mockDelegate didComplete:OCMOCK_ANY]);
}

Test Doubles

// Stub: Replace with canned response
@interface StubNetworkService : NetworkService
@property (nonatomic, strong) NSData *stubbedData;
@end

@implementation StubNetworkService
- (void)fetchWithCompletion:(void (^)(NSData *))completion {
    completion(self.stubbedData);
}
@end

// Spy: Record calls
@interface SpyLogger : Logger
@property (nonatomic, strong) NSMutableArray *loggedMessages;
@end

@implementation SpyLogger
- (instancetype)init {
    self = [super init];
    if (self) { _loggedMessages = [NSMutableArray array]; }
    return self;
}
- (void)log:(NSString *)message {
    [self.loggedMessages addObject:message];
}
@end

// Fake: Working implementation
@interface FakeUserDefaults : NSUserDefaults
@property (nonatomic, strong) NSMutableDictionary *storage;
@end

@implementation FakeUserDefaults
- (void)setObject:(id)value forKey:(NSString *)key {
    self.storage[key] = value;
}
- (id)objectForKey:(NSString *)key {
    return self.storage[key];
}
@end

Cross-Cutting Patterns

For cross-language comparison and translation patterns, see:

  • patterns-concurrency-dev - GCD, NSOperation, threading
  • patterns-serialization-dev - NSCoding, JSON, property lists
  • patterns-metaprogramming-dev - Runtime, swizzling, introspection

References