현재 많은 곳에서 iOS를 개발할 때, Swift 언어를 사용해서 개발을 한다.
이 Swift 언어는 애플의 WWDC14에서 공개된 언어이다.
하지만 iOS의 역사는 Swift 공개 이전에 Objective-C라는 언어와 함께해왔다.
Objective-C는 80년대부터 사용된 역사가 깊은 언어로 C 언어에서 파생된 객체지향적 언어이다.
스티브 잡스가 이 언어를 애플의 표준 프로그래밍 언어로 사용하기 시작하면서 현재까지도 iOS 개발의 많은 곳에 영향을 끼치고 있다.
실제 현업에서의 코드 중에서 오래된 레거시 코드가 Objective-C로 작성된 것들이 있기도 하고, 다양한 근본 있는(?) 라이브러리를 연결하다 보면 자연스레 이 언어와 마주하는 일이 발생한다.
Swift는 Obejctive-C와 긴밀하게 연결되어 서로 간에 브릿지를 잘 정리하고 구현하면 함께 사용할 수 있다.
iOS 개발에 이제야 입문하게 된다면 Swift 언어에 가장 집중해서 공부를 해야겠지만, iOS 개발에 대한 더욱 심도 있는 이해도를 가지기 위해서는 Objective-C 공부도 병행하면 좋다.
개인적으로 좋은 기회가 있어서 기본적인 문법을 배우고 현업에서 개발도 참여할 수 있었는데, 당시 정리했던 내용들과 추가적으로 복습하면서 알게되는 내용들을 가볍게 정리할 예정이다.
(최신 레퍼런스가 많지 않고, 참고 서적 몇 권을 뒤적여가며 배운 내용이기 때문에 일부 틀리거나 애매한 설명이 있을 수 있으니 잘못된 내용이 있으면 지적 바랍니다.)
언어적 특성
앞서 언급한대로 Objective-C는 C 언어에서 파생된 객체지향적 언어이다.
클래스를 구현하고 이에 따른 인스턴스를 생성하며, 각 인스턴스는 다양한 프로퍼티와 메서드 등을 지니고 있다.
객체지향적 설계를 고려해서 클래스의 캡슐화와 정보의 은닉을 잘 지키면서 구현하는 것이 중요하다.
Objective-C는 모든 클래스가 NSObject라는 최상위클래스를 상속하여 구현하도록 되어 있다.
Swift는 아무 것도 상속하지 않고 클래스를 만들 수 있는 것과는 다른 부분이다.
(물론 Swift에서도 NSObject를 상속할 수 있다.)
새로운 클래스를 생성하면 Xcode의 프로젝트 내에 헤더 파일(.h)과 구현부 파일(.m)이 동시에 생성된다.
C 언어나 C++ 언어에서 헤더 파일(.h)과 구현부 파일(.c 또는 .cpp)가 생성되는 것과 유사하다.
헤더 파일에는 외부에 공개하여 클래스의 프로퍼티나 메서드에 접근할 수 있도록 인터페이스를 제공한다.
구현부 파일에는 헤더에 작성된 내용을 구현하거나 헤더에 공개되지 않는 내용을 구현한다.
당연한 얘기지만 외부에 공개될 필요가 없는 정보는 최대한 헤더에 공개하지 않으면서 클래스가 독립적으로 작동하도록 작성하는 것이 중요하다.
익명 카테고리를 사용해서 구현부 내에 인터페이스를 작성하는 방식도 있으나 다음에 카테고리를 정리하면서 자세히 다루도록 하겠다.
메시지
Objecitve-C에서는 메시지 송수신이라는 개념이 존재한다.
쉽게 생각하면 다른 언어들에서 메서드를 호출하는 개념이 메시지 송수신과 유사하다고 보면 된다.
메시지의 송수신 과정에는 아래의 세 가지 요소가 필요하다.
- 메시지 (Message)
- 주체 (Sender)
- 대상 (Receiver)
메시지의 송수신 과정에서 리시버가 유효하지 않으면 메시지는 전달되지 못하고 단순히 생략된다.
하지만 메시지를 보내는 주체가 유효하지 않으면 런타임에 오류가 발생한다.
(컴파일 시간이 아닌 런타임에 오류가 발생하는 것은 Objective-C가 동적 언어이기 때문인데, 컴파일 시간에는 메시지 송수신에 대한 유효성을 아직 알지 못하는 것으로 알고 있다.)
메시지를 전송하게 되면 우선 메시지 인자를 스택에 쌓고, 대상에 대한 포인터와 메시지 셀렉터 상수를 스택에 쌓는다.
그리고 메시지 전송 함수가 호출되며, 대상이 유효하지 않으면 즉시 반환된다.
메시지 전송 함수를 통해 isa 인스턴스 변수의 값을 가져온다.
(isa 변수는 대상의 클래스 객체 자체를 가르키며, Objective-C에서는 클래스 또한 일종의 객체로 다뤄진다.)
셀렉터를 통해 대상 클래스의 메서드를 찾아내서 실제로 실행된 후, 최초에 메시지를 전송했던 코드 이후의 코드가 실행된다.
메시지 전송의 예시이다.
info = [printInfo setLeftMargin: 60.0]
cell = [albumview cellAtRow:i column:j];
id 타입
어떤 클래스로부터 생성되었든 상관 없이 객체를 담을 수 있는 하나의 타입이다.
구조 및 구현의 유연성을 높이기 위해 사용될 수 있지만, 가능하면 특정 클래스로 지정하는 것이 안전하다.
또한, 생성자를 만들 때 반환 타입으로 id를 사용하는 경우가 있는데, id 대신 instancetype을 사용하는 것이 애플에서 공식적으로 권장하는 방식이다.
둘의 차이는 쉽게 생각하면 id는 아무 타입이나 담을 수 있는 것을 의미하고, instancetype은 생성된 객체의 타입으로 해당 타입이 고정된다고 보면 된다.
객체의 생성
Objective-C에서 객체를 생성하는 문법 또한 다른 언어와 달라서 생소할 수 있다.
객체 생성을 하는 생성자 또한 일종의 메시지 전송으로 볼 수 있으며 아래와 같은 형태로 생성한다.
기본적으로 alloc을 통해 메모리를 할당하고 init 또는 별도 구현된 생성자를 통해 생성을 한다.
만약 생성자로 init을 사용한다면 new라는 메서드를 사용해서 간편하게 생성할 수 있다.
별도로 구현한 생성자를 사용할 때는 사용할 수 없다.
[[NSObject alloc] init];
[NSObject new];
[[NSObject alloc] initWithSometing: someting];
// initWithA 는 Objective-C 작성의 대표적인 컨벤션 중 하나이다.
나중에 별도로 다루겠지만, Objective-C는 과거와 달리 ARC를 이용해서 자동으로 메모리를 관리한다.
이러한 변화로 인해 현재는 별도의 설정 없이는 dealloc을 명시적으로 호출하는 것은 막혀있다.
OS 차원에서 컴파일 시점에 언제 메모리 해제를 할지 결정하는데 개발자가 별도로 호출하면 당연히 사이드 이펙트가 발생할 수 있을 것이다.
물론, dealloc을 직접 오버라이드해서 메모리에서 해제되는 시점에 일어날 이벤트를 작성하는 것은 가능하다.
개인적인 공부를 위해 작성한 내용이므로 틀린 내용이나 수정이 필요한 부분이 있을 수 있으니 감안하고 봐주시면 감사하겠습니다.
'Objective-C' 카테고리의 다른 글
[Objective-C] 클래스의 작성 (0) | 2023.12.20 |
---|