Objective-C (2.0)

This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].

1 Assessment

This page assesses Objective-C in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.

Core coupling 42/100 · Ecosystem coupling 65/100 · Engineering readiness 53/100

1.1 Core coupling

1.1.1 Type

  • ❌ Dispatch is resolved at run time (T3): A message send such as [obj doThing] compiles to objc_msgSend, and the selector is looked up as the message is delivered, so an object can receive a message its declared interface does not list and the fault appears as unrecognized selector sent to instance
  • ❌ There is no namespace (T6): Every class and protocol shares one global name table, so code relies on prefixes such as NS, UI, and CG to keep names apart
  • ❌ id erases the static type (T7): The generic object pointer carries no compile-time type, so a value held in id is checked only by -isKindOfClass: or -respondsToSelector: at run time
  • ✅ Protocols separate specification from implementation: A @protocol declares a method set that any class can adopt with <NSCopying>, so conformance does not require inheritance and one class can adopt several protocols
  • ✅ Categories extend a class without its source: @interface NSString (MyExtras) adds methods to an existing class, so a consumer extends behaviour without subclassing and without access to the original
  • ⚠️ Static type information is optional and partly erased: Declared parameter and return types (NSString *, instancetype) are checked at compile time, but lightweight generics such as NSArray<NSString *> * and the nullability annotations nonnull and nullable are erased at run time

1.1.2 Construction

  • ❌ There is no immutability by default (C2): NSString, NSArray, and NSDictionary each have a mutable counterpart, and a property declared strong without copy shares a mutable object with its setter; @property (copy) is the opt-in that protects it
  • ✅ alloc and init separate allocation from initialisation: [[NSObject alloc] init] and a designated initialiser such as -initWithFrame: let one class route every construction path through one setup method
  • ✅ @property synthesises the accessors: @property (nonatomic, copy) NSString *name; generates the getter, the setter, and the instance variable, so a consumer binds to the property name rather than to the ivar layout
  • ✅ Blocks carry behaviour as a value: A ^-literal closure captures its environment and is passed as an argument, as in -[NSArray enumerateObjectsUsingBlock:], so a callback needs no delegate class
  • ✅ Class clusters hide the concrete class: NSNumber and NSString return a private subclass from a factory method such as +numberWithInt:, so construction does not couple a caller to one implementation class
  • ⚠️ +new and -init are conventions, not enforced rules: The runtime does not require an object to be initialised, so an uninitialised instance is possible and the class author must insist on the designated initialiser

1.1.3 Memory

  • ✅ ARC removes manual retain and release: The compiler inserts objc_retain, objc_release, and the autorelease-pool calls at the right points, so the common case needs no memory-management call
  • ✅ strong and weak state the ownership intent: @property (weak) id delegate; and __weak make the reference weak, and a weak reference zeroes itself when its referent is deallocated
  • ⚠️ An autorelease pool defers the release: An autoreleased object lives until its enclosing @autoreleasepool drains, so a loop that builds many objects adds its own pool
  • ❌ There is no RAII (M3): ARC is not block-scoped, so a resource that is not an object, such as a FILE *, a malloc block, or a Core Foundation handle, is released by an explicit call or a @try and @finally block
  • ❌ There is no ownership model (M4): No static lifetime or aliasing rule bounds an object, so a strong reference cycle leaks until it is broken with weak
  • ❌ Reclamation timing is not a language rule (M2): The language never states when an object is freed; the tracing collector that once shipped with the Apple runtime was deprecated and then removed, and ARC’s reference counting is a compiler convention rather than a language guarantee

1.1.4 Error

  • ❌ Exceptions are unchecked (Er2): @throw, @try, @catch, and @finally carry an NSException that no signature declares, so a caller cannot see the failure set, and the convention reserves exceptions for programmer errors
  • ✅ NSError ** reports a recoverable failure: A method such as -[NSString writeToFile:atomically:encoding:error:] writes the failure into an out-parameter, so a caller recovers without an exception
  • ✅ @try, @catch, and @finally are structured: A @catch clause names the exception class, and @finally runs on both the normal and the exceptional path
  • ⚠️ Two error conventions coexist: NSException marks a programming error and NSError marks a domain failure, so a caller must know which convention a given API follows
  • ⚠️ A message to nil fails silently: [nil doThing] returns nil or 0 and raises nothing, so a missed wiring point produces no error
  • ⚠️ NSAssert and NSParameterAssert are run-time checks: They abort in a debug build and are compiled out under NS_BLOCK_ASSERTIONS, so they do not enforce a contract in a release build

1.1.5 Concurrency

  • ❌ Synchronisation is manual with no race detection (K3): @synchronized, NSLock, NSRecursiveLock, and dispatch_semaphore leave the reasoning to the programmer, and the compiler reports no data race
  • ✅ Grand Central Dispatch separates work from threads: dispatch_async(dispatch_get_global_queue(0, 0), ^{ ... }) submits a block to a queue, so the code names a queue rather than a thread
  • ✅ A completion block returns on a named queue: An asynchronous API takes a block and a queue, so a caller need not share mutable state with the worker
  • ✅ NSOperationQueue declares dependencies: -[NSOperation addDependency:] orders operations in the queue, so sequencing is stated rather than hand-scheduled
  • ✅ @synchronized gives a per-object mutex: @synchronized(self) { ... } serialises a critical section on one object without a separate lock object
  • ⚠️ @property (atomic) does not make a compound operation atomic: The default attribute serialises each accessor call, so a check-then-set sequence over the same property can still interleave with another thread

1.1.6 Platform

  • ❌ One vendor owns the mainstream platform (P2): The complete language, its reference compiler (Clang), and its flagship frameworks (Cocoa and Cocoa Touch) are Apple’s
  • ✅ GNUstep provides a second implementation: The GNUstep runtime, Foundation, and AppKit reimplementations let much Objective-C code build off Apple’s platform
  • ✅ The language is a strict superset of C: A .m file mixes C functions and structs with message sends, so any C library is callable directly
  • ✅ Modules replace recursive header inclusion: @import Foundation; imports a clang module, so a build does not depend on #import order
  • ⚠️ The frameworks differ by platform: AppKit on macOS and UIKit on iOS share Foundation but not the user-interface layer, so UI code does not move between them

1.1.7 Trade-offs (not coupling)

  • Message-passing syntax is a familiarity cost: [receiver selector:argument] differs from dot-syntax object languages, and the adjustment is a learning property rather than a forced dependency
  • Long method names are a deliberate style: -[NSString stringByReplacingOccurrencesOfString:withString:] is verbose but self-describing, which is a readability choice
  • Manual retain and release remains available: A project can leave ARC off and manage lifetimes itself, so the older memory model is a choice rather than a requirement
  • NSException for programmer errors is a policy: Treating an exception as a bug signal and NSError as a recovery signal is a convention the language permits but does not require
  • Prefixes mitigate the flat namespace: A class prefix is a convention that reduces collisions; it is a workaround for the missing namespace, not an additional coupling
  • A mixed Objective-C and Swift target is a migration decision: Both languages share the runtime and call each other, so how far to migrate is a project choice

1.2 Ecosystem coupling

1.2.1 Performance

  • ✅ The language compiles to native code: Clang lowers Objective-C to machine code through LLVM, so a message send is a real call and a program needs no warm-up
  • ✅ The runtime is thin and written in C: libobjc supplies the selector table, the class metadata, and the reference-counting helpers with little work beyond the message send
  • ⚠️ objc_msgSend costs more than a static call: Every message send performs a selector lookup that is cached after the first use, so a tight loop of message sends is slower than the equivalent direct call
  • ⚠️ Dynamic dispatch limits inlining and devirtualisation: Because the receiver’s class is known only at run time, the compiler cannot always inline a method body
  • ⚠️ Key-value observing and method swizzling add indirection: Both change how a method runs after the fact, and the cost appears wherever they are installed

1.2.2 Ecosystem

  • ❌ The primary ecosystem is proprietary (Ec4): The reference documentation, the frameworks, and the IDE (Xcode) come from one vendor under its licence terms
  • ✅ The toolchain is mature inside that ecosystem: Xcode supplies one editor, compiler, debugger (lldb), Interface Builder, and the Instruments profiler in one install
  • ✅ CocoaPods, Carthage, and Swift Package Manager resolve dependencies: Several long-established package managers exist for Objective-C libraries
  • ✅ GNUstep supplies a free Foundation: A portable implementation covers NSString, NSArray, and the rest of the Foundation API off Apple’s platform
  • ⚠️ Current material is written for Swift first: The platform APIs and sample code are presented for Swift, so an Objective-C reader works from older or generated headers

1.2.3 Evolution

  • ❌ The language is frozen (V2): Objective-C 2.0 (2006) is effectively the last language revision, and platform-facing work has moved to Swift
  • ✅ The compiler keeps supplying new features: ARC, blocks, object literals such as @[] and @{}, lightweight generics, and nullability annotations arrived as Clang features rather than as language revisions
  • ✅ A mixed Objective-C and Swift target is supported: Both languages link against the same runtime and can call each other, so a project can migrate one file at a time
  • ✅ Old source still compiles: OpenStep-era Foundation API and the 1990s NeXT runtime conventions are still supported by the current toolchain
  • ⚠️ Documentation for new APIs follows the vendor’s current language: A header is the reliable reference for the Objective-C form of a new API

1.2.4 Domain

  • ❌ The domain and the vendor are one decision (D1): The language is built for Apple’s platforms, so adopting it means shipping on macOS, iOS, iPadOS, watchOS, and tvOS
  • ✅ Excellent fit for the Apple application model: The Cocoa delegate, target-action, and notification patterns and the main run loop are expressed directly in the language
  • ✅ Foundation runs off Apple’s platform: Portable Foundation code from GNUstep works on Linux and other systems for command-line and server work
  • ⚠️ Cross-platform work needs a second toolkit: GNUstep supplies Foundation but has no equivalent of a modern Apple user-interface layer
  • ⚠️ Server and command-line use is marginal: The language is used for it, but the design centre is the application platform

1.2.5 Learning

  • ❌ Message passing is a barrier for a newcomer (L1): [receiver selector:argument], the colon-bearing selector notation, and the @-keyword set differ from dot-syntax object languages
  • ❌ The C substrate must be learned as well: Pointers, struct, C strings, manual C memory, and @encode sit underneath the object model, so the language is C plus message passing
  • ⚠️ The runtime is a second topic: objc_msgSend, metaclasses, key-value coding and observing, and ARC semantics are separate from the source syntax
  • ⚠️ Documentation quality follows the vendor: Apple’s guides and headers are thorough on the platform, and off-platform material is thinner
  • ✅ The object model is conceptually small: Once the message send is understood, most of the rest follows from it, including classes, selectors, and protocols

1.2.6 Syntax

  • ✅ A message send names every argument: [view addSubview:subview positioned:NSWindowBelow relativeTo:nil] labels each argument at the call site, so the call reads as a sentence
  • ✅ Object literals are compact: @42, @[@"a", @"b"], and @{@"k": @1} build an NSNumber, an NSArray, and an NSDictionary in one form
  • ✅ @property shortens an accessor pair: One declaration replaces a hand-written getter, setter, and instance variable
  • ⚠️ The selector includes its colons: addSubview:positioned:relativeTo: is one name, so a missing or an extra colon changes the selector
  • ⚠️ Dot syntax and brackets both reach an accessor: obj.prop and [obj prop] are interchangeable, so a reader must accept two spellings for the same call
  • ⚠️ The preprocessor is part of the surface: #import, #define, and macro-based API patterns are load-bearing in existing code

1.2.7 Testing

  • ✅ XCTest ships with the platform toolchain: XCTestCase, the XCTAssert family, and performance tests are part of the developer tools
  • ✅ Instruments profiles a real run: The Time Profiler, Allocations, and Leaks instruments show where time and memory go, so a test can measure a change
  • ✅ The debugger understands messages: lldb can break on a selector, inspect the arguments of a message send, and evaluate an expression in the running process
  • ⚠️ The framework is tied to the toolchain: XCTest and the simulator come from one vendor, so an off-platform run needs GNUstep and a different harness
  • ⚠️ A wrong selector passes the build: With dynamic dispatch a missing method or a wrong type is a run-time failure, so the tested paths matter more than the compiled ones

1.2.8 Security

  • ✅ ARC removes a class of memory faults: The compiler owns retain and release, so a use-after-free from a missing release is rare in ARC code
  • ✅ A weak reference does not dangle: weak and __weak zero the reference when its referent is deallocated, so a stale link becomes nil
  • ✅ A message to nil is safe: [nil doThing] returns nil or 0 instead of trapping, which removes a class of null-dereference crash
  • ⚠️ id permits type confusion: A value typed id accepts any object, and a wrong cast is caught only where the code checks the class
  • ⚠️ C is still underneath: malloc, pointer arithmetic, and a file compiled with -fno-objc-arc bypass every object-model guarantee
  • ⚠️ Method swizzling changes behaviour after the build: method_exchangeImplementations replaces an implementation at run time, which is powerful and easy to misuse

1.3 Engineering readiness

1.3.1 Reproducibility

  • ❌ Builds are not byte-reproducible by default.

1.3.2 Supply chain

  • ⚠️ Published packages carry no signed provenance by default.
  • ⚠️ The package registry is a flat name space.

1.3.3 Observability

  • ⚠️ No stable tracing and metrics standard.

1.3.4 Feedback

  • ✅ No charged mechanism in this dimension.

1.3.5 Interop

  • ⚠️ No maintained portable target.

1.3.6 Governance

  • ⚠️ One vendor decides the language’s direction.
  • ⚠️ The specification is not public.

1.4 Profile

Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.

1.4.1 Energy and cost

What this axis asks: what does a running program cost in energy and time?

  • ✅ Compiles to native code: the fastest class in the language energy and time benchmarks [5], [6]. A causal re-analysis attributes the difference to execution time [7].

1.4.2 AI and agent readiness

What this axis asks: how well does a coding model and an agent work in this language?

  • ⚠️ Moderate training data: models write it competently, with measurable language-specific weakness [8].

1.4.3 Safety certification

What this axis asks: is there a certified or standards-qualified path to a safety-critical system?

  • ⚠️ No certified or standards-qualified path for safety-critical systems.

1.5 Scoring detail

1.5.1 Core coupling Dimensions

Dimension Mechanism Description Present Cost
Type T1 dynamic no static check — 0
T2 weak static — 0
T3 dynamic/duck dispatch ✅ −8
T4 no sum types — 0
T5 no generics — 0
T6 no namespaces ✅ −8
T7 type erasure/reflection ✅ −4
T8 operator overloading — 0
T9 inheritance required for polymorphism — 0
T10 no null-safety — 0
T11 no module boundary — 0
T12 no effect tracking ✅ −8
T13 async/sync colouring — 0
T14 unchecked foreign boundary ✅ −4
Type subtotal −15 (−32)
Construction C1 global mutable state by default — 0
C2 no immutability by default ✅ −4
C3 constructors coupled to allocation — 0
C4 no constructor/validation — 0
Construction subtotal −4
Memory M1 manual deallocation — 0
M2 GC non-deterministic pauses ✅ −8
M3 no RAII ✅ −4
M4 no ownership model ✅ −8
Memory subtotal −15 (−20)
Error Er1 no structured path — 0
Er2 unchecked exceptions only ✅ −8
Er3 errors ignored/non-exhaustive — 0
Er4 no composable result type — 0
Error subtotal −8
Concurrency K1 GIL/no parallel — 0
K2 shared mutable state default — 0
K3 manual sync no race detection ✅ −8
K4 no structured concurrency — 0
K5 no primitives — 0
Concurrency subtotal −8
Platform P1 cannot function outside one vendor/domain — 0
P2 single-vendor but other uses ✅ −8
P3 legacy/discontinued platform — 0
Platform subtotal −8

Core Dimensions score: 42/100 — total cost = −58 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.2 Ecosystem coupling Dimensions

Dimension Mechanism Description Present Cost
Performance R1 interpreted no AOT/JIT — 0
R2 GC pauses — 0
R3 JIT warmup — 0
Performance subtotal 0
Ecosystem Ec1 no package manager — 0
Ec2 fragmented tooling — 0
Ec3 small/niche/discontinued — 0
Ec4 proprietary ✅ −8
Ec5 platform-locked tooling — 0
Ec6 monoculture — 0
Ec7 legacy burden — 0
Ecosystem subtotal −8
Evolution V1 breaking changes — 0
V2 frozen ✅ −4
V3 discontinued — 0
V4 glacial standards — 0
Evolution subtotal −4
Domain D1 domain/vendor lock-in ✅ −15
D2 poor domain fit — 0
D3 framework forces architecture — 0
Domain subtotal −15
Learning L1 steep ✅ −8
L2 large surface/multi-paradigm — 0
L3 sparse docs — 0
Learning subtotal −8
Syntax S1 verbose — 0
S2 inconsistent — 0
S3 punctuation-heavy — 0
Syntax subtotal 0
Testing G1 weak tooling — 0
G2 poor debugging — 0
G3 runtime-only errors — 0
Testing subtotal 0
Security Q1 memory-unsafe — 0
Q2 dynamic/weak typing risk — 0
Q3 known vulnerability/injection — 0
Security subtotal 0

Meta Dimensions score: 65/100 — total cost = −35 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.3 Engineering readiness Dimensions

Dimension Mechanism Description Present Cost
Reproducibility Y1 build not reproducible ✅ −8
Y2 dependencies not pinned — 0
Reproducibility subtotal −8
Supply chain Y3 no signed provenance ✅ −8
Y4 registry without namespacing ✅ −4
Supply chain subtotal −12
Observability Y5 no stable tracing standard ✅ −8
Y6 no structured logging — 0
Observability subtotal −8
Feedback Y7 slow build — 0
Y8 no fast in-editor analysis — 0
Feedback subtotal 0
Interop Y9 no foreign-function interface — 0
Y10 no portable target ✅ −4
Interop subtotal −4
Governance Y11 single-vendor governance ✅ −8
Y12 closed specification ✅ −8
Governance subtotal −15 (−16)

Readiness Dimensions score: 53/100 — total cost = −47 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

Verdict: Objective-C’s design yields moderate language-imposed coupling, and its weakest core dimensions are the ones the language was built around. Dynamic dispatch, the flat global namespace, and the id pointer defer type and dispatch checks to run time (T3, T6, T7). Against that, protocols give multiple inheritance of specification without implementation, categories extend a class without its source, blocks carry a callback as a value, and ARC removes manual retain and release while weak makes a stale reference zero itself. Its main weaknesses lie underneath and around the object model: there is no ownership rule and no block-scoped release, exceptions are unchecked, and the entire platform is one vendor’s, so the domain, the evolution, and the ecosystem are all locked to the same decision.


The formal development. The definitions, theorems, and proofs behind this page are published in:

All published papers

References

[1]
Y. Loth, “On the nature of coupling: All coupling as shared resource access.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925122.
[2]
Y. Loth, “Coupling as a consequence of cohesion: Decomposition, the coupling floor, and domain-imposed coupling.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925201.
[3]
Y. Loth, “On the nature of cohesion: Cohesion as correctness relative to a partitioning rule.” Zenodo, Jul. 2026. doi: 10.5281/zenodo.21703193.
[4]
A. Inc., “Objective-c documentation.” Apple Inc., 2024. Available: https://developer.apple.com/documentation/objectivec
[5]
R. Pereira et al., “Energy efficiency across programming languages: How do energy, time, and memory relate?” in Proceedings of the 10th ACM SIGPLAN international conference on software language engineering (SLE), 2017, pp. 256–267. doi: 10.1145/3136014.3136031.
[6]
R. Pereira et al., “Ranking programming languages by energy efficiency,” Science of Computer Programming, 2021, doi: 10.1016/j.scico.2021.102609.
[7]
N. van Kempen, H.-J. Kwon, D. T. Nguyen, and E. D. Berger, “It’s not easy being green: On the energy efficiency of programming languages,” in Proceedings of the 40th IEEE/ACM international conference on automated software engineering (ASE), 2025. doi: 10.1109/ASE63991.2025.00131.
[8]
S. Joel, J. J. Wu, and F. H. Fard, “A survey on LLM-based code generation for low-resource and domain-specific programming languages,” ACM Transactions on Software Engineering and Methodology, 2025, doi: 10.1145/3770084.