Use when writing or reviewing Objective-C — including adding features to existing ObjC pages — debugging retain cycles, reasoning about blocks or ARC, using runtime features, or bridging Objective-C with Swift.
Resources
2Install
npx skillscat add faimin/zdappledevelopskills/objective-c-patterns Install via the SkillsCat registry.
SKILL.md
Objective-C Patterns
When to Use
Use this skill when the task involves Objective-C language behavior rather than app-specific product logic:
- Writing or reviewing Objective-C classes, categories, or protocols
- Debugging ownership bugs, leaks, or
EXC_BAD_ACCESS - Choosing between delegates, blocks, target-action, KVO, or runtime hooks
- Evaluating swizzling, forwarding, associated objects, or other dynamic behavior
- Designing Objective-C and Swift boundaries for mixed-language codebases
Start with the router, then open only the references needed for the current question.
Operating Rules
- Prefer simple Objective-C language features before reaching for runtime mutation.
- Nil-check optional blocks before invocation, for example
if (completion) { completion(...); }. - Treat retain cycles as ownership design bugs, not cleanup chores.
- Prefer inheritance, composition, or explicit integration points before any hook or swizzle.
- Use swizzling only when a direct override, wrapper, delegate, or explicit integration point is unavailable.
- Prefix category APIs with a project-specific abbreviation (e.g.
xx_), keep categories lightweight, and move complex stateful logic into real classes. - Prefix private methods with
_. - In getter methods, build the object into a local variable and assign
_ivaras the final statement; seereferences/coding-conventions.md. - Wrap multi-property object initialization in a
({ })statement expression to group creation and configuration; seereferences/coding-conventions.md. - Check
respondsToSelector:before invoking any optional delegate method; seereferences/coding-conventions.md. - Prefer
RACObserveover raw KVO when the project depends onReactiveObjC; seereferences/kvc-kvo-and-associations.md. - Group methods with
#pragma mark -and split very large implementations into focused categories when it improves readability. - Place
- (void)deallocnear the top of@implementationso teardown stays visible during reviews. - Guard uncertain collection indexing before subscripting, and prefer
firstObjectorlastObjectfor boundary access. - Use early returns for nil, invalid state, or failed preconditions instead of nesting conditionals.
- Route bridging questions to the interop reference before proposing mixed-language APIs.
- Keep dynamic behavior local and easy to remove. If a pattern cannot be explained in a few sentences, it is probably the wrong default.
- For crash or leak triage, confirm the symptom first, then narrow to ownership, threading, or runtime mutation before editing code.
Topic Router
Consult the matching reference before giving implementation advice:
| Topic | Reference |
|---|---|
| Blocks, captures, ARC ownership | references/blocks-and-memory.md |
| Runtime APIs and swizzling | references/runtime-and-swizzling.md |
| Forwarding and dynamic dispatch | references/message-forwarding.md |
| KVC, KVO, RAC observation | references/kvc-kvo-and-associations.md |
| Coding conventions, getter patterns, delegate guards | references/coding-conventions.md |
| Queues, locks, legacy async work | references/threading-and-gcd.md |
| Swift bridging boundaries | references/objc-swift-interop.md |
| Crash and leak triage | references/diagnostics.md |
Memory Checklist
Use this checklist when ownership or lifecycle is unclear:
- Identify who owns the object now, and who should own it after the change.
- Check whether the relationship is one-shot, repeated, or long-lived.
- Keep delegates
weakunless the platform contract says otherwise. - Treat stored blocks as owners of the objects they capture.
- Break cycles by redesigning the relationship first, not by sprinkling
nilassignments later. - Verify notification, KVO, timer, display link, and dispatch source teardown paths.
- For bridged Swift APIs, confirm nullability and collection element types are explicit.
Retain-Cycle Workflow
- Find the strong ownership loop.
- Name the expected owner for each node in the loop.
- Decide which edge should become non-owning or shorter-lived.
- Replace the pattern with a better boundary:
- delegate for long-lived callbacks
- block for one-shot completion
- injected coordinator or service for shared orchestration
- Use
weakcapture only where the ownership model truly requires it. - Re-check deallocation with a breakpoint, Instruments, or a temporary
dealloclog.
Typical loop shapes:
- controller -> view model -> completion block -> controller
- owner -> timer or display link -> target -> owner
- object A -> object B -> delegate-like property -> object A
- object -> associated object -> block -> object
Quick Diagnostics
| Symptom | Likely cause | Reference |
|---|---|---|
| Object never deallocates | Block capture or delegate ownership bug | references/blocks-and-memory.md |
EXC_BAD_ACCESS after async callback |
Object deallocated earlier than expected | references/diagnostics.md |
| Strange behavior after adding category hooks | Swizzle collision or wrong method exchange timing | references/runtime-and-swizzling.md |
| Message sent to wrong helper object | Forwarding chain too broad or incomplete | references/message-forwarding.md |
| Observer crash on property change | KVO lifecycle mismatch | references/kvc-kvo-and-associations.md |
| UI state updates from background queue | Missing main-thread hop | references/threading-and-gcd.md |
| Swift sees awkward Objective-C API | Missing nullability or poor naming surface | references/objc-swift-interop.md |
Common Mistakes
- Using associated objects to avoid creating a real subclass or stored property owner.
- Swizzling framework methods without collision guards, original implementation checks, or a rollback plan.
- Assuming
[weak self]is always correct. Weak capture is a tool, not the ownership model. - Using blocks for long-lived relationships that should be delegates or explicit collaborators.
- Invoking optional blocks without checking whether they are non-nil.
- Hiding complex behavior in broad categories instead of a subclass or helper object.
- Using raw subscripting on uncertain arrays or sets when
firstObject,lastObject, or bounds checks would make the contract explicit. - Forwarding selectors broadly enough to hide missing APIs or typos.
- Mixing KVO, manual ivar mutation, and direct setter bypasses without understanding notification behavior.
- Exposing Swift generics, structs, or async-only APIs directly to Objective-C callers that cannot consume them safely.
- Treating intermittent crashes as "ARC bugs" before checking lifetime, queueing, and swizzle interactions.