IBOutlet

IBAction / IBOutlet / IBOutletCollection

프로그래밍 언어에서는 종종 필수 지시어가 사람에게는 별 의미 없는 것이 되곤 한다. 오브젝티브 C에서는 #pragma 디렉티브, 메소드 타입 인코딩같은 것들이나 기본적인 저장 클래스는 기본적으로 의미가 없는 것으로 보이며, 실제로 컴파일러가 똑똑해지면서 그 의미가 퇴색된다. 그럼에도 불구하고 이러한 것들은 코드 그 자체가 다른 개발자에게 힌트를 줄 수 있는 수단이 되기도 한다.

이제 막 코코아나 코코아터치를 시작한 개발자에게 IBAction, IBOultet, IBOutletCollection과 같은 매크로는 이러한 현상의 일부이다.

다른 두글자 접두어들과는 달리 IB-라는 접두어는 시스템 프레임워크와는 아무런 상관이 없고 그저 인터페이스 빌더와 관련이 있는 말이다. 인터페이스 빌더는 오브젝티브 C의 초창기에 그 근원을 두고 있는데, 88년에 “프로젝트 빌더”는 “NeXTSTEP 개발 도구”로 발전하였고, Xcode에 이르기까지 그 구조가 크게 바뀌지 않았다. 아마 요즘의 iOS 개발자들도 NeXTSTEP 워크스테이션을 접해보면 컨트롤들을 뷰에 끌어다 놓는 이 환경이 익숙하게 느껴질 것이다.

인터페이스 빌더가 별도의 앱으로 분리된 시점으로 돌아가보면, 프로젝트 빌더의 .h, .m 파일들의 코드와 .nib 파일속의 객체 그래프를 서로 연관짓고 이 정보를 유지하는 것은 꽤나 어려운 일이었다. IBOutletIBAction은 키워드로서 사용되었고 인터페이스 빌더에서 시각적으로 표현되는 부분들을 위해 쓰였다.

IBActionIBOutlet은 그 자체로는 의미가 없다. 이들은 순수하게 다음과 같이 정의되는 매크로이다.

#define IBAction void
#define IBOutlet

물론 실제로는 다음과 같이 정의되어 있기는 한데..

#define IBOutlet __attribute__((iboutlet))
#define IBAction __attribute__((ibaction))

IBAction

실질적으로 이 키워드는 2004년부터는 필요가 없었다. 단지 -(void)메소드이름:(id)sender라는 포맷의 메소드는 인터페이스 빌더가 인식할 수 있었다.

그럼에도 불구하고 많은 개발자들은 아울렛과 연결될 필요가 있는 메소드의 리턴타입에 IBAction을 쓰는 습관을 계속 유지해왔다. 스토리보드나 XIB를 사용하지 않는 프로젝트에서도 타깃/액션 메소드를 정의할 때 이 키워드를 썼다.

IBAction 메소드의 정의 방법

컴파일러가 강제하는 몇몇 습관 때문에 오브젝티브C에서의 이름짓기는 매우 중요하며, 따라서 IBAction 메소드의 이름 짓기 역시 사소한 문제가 아니다. 반론의 여지는 있지만 선호되는 관습은 다음과 같다.

  • 반환형은 IBAction
  • 능동적인 의미의 동사를 메소드 이름으로 쓴다.: didTapButton:, didPerformAction: 과 같은 식으로
  • id타입의 sender 파라미터를 필수로 받는다.
  • 선택적으로 이벤트를 받을 수 있는데, withEvent:(UIEvent)event가 뒤에 추가될 수 있다.

예를 들어:

//  YES
- (IBAction)refresh:(id)sender;
- (IBAction)toggleVisibility:(id)sender withEvent:(UIEvent *)event;

// NO
- (IBAction)performSomeAction; // sender가 없다.

와 같은 식으로 이름지을 수 있다.

IBOutlet

IBAction과 달리 IBOutlet은 XIB나 스토리보드의 객체 속성과 코드를 후킹하기 위해 여전히 필수적인 요소이다. IBOutlet 연결은 뷰나 컨트롤과 이들을 관징하는 뷰 컨트롤러 사이에 만들어진다. 그러나 IBOutlet은 최상위 속성을 노출시키기 위해서도 사용될 수 있다. 이를테면 다른 컨트롤러에 대한 참조나, 혹은 컨트롤러 외부에서 참조할 public 속성을 위해서도 지정할 수 있다.

@property나 ivar를 사용할 때는 언제인가.

오브젝티브C에서는 인스턴스 변수보다는 프로퍼티를 사용하는 것이 선호된다. (그리고 안전하다.) 이는 아울렛에서도 동일하게 적용된다.

//  YES
@interface GallantViewController: UIViewController
@property (nonatomic, weak) IBOutlet UISwitch *switch;
@end

//  NO
@interface GoofusViewController : UIViewController {
    IBOutlet UISwitch *_switch
}
@end

프로퍼티는 기본적으로 특정한 속성값을 객체 외부에 노출시키는 인터페이스를 의미하므로, 인스턴스 변수를 직접 사용하는 것보다 항상 선호되어야 한다.

weak와 strong을 사용할 때 구분

ARC를 사용할 때 가장 헷갈리는 문제 중 하나는 IBOutlet 프로퍼티를 선언할 때 그 속성을 weak로 줄것이냐 strong으로 줄 것이냐 하는 점이다. 실제로 두 가지를 섞어서 쓰든 어떤 것을 써도 대부분의 경우에는 아무런 문제가 없이 동작하기는 한다. 따라서 상당히 모호한 문제가 되는데, 이 때는 항상 weak를 쓰되, 특별한 경우에만 strong을 쓴다는 것이 답이될 수 있다. 즉 아울렛 자체를 참조하는 객체가 이를 ‘소유’하는 경우에만 strong으로 선언한다.

  • 이는 주로 File's Owner 최상위 객체에 해당한다.
  • nib 파일 내의 객체가 원래 컨테이너 밖에서 필요해지는 경우가 있을 수 있다. 예를 들어 초기 뷰 계층 구조에서는 임시적으로 삭제되었으나 독립적으로 유지되어야 한다면 weak이 아닌 strong을 사용해야 한다.