본문 바로가기
프로그래밍 놀이터/iOS

[Effective Objective-C] #21 오브젝티브-C 에러 모델을 이해하라

by 돼지왕 왕돼지 2017. 9. 1.
반응형

 [Effective Objective-C] #21 오브젝티브-C 에러 모델을 이해하라


출처 : Effective Objective-C


0, arc, automatic reference counting, dictionary, enum, error code, error domain, exception-safe, fobj-arc-exceptions, integer, nil, nil 포인터 검사, nserror, nserror 포인터, nserror**, string, user info, [Effective Objective-C] #21 오브젝티브-C 에러 모델을 이해하라, 델리게이트 메서드, 델리게이트 프로토콜, 도메인, 라이브러리, 리소스 릴리즈, 릴리스, 릴리즈, 메모리 누수, 보통 에러, 불립값, 성공 실패 불린값, 세그먼테이션 폴트, 심각한 에러, 에러 도메인, 에러 모델, 에러 코드 열거형, 열거형, 예외, 예외 발생, 예외 안전, 예외 저장, 외부 파라미터, 재정의, 전역 상수, 지역화된 설명, 추가 정보, 추가적인 코드, 추상, 캡슐화, 컴파일러 플래그, 크래시, 클래스 추상, 포인터의 포인터, 호출자



-

기본적으로 ARC ( Automatic Reference Counting) 가 예외(Exception)에 안전하지 않다.

사실 이는 범위 끝에서 릴리스되어야 하는 객체가 예외가 발생하면 릴리스되지 않는다는 것을 의미한다.

컴파일러 플래그( flag ) 를 켜면 예외 안전( exception-safe ) 모드를 생성할 수 있지만,

그렇게 하면 예외가 발생하지 않는 상황에서도 동작하는 추가적인 코드가 만들어진다.

이 컴파일러 플래그는 -fobj-arc-exceptions 다.



-

ARC 를 사용하지 않더라도 예외가 발생했을 때 메모리 누수를 막는 안전한 코드를 작성하는 것은 어렵다.

리소스를 릴리스하기 전에 예외가 발생한다면 절대 릴리스되지 않는다.



-

최근에 오브젝티브-C 는 예외가 발생했을 때 복구를 하지 않고 앱을 반드시 종료해야 하는 흔치 않은 상황일 경우,

예외를 저장하는 방법을 채택했다.

이는 복잡한 예외 안전 코드가 필요 없다는 것을 의미한다.



-

심각한 에러가 발생했을 때만 예외를 사용해야 한다는 점을 기억하라.

예를 들어 클래스에서 예외를 발생시켜야 하는 경우는 반드시 하위 클래스를 만들어 사용해야만 하는 추상 기본 클래스를 직접 생성하려고 할 때다.

오브젝티브-C 는 다른 언어와는 달리 클래스가 추상이라고 알려주는 언어 생성자가 없다.

그래서 비슷한 효과를 내는 가장 좋은 방법은, 하위 클래스에서 재정의해야 하는 모든 메서드에서 예외를 던지게 하는 것이다.

추상 기본 클래스의 인스턴스를 생성하고 사용하려 하면 예외가 발생할 것이다.



-

심각한 에러일 때만 예외를 사용할 수 있다면 다른 보통 에러들은 어떻게 처리해야 하나?

오브젝티브-C 가 일반적인 에러를 처리할 때 쓰는 방법은,

메서드에서 에러가 발생하면 nil 또는 0 을 반환하게 하거나 NSError 를 사용하는 것이다.



-

NSError 는 좀 더 많은 유연성을 제공한다.

에러가 발생한 이유를 되돌려 줄 수 있기 때문이다.

NSError 객체는 이러한 정보의 일부를 캡슐화한다.


Error domain ( String )

     에러가 발생한 도메인이다.

     보통 에러가 발생한 곳을 유일하게 정의하는 데 사용할 수 있는 전역 변수다.


Erorr code ( Integer )

     에러가 발생한 특정 에러 도메인 내에서 유일하게 정의되는 코드다.

가끔 특정 에러 도메인 내에서 발생할 수 있는 모든 에러의 집합을 정의하기 위해 열거형(enum)을 사용한다.


User info ( Dictionary )

     에러에 대한 추가 정보다.

     지역화된 설명과 이 에러가 발생한 원인이 되는 에러 같은 정보가 있다.




-

API 설계에서 에러가 사용되는 일반적인 방법 중 첫 번째는 델리게이트 프로토콜을 이용하는 것이다.

에러가 발생하면 객체는 프로토콜의 메서드 중 하나를 통해 델리게이트에 에러를 전달한다.



-

예외를 처리하지 않고 호출자한테 던지는 것이 더 선호된다.

에러 처리 여부를 사용자 판단에 맡길 수 있기 때문이다.



-

에러를 처리하는 또 다른 방법인 NSError 는 다음과 같이 메서드를 호출할 때 외부 파라미터로 전달하는 방법으로 사용된다.

- (BOOL)doSomething:(NSError**)error



-

메서드로 전달되는 error 변수는 NSError 의 포인터를 가리키는 포인터의 포인터다.

이는 메서드가 반환값과 더불어 효과적으로 NSError 객체도 반환받을 수 있게 한다.



-

가끔 이와 같은 에러를 반환하는 메서드는 성공 또는 실패를 가리키는 불린값도 반환한다.

그래서 반한된 에러가 정확히 무슨 에러인지 관심이 없다면 그냥 불린값만 검사하면 된다.

또는 무슨 에러인지 알아야 한다면 반환되는 에러를 검사하면 된다.



-

- (BOOL)doSomething:(NSError**)error{

     if ( /* error */ ){

          if (error){

               *error = [NSError errorWithDomain:domain code:code userInfo:userInfo];

          }

          return NO;

     } else{

          return YES;

     }

}



-

error 파라미터는 제일 먼저 nil 이 아닌지 검사해야 한다.

null 포인터를 역참조하면 세그먼테이션 폴트나 크래시의 원인이 되기 때문이다.

호출자가 nil 을 전달하는 것도 정상이기 때문에 어떤 에러인지 관심 없는 경우에도 반드시 체크해야 한다.



-

도메인은 전역 상수 NSString 으로 정의되는 것이 가장 좋다.

그리고 에러 코드는 열거형으로 정의되는 것이 제일 좋다.


// header

extern NSString *const EOCErrorDomain;


typedef NS_ENUM(NSUinteger, EOCError){

     EOCErrorUnknown = -1,

     EOCErrorInternalInconsistency = 100,

     EOCErrorGeneralFault = 105,

     EOCErrorBadInput = 500,

};


// implementation

NSString *const EOCErrorDomain = @“EOCErrorDomain”;



-

사용자 라이브러리를 위한 에러 도메인을 생성하는 것은 신중해야 한다.

NSError 객체를 생성하고 반환할 수 있게 하기 때문이다.



-

에러 코드를 위한 열거형을 생성하는 것은 좋은 생각이다.

에러를 문서화하고 코드를 의미 있는 이름으로 제공하기 때문이다.

또 에러가 정의되어 있는 헤더 파일에 각 에러에 대한 자세한 주석을 남길 수도 있다.




기억할 점


전체 앱을 종료시키는 심각한 에러에 대해서만 예외를 사용하라.


심각하지 않은 에러는 에러를 처리하는 델리게이트 메서드를 제공하거나

NSError 객체를 외부 파라미터로 제공하라.




반응형

댓글