-
NSObject에 대하여Swift 2024. 12. 19. 08:44
안녕하세요. 그린입니다 🍏
이번 포스팅의 주제는 NSObject에 대해 한번 정리해보려 합니다 🙋🏻
사실, NSObject는 Swift 카테고리보다는 Objective-C에 더 가깝긴하죠.
또, iOS 개발의 근간이라고 볼 수 있습니다.
그럼 한번 알아볼까요?
NSObject?
NSObject는 Objective-C 런타임 시스템의 근간을 이루는 최상위 클래스죠.
Foundation 프레임워크의 루트 클래스로, 거의 모든 Cocoa와 Cocoa Touch 클래스의 기본 클래스 역할을 해줍니다.
결국 Objective-C의 객체 지향 프로그래밍을 가능하게 해주는 핵심 요소라고 볼 수 있어요.
이 NSObject를 상속받는 하위 클래스들은 런타임 시스템에 대한 기본 인터페이스와 Objective-C 객체처럼 동작할 수 있는 기능을 상속 받습니다 😃
NSObject의 핵심 기능
기본적인 객체 동작 제공
NSObject는 모든 Objective-C 객체가 가져야 할 기본적인 인터페이스를 정의해줍니다.
@interface NSObject <NSObject> { Class isa; } + (Class)class; - (Class)class; - (BOOL)isKindOfClass:(Class)aClass; - (BOOL)isMemberOfClass:(Class)aClass; - (BOOL)conformsToProtocol:(Protocol *)aProtocol; - (BOOL)respondsToSelector:(SEL)aSelector;
런타임 기능 지원
NSObject는 동적 메시지 디스패치, KVO 등 Objective-C 런타임의 핵심 기능을 제공해줍니다.
// 동적 메시지 전송 - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; // KVO 지원 - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
NSObject 상속의 장점
자동 메모리 관리
NSObject를 상속받는 클래스는 자동으로 레퍼런스 카운팅 매커니즘을 사용할 수 있게 되어 메모리를 자동으로 시스템에서 관리할 수 있게 됩니다.
@interface MyCustomClass : NSObject @property (strong, nonatomic) NSString *name; @end @implementation MyCustomClass - (void)dealloc { // ARC에서는 [super dealloc] 호출이 자동으로 처리됨 } @end
객체 생명주기 관리
NSObject는 객체의 생성, 초기화, 해제에 관해 기본 메서드를 제공해줘요.
+ (instancetype)alloc; - (instancetype)init; - (void)dealloc;
그럼 조금 더 들어가서 메모리와 연관지어 한번 살펴볼까요?
메모리 관리와 NSObject
레퍼런스 카운팅
NSObject는 retain/release 매커니즘을 통해 메모리 관리를 수행합니다.
@implementation MemoryExample : NSObject - (void)memoryManagementExample { NSObject *obj = [[NSObject alloc] init]; // 레퍼런스 카운트 1 [obj retain]; // 레퍼런스 카운트 2 [obj release]; // 레퍼런스 카운트 1 [obj release]; // 레퍼런스 카운트 0, 객체 해제 } @end
ARC와의 통합
ARC를 통해 자동으로 메모리를 관리해줄 수 있어요.
@property (strong, nonatomic) NSString *strongReference; @property (weak, nonatomic) NSString *weakReference;
Runtime과의 상호작용
동적 메시지 처리
NSObject는 런타임 시스템과의 상호작용을 위한 다양한 메서드를 제공합니다.
implementation RuntimeExample : NSObject - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([alternateDelegateObject respondsToSelector:[anInvocation selector]]) { [anInvocation invokeWithTarget:alternateDelegateObject]; } else { [super forwardInvocation:anInvocation]; } } + (BOOL)resolveInstanceMethod:(SEL)sel { // 동적 메서드 해결 return [super resolveInstanceMethod:sel]; } @end
Introspection
객체의 런타임 정보를 조회할 수 있는 기능을 제공합니다.
- (void)introspectionExample { NSObject *obj = [[NSObject alloc] init]; // 클래스 정보 조회 Class cls = [obj class]; // 메서드 존재 여부 확인 BOOL respondsToSelector = [obj respondsToSelector:@selector(description)]; // 프로토콜 준수 여부 확인 BOOL conformsToProtocol = [obj conformsToProtocol:@protocol(NSCopying)]; }
그럼 이제 NSObject에 대해 기본적으로 알아봤으니 Swift에선 어떤 경우에 명시적으로 상속 받아 사용하는지 알아볼까요?
Swift에서 NSObject 클래스를 명시적으로 상속하는 경우
기본적으로 UIKit의 대부분 핵심 클래스들은 이미 NSObject를 상속받고 있어요.
이런 흐름이죠.
NSObject -> UIResponder -> UIView, UIVC, UIApplication
그렇기에 자연스럽게 자동으로 이 컴포넌트들은 모두 NSObject의 KVO, Runtime 같은 기능들을 사용할 수 있죠.
이게 UIKit이 Objective-C 런타임 특성들을 활용할 수 있는 이유기도 합니다.
여기서 하나 차이는 SwiftUI는 View 프로토콜이라 NSObject를 상속받지 않아요.
자 그럼, 우리가 명시적으로 NSObject를 상속해야 하는 상황들에 대해 보겠습니다.
KVO가 필요한 경우
class PersonViewModel: NSObject { @objc dynamic var name: String = "" { didSet { print("Name changed to: \(name)") } } } // 사용 예시 let viewModel = PersonViewModel() viewModel.observe(\.name, options: [.new]) { object, change in print("Name updated to: \(change.newValue ?? "")") }
이렇게 UIKit 컴포넌트가 아닌 클래스에서 KVO를 위해 @objc를 사용하는 등 KVO 방식이 필요할때는 상속해야 합니다.
Objective-C 런타임 기능이 필요한 경우
class RuntimeExample: NSObject { @objc dynamic func methodToSwizzle() { print("Original method") } }
메서드 스위즐링과 같은 옵젝씨의 기능이 필요한 경우에도 상속합니다.
Selector를 사용하는 경우
class TimerHandler: NSObject { @objc func handleTimer() { print("Timer fired") } func startTimer() { Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(handleTimer), userInfo: nil, repeats: true) } }
UIKit을 이용하면 익숙한 코드 구조죠?
#selector 이용을 위한 클래스를 만들때 상속해야 합니다.
UIKit의 Delegate 패턴을 사용하는 경우
class CustomDelegate: NSObject, UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // 구현 } }
여기서 커스텀 딜리게이트 구현을 위해 UITableViewDelegate를 상속 받습니다.
근데 왜 NSObject도 상속받느냐..!?
UITableViewDelegate가 NSObjectProtocol을 상속받기 때문입니다.
그렇기에 Objective-C 런타임이 딜리게이트 메서드를 동적으로 호출할 수 있는것이죠.
이 경우에도 UIKit이 Objective-C 기반으로 만들어졌기에 제약이 존재하게 됩니다.
이처럼 다양하게 명시적으로 NSObject를 상속해 기능을 활용해야 하는 경우가 존재합니다.
물론, 상속이 꼭 필요한 경우가 아니면 순수하게 Swift 클래스를 사용하는게 더 적절해요.
왜냐면, NSObject를 상속받으면 메모리와 성능 오버헤드가 약간은 발생할 수 있습니다.
어떠셨나요?
NSObject에 대해 우리는 최상위 근간을 이루는 클래스 정도라고만 알고 있었을텐데 조금 더 어떻게 이뤄지고 역할을 하는지 조금 더 자세히 알게 되었나요?
하나 더 비밀을 말씀드리자면 ☝️
이번 포스팅은 AI를 적극적으로 한번 활용해봤습니다!
매번 AI로 기술 블로그 포스팅에 대한 구조를 잡아보면 어떨까? 라는 생각이 많았는데요.
주제와 레퍼런스와 핵심 방향 및 다룰 내용에 대해 던져주고 목차와 어느정도의 정돈된 내용을 뽑아 봤어요!
물론, 검수하고 첨삭하는 등의 노고는 거쳐야 하지만 조금 더 자신의 생각을 더 매끄럽게 전달할 수 있는 부분에서 좋은것 같습니다 ☺️
마무리
NSObject는 Objective-C와 iOS 개발에서 필수불가결한 요소라고 학습이 되네요!
기본적인 객체 동작부터 고급 런타임 기능까지, 현대 iOS 애플리케이션 개발에 필요한 거의 모든 기반을 제공합니다.
또한 Swift와의 상호운용성 측면에서도 NSObject는 여전히 중요한 역할을 수행하고 있으며, 특히 Objective-C 런타임을 활용해야 하는 경우에는 필수적입니다.
NSObject의 이해는 단순히 하나의 클래스를 아는 것을 넘어서, iOS 플랫폼의 기반 기술을 이해하는 것과 직결됩니다.
이는 더 나은 앱을 개발하고, 발생할 수 있는 문제들을 효과적으로 해결하는 데 큰 도움이 될거라 생각해요 😃
레퍼런스
'Swift' 카테고리의 다른 글
Explore the Swift on Server ecosystem (feat. WWDC 2024) (34) 2024.12.02 ETag 캐싱으로 앱 성능 최적화하기 (32) 2024.11.26 Consume noncopyable types in Swift (feat. WWDC 2024) (26) 2024.11.18 Go further with Swift Testing (feat. WWDC 2024) (4) 2024.09.30 Meet Swift Testing (feat. WWDC 2024) (6) 2024.09.26