Objective-C: 언어의 본질과 iOS 개발에서의 위치

저자: IT Sectr 게시일: 2026-02-09 읽는 시간: 8 분

Objective-C는 동적 메시지 디스패치를 갖춘 프로그래밍 언어로, 1980년대 Brad Cox가 만들었습니다. Apple은 NeXTSTEP의 주요 언어로, 이후 iOS SDK의 주요 언어로 Objective-C를 선택했습니다. Programming With Objective-C — 메시지 구문과 메모리 관리 소개.

핵심 요점

  • Objective-C — 대괄호 메시지 구문과 동적 런타임을 갖춘 C의 엄격한 슈퍼셋
  • Message passing — [object selector:argument]를 통한 메서드 호출과 objc_msgSend를 통한 동적 구현 검색
  • Categories — 상속 및 소스 코드 접근 없이 기존 클래스에 메서드를 추가하는 메커니즘
  • Blocks — 클로저가 있는 익명 함수, Swift 클로저의 전신
  • ARC vs MRC — 메모리 관리를 위한 자동 및 수동 참조 카운팅

Objective-C란?

Objective-C는 Smalltalk 스타일의 객체 지향 프로그래밍 기능으로 C를 확장하는 프로그래밍 언어입니다. Objective-C 코드는 LLVM 또는 GCC를 통해 네이티브 머신 코드로 컴파일되며, C와의 완전한 하위 호환성을 유지합니다 — 모든 C 코드는 Objective-C에서 유효합니다.

Apple은 1997년 NeXT(Objective-C와 함께)를 인수했습니다. 이 언어는 macOS와 iOS의 프레임워크인 Cocoa와 Cocoa Touch의 기초가 되었습니다. 2014년부터 Apple은 Swift를 대체 언어로 추진하고 있지만, Objective-C는 레거시 프로젝트와 일부 시스템 프레임워크에 여전히 매우 중요합니다. Apple(WWDC 2024)에 따르면, App Store 앱의 약 35%가 여전히 Objective-C 코드를 포함하고 있습니다.

주요 특징은 동적 런타임입니다. 메서드 호출이 컴파일 시간에 해결되는 Swift와 달리, Objective-C는 objc_msgSend 함수를 통해 런타임에 메시지를 보냅니다. 이를 통해 메서드 즉시 재정의(method swizzling), 동적 클래스 추가, 존재하지 않는 선택자에 대한 forward invocation 사용이 가능합니다.

구문: C + 객체 지향 확장

모든 C 코드는 Objective-C에서 유효합니다. 객체 지향 확장은 클래스(@interface/@implementation), 카테고리, 프로토콜(@protocol), 동적 타이핑(id)을 추가합니다. 파일 확장자는 .m(구현)과 .h(헤더)입니다.

메시지 구문과 동적 디스패치

메시지 구문이 Objective-C와 C 계열 언어의 주요 차이점입니다. object.method(argument) 대신 [object method:argument]를 사용합니다. 각 메시지는 objc_msgSend를 통과하며, 런타임에서 동적으로 메서드 구현을 찾습니다.

objective-c
// 명명된 매개변수가 있는 메시지 구문
NSString *greeting = [NSString stringWithFormat:@"Hello, %@", name];

// 중첩 메시지
NSArray *sortedArray = [[array sortedArrayUsingSelector:@selector(compare:)] copy];

// 인트로스펙션을 통한 타입 확인
if ([object isKindOfClass:[UIView class]]) {
    UIView *view = (UIView *)object;
    view.backgroundColor = [UIColor redColor];
}

동적 디스패치는 런타임에 메서드를 재정의(method swizzling)할 수 있게 합니다 — 강력하지만 위험한 기술입니다. 예를 들어, AFNetworking 및 Aspects와 같은 프레임워크는 URLSession 호출을 가로채기 위해 swizzling을 사용합니다. Apple은 경고합니다: 잘못 사용하면 swizzling이 시스템 프레임워크를 손상시킬 수 있습니다.

카테고리와 클래스 확장

카테고리는 상속 및 소스 코드 접근 없이 기존 클래스(NSString 및 UIView와 같은 시스템 클래스 포함)에 메서드를 추가할 수 있는 Objective-C의 고유한 기능입니다. 카테고리는 @interface ClassName (CategoryName)을 사용하여 선언됩니다.

objective-c
// UIColor+Hex.h 파일 — UIColor용 카테고리
@interface UIColor (Hex)
+ (instancetype)colorWithHex:NSUIntegerhex;
@end

// UIColor+Hex.m 파일 — 구현
@implementation UIColor (Hex)
+ (instancetype)colorWithHex:NSUIntegerhex {
    CGFloat r = ((hex >> 16) & 0xFF) / 255.0;
    CGFloat g = ((hex >> 8) & 0xFF) / 255.0;
    CGFloat b = (hex & 0xFF) / 255.0;
    return [self colorWithRed:r green:g blue:b alpha:1.0];
}
@end

확장(Extensions)(Class Extension)은 이름 없이 .m 파일에 선언되는 특별한 종류의 카테고리입니다: @interface ClassName (). 카테고리와 달리 확장은 메서드뿐만 아니라 ivar(인스턴스 변수)와 프로퍼티도 추가할 수 있습니다. 확장은 외부 모듈에서 내부 API를 숨기는 데 사용됩니다.

블록(Blocks) — 클로저가 있는 익명 함수

블록은 외부 스코프에서 변수를 캡처하는 Objective-C의 익명 함수입니다. 블록 구문: ^(int x) { return x * 2; }. 블록은 UIKit의 콜백 핸들러, Grand Central Dispatch의 비동기 작업, NSArray/NSDictionary 컬렉션의 함수형 연산에 사용됩니다.

objective-c
// 블록 타입 선언
typedef void (^CompletionBlock)(BOOL success, NSError *error);

// 매개변수로서의 블록
- (void)fetchDataWithCompletion:(CompletionBlock)completion {
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        BOOL result = [weakSelf processData];
        if (completion) {
            completion(result, nil);
        }
    });
}

블록의 메모리 관리는 매우 중요합니다. 블록은 self를 강한 참조로 캡처하여 직접 사용 시 retain cycle을 만듭니다. 해결책은 __weak typeof(self) weakSelf = self, 이어서 블록 내부에서 확인하는 것입니다. 이 문제는 Swift에서 capture lists [weak self]를 통해 완전히 해결되었습니다.

ARC와 MRC: 메모리 관리

Objective-C는 MRC(Manual Reference Counting)에서 ARC(Automatic Reference Counting)로 발전했습니다. MRC에서는 개발자가 수동으로 retain(카운터 증가), release(감소), autorelease(지연 해제)를 호출했습니다. 이러한 호출의 오류는 메모리 누수 또는 댕글링 포인터로 인한 충돌을 일으켰습니다.

작업MRCARC
객체 생성[[Object alloc] init][[Object alloc] init]
참조 유지[object retain]자동
해제[object release]자동
자동 해제[object autorelease]자동(필요 없음)
약한 참조__weak 존재하지 않음__weak(자동 nil)

ARC는 Xcode 4.2와 LLVM 3.0(2011)에서 도입되었습니다. 컴파일러는 객체 수명을 분석하여 컴파일 시간에 자동으로 retain/release를 삽입합니다. ARC는 가비지 컬렉션이 아닙니다 — 자동 삽입이 있는 정적 분석입니다. Objective-C ARC는 Swift ARC와 호환됩니다: 둘 다 런타임 수준에서 동일한 참조 카운팅 시스템을 사용합니다.

Bridging Header를 통한 Swift와의 상호 운용성

하나의 프로젝트에서 Objective-C와 Swift를 함께 사용하는 것은 레거시 프로젝트에서 일반적인 관행입니다. Apple은 Bridging Header를 제공합니다 — Swift가 Objective-C 클래스를 보고 Objective-C가 NSObject를 상속하는 Swift 클래스를 보는 자동 생성 파일입니다.

objective-c
// ProjectName-Bridging-Header.h
// Swift가 이 헤더들을 자동으로 인식합니다
#import "LegacyManager.h"
#import "NetworkClient.h"
#import "DataStore.h"

Objective-C에서 Swift로의 마이그레이션은 점진적인 과정입니다. 새 파일은 Swift로 작성되고, 기존 파일은 점진적으로 리팩터링됩니다. Bridging Header는 Objective-C 프로젝트에 첫 번째 Swift 파일을 추가할 때 자동으로 생성됩니다. 역방향 가시성(ObjC → Swift)을 위해 Xcode는 @objc로 표시된 Swift 클래스의 @interface 선언이 포함된 <ProjectName>-Swift.h 파일을 생성합니다.

swift
// Objective-C에서 볼 수 있는 Swift 클래스
@objc class SwiftRouter: NSObject {
    @objc func navigateToProfile(userId: Int) {
        // 구현
    }
}

제한 사항: Swift의 값 타입(struct, enum)은 Objective-C에서 직접 볼 수 없습니다 — @objc와 함께 클래스로 래핑해야 합니다. Swift의 제네릭은 Objective-C에서 제한적으로 접근 가능합니다. 권장 접근 방식은 새 코드를 Swift로 작성하고 기존 Objective-C 코드는 해당 기능을 변경할 때만 리팩터링하는 것입니다.

Objective-C의 런타임과 Method Swizzling

Swift에 대한 Objective-C의 주요 장점은 런타임에 대한 완전한 접근입니다. Method swizzling(런타임에 메서드 구현 교체)은 상속 없이 시스템 메서드를 재정의할 수 있게 합니다. Aspects 및 JRSwizzle과 같은 라이브러리는 AOP(관점 지향 프로그래밍) — 모니터링, 분석, 로깅에 이 기능을 사용합니다.

Forward invocation은 또 다른 런타임 기능입니다: 객체가 선택자에 응답하지 않으면 시스템이 forwardInvocation:을 호출하여 메시지를 다른 객체로 전달할 수 있습니다. 이것은 Objective-C(NSProxy)의 프록시 패턴의 기초이며, 지연 초기화 및 분산 객체에 사용됩니다.

Apple은 새 코드에서 런타임 트릭 사용을 최소화하고 Swift의 정적 타이핑을 선호할 것을 권장합니다. 그러나 레거시 프로젝트에서는 objc_msgSend, method_exchangeImplementations 및 objc_getAssociatedObject에 대한 지식이 기존 코드베이스 유지에 필요합니다.

프로퍼티와 Key-Value Observing

Objective-C는 getter/setter가 있는 ivar에 대한 프로퍼티를 구문 설탕으로 지원합니다. atomic/nonatomic, strong/weak/copy, readonly/readwrite 및 assign/retain 수식어가 메모리 동작과 스레드 안전성을 결정합니다. KVO(Key-Value Observing)는 런타임에 내장된 프로퍼티 변경 관찰 메커니즘입니다: 모든 객체는 addObserver를 통해 다른 객체의 프로퍼티 변경을 구독할 수 있습니다.

자주 묻는 질문

Objective-C에서 메시지 구문이란?

메서드 호출은 [object selector:argument]처럼 보입니다. 메시지는 objc_msgSend를 통과하며 런타임에서 동적으로 구현을 찾습니다. 선택자는 메서드 이름(@selector(methodName))이며 함수 포인터가 아닙니다. 이를 통해 swizzling과 forward invocation이 가능합니다.

카테고리(Categories)와 확장(Extensions)의 차이점은?

카테고리(@interface ClassName (Name))는 상속 없이 모든 클래스(시스템 클래스 포함)에 메서드를 추가합니다. 확장(@interface ClassName ())은 .m 파일에 선언되며 프로퍼티와 ivars를 추가할 수 있습니다. 카테고리는 ivars를 추가할 수 없지만 확장은 가능합니다.

Objective-C에서 블록(Blocks)이란?

블록은 ^(매개변수) { 본문 } 구문의 익명 함수로, 컨텍스트에서 변수를 캡처합니다. C++의 람다와 Swift의 클로저와 유사합니다. self 캡처 시 retain cycle을 방지하기 위해 __weak가 필요합니다. UIKit, GCD, Foundation에서 사용됩니다.

ARC와 MRC의 차이점은?

MRC는 수동 참조 카운팅: 프로그래머가 retain, release, autorelease를 호출합니다. ARC는 자동: 컴파일러가 정적 분석에 기반하여 retain/release를 삽입합니다. ARC는 GC가 아닙니다 — 카운터가 0이 되면 객체가 즉시 해제됩니다. ARC는 __weak와 __strong 수식어를 지원합니다.

같은 프로젝트에서 Objective-C와 Swift를 사용할 수 있나요?

네, Bridging Header를 통해 가능합니다. Swift는 이 헤더를 통해 Objective-C를 보고, Objective-C는 <ProjectName>-Swift.h를 통해 Swift를 봅니다. Swift 클래스는 NSObject를 상속하고 @objc로 표시되어야 합니다. Swift의 struct와 enum은 Objective-C에서 직접 볼 수 없습니다.

요약

  • Objective-C — Cocoa/Cocoa Touch를 위한 동적 런타임과 메시지 구문을 갖춘 C의 엄격한 슈퍼셋
  • Message passing — objc_msgSend를 통한 동적 디스패치와 swizzling 기능이 있는 [object method] 호출
  • 카테고리 — Apple 시스템 클래스를 포함한 상속 없는 기존 클래스에 메서드 추가
  • 블록 — retain cycle 방지를 위해 __weak가 필요한 클로저가 있는 익명 함수
  • ARC vs MRC — 수동 retain/release에서 컴파일러를 통한 자동 참조 카운팅으로의 발전
  • Bridging Header — 점진적 마이그레이션을 위한 한 프로젝트에서 Swift와 Objective-C의 협업 메커니즘
  • 레거시 중요성 — 약 35%의 App Store 앱이 유지보수가 필요한 Objective-C 코드 포함

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기