[iOS] Permission Alert View 에 custom 메시지 추가하기

iOS App이 사용자의 사진, 캘린더, Push Notification, 연락처 등 개인정보에 접근 시도할 때,  System은 AlertView를 띄어 사용자에게 접근 권한을 부여할 것인지 묻는다.

이 때 보여지는 AlertView 메시지는 context 정보가 없기 때문에 사용자의 허가를 득하기가 쉽지 않다.

그래서 요즘은 권한 부여 AlertView를 띄우기 전에 접근이 필요한 이유를  사용자에게 먼저 알려주는 방법을 사용함으로써 이러한 장벽을 낮추려는 시도가 많다.

오늘 이와 관련한 글  The Right Way to Ask Users for iOS Permissions 을 읽다가 한가지 놀라운 – 나만 몰랐으니 나만 놀란걸 수도 – 사실을 발견하였다. (링크된 블로그 글도 꼭 읽어보길 바란다.)

글에 첨부된 이미지들을 살펴보니 System에서 관장하는 접근 권한 AlertView에 App에 특화된 접근 목적이 기술되고 있는 것 아닌가.

실제 구현 방법을 조사해보니 너무 간단했다. 간단히 방법을 알아보자. (너무 간단해서 개인 블로그 대기 리스트에 넣지 않고 바로 쓴다.)

구현 

iOS 는 info.plist의 특정 키값을 통해 접근 권한 AlertView에 메시지를 추가 할 수 있게 한다. 즉 개발자가 할 일은 info.plist에 키 값을 넣고 메시지를 넣으면 끝이다. (다국어를 지원한다면 Localized 작업 필요)

iOS Developer Library 의 Information Property List Key Reference 를 보면 접근 권한 메시지에 해당하는 키값을 확인할 수 있다. Xcode의 editor를 이용한다면 “Privacy – ” 로 시작하는 키값을 찾으면 된다. Key 값에 따라 지원하는 iOS 버전도 다르니 Refernce를 확인하자. (애석하게도 Push Notification 에 대한 키값은 보이질 않는다.)

< From : iOS Developer Library >

간단한 테스트 앱을 만들어 확인하였다. Info.plist 에 키값을 추가 하기 전의 접근 권한 알림창 – 기본 접근 권한 알림창 – 은 아래와 같다.

< 메시지 적용 전 접근 알림 >

아래와 같이 info.plist에 키값을  추가하였다. (사진과 연락처)

< info.plist 화면 in Xcode >

앱을 실행시키면 기존 접근 권한 알림과 달리 info.plist에 설정한 메시지가 AlertView에 추가된 것을 확인할 수 있다.

< 사진 라이브러리 접근 알림 창 >

< 연락처 접근 권한 알림 창 >

 

App에서 접근하려고 하는 사용자의 데이터가 App 동작의 핵심 역할을 한다면, 접근이 필요한 이유를 명시함으로써 사용자의 행동을 좀 더 쉽게 유도할 수 있을 것이다.

본인은 위 내용을 현재 작업 중인, 아니 현재 뿐 아니라 앞으로 작업할 서비스에도 적용할 생각이다.

Advertisements

[Objective-C] 메시지 전송

Objective-C 메소드는 자바에서처럼 호출하는게 아니다. 동적으로 움직이는 중앙 전송 함수를 통해 객체에게 메시지를 전달한다. 대상 객체에게 메시지를 전송하는 대략적인 과정은 다음과 같다.

  1. 메시지 인자를 스택에 쌓는다.
  2. 수신자에 대한 포인터와 메시지 셀렉터 상수를 스택에 쌓는다.
  3. 메시지 전송 함수가 호출된다.
  4. 수신자 포인터가 nil 값이면 메시지 전송 함수는 즉시 반환된다.
  5. 메시지 전송 함수에서 수신자의 포인터를 사용해 isa 인스턴스 변수의 값을 가져온다. isa 변수는 수신자의 Class 객체를 가리키며, Class 객체는 해당 클래스에 대한 함수 테이블을 가지고 있다. (자바와 다르게 Objective-C 에서는 Class 도 하나의 객체다)
  6. 셀렉터를 사용해 수신자 클래스의 함수 테이블에서 대상 메소드를 찾아낸다.
  7. CPU 의 프로그램 카운터에 대상 함수의 포인터를 지정하고, 메소드의 코드가 실제로 실행된다.
  8. 메소드가 실행을 마치고 최초에 메시지를 전송했던 코드가 계속해서 실행된다.

Objective-C 에서는 일반적인 메시지 전달 – ex) [TestClass testMethod] – 이 아닌 코드에서 직접 메시지를 전송하는 방법을 제공한다. 일반적으로 다음 세가지가 있다. (이러한 방식은 동적인 메시지 처리를 목적으로 한다. )

  • NSObject 의 -performSelector: 메소드 사용
  • NSInvocation 객체 만들어 사용
  • C 언어 함수처럼 Objective-C 함수를 직접 호출 : 성능 이슈인 경우 주로 사용

-performSelector: 메소드

NSObject 클래스에는 다양한 형태의 -performSelector: 메소드가 구현돼 있다. -performSelector: … 메소드를 이용하면 실행 thread 선택 및 지연 메시지 전달이 가능하다. (NSObject Class Reference )

-performSelector: 각 메소드를 알아보기 전에 지연 메시지 전달 이해를 위해 실행 루프에 대해 알아보자.

실행 루프(Run Loop)는 독립된 스레드에서 동작하는 이벤트 큐다. 모든 iOS 애플리케이션은 실행 루프 스레드가 최소 1개 이상 동작하며 가장 먼저 동작하기 시작한 첫 번째 실행 루프를 메인 실행 루프 또는 메인 스레드라고 한다. 사용자의 모든 상호 작용은 메인 실행 루프를 통해 메인 스레드에서 실행되며, 개발자는 이렇게 되도록 개발하여야 한다.

프로그램 동작의 필요에 따라 별도의 스레드를 추가 – 새로운 실행 루프를 추가 – 할 수 있다. 실행 루프는 자신의 이벤트 큐에 쌓여 있는 이벤트를 불러와 처리하는 작업을 계속 반복한다. 큐에 쌓여있는 이벤트가 없다면 해당 스레드는 일시적으로 정지된다.

-performSelecotor :withObject: afterDelay: 메소드를 통한 지연 메시지 전달은 메소드 호출 이벤트를 현재 실행 중인 실행 루프의 이벤트 큐에 쌓음으로써 구현된다. 지연 시간 – afterDelay: – 은 메시지 전송 시점을 보장하는 것이 아니라 일정 시간 내에 메시지가 전송되지 않음을 보장한다.

메인 스레드가 아닌 다른 작업 스레드에서 -performSelectorOnMainThread:를 이용하면 메인 스레드의 이벤트 큐에 메소드 호출 이벤트를 쌓을 수 있다. 타이머와 연동되는 사용자 인터페이스 관련 이벤트 등을 처리할 때 유용하다.-performSelector:onThread: 메소드를 이용하면 특정 스레드의 이벤트 큐에 메소드 호출 이벤트를 쌓을 수 있다.

-performSelector:onThread: 메소드와 -performSelectorOnMainThread: 메소드의 경우, 작업을 실행할 스레드와 -performSelector: 메소드를 호출하는 현재 스레드가 동일하다면 메시지를 이벤트 큐에 쌓지 않고 수신자에게 즉시 전송한다.

-performSelector: 메소드의 여러 인자 중 wait 인자를 이용하면 동기/비동기 구현도 가능한다. wait 인자를 NO 지정하면 비동기 전송, YES 지정하면 동기 전송이 된다.

또한  mode 인자를 통해 실행 루프의 모드를 설정할 수 있다. 실행 루프는 각자 특정 모드로 실행되는데, 모드는 일부 이벤트를 필터링해 무시하고 처리하지 않는 용도로 사용된다.

-performSelectorInBackground: 메소드는 작업용 스레드를 새로 생성한 후 생성된 스레드에서 지정된 메시지를 처리하게 한다. 이 메소드는 지정된 메시지 하나를 처리하는 목적으로 스레드를 생성하므로 이벤트 큐와 실행 루프를 사용하지 않는다. 메시지를 처리하고 나면 새로 생긴 스레드는 제거된다.

-performSelector : … 메소드의 단점은 전달 가능한 인자 개수에 제한이 있다는 점과 전달 인자 타입이 객체 포인터(id)로 지정되어 있어 정수형 포인터 같은 경우 (id)&tempInteger 와 같이 타입 캐스팅이 필요하다는 점이다. (인자가 없으면 nil 을 넘겨주면 된다.)

NSInvocation

-performSelector:… 메소드와 달리 NSInvocation 클래스는 인자의 개수 및 종류 – 정수형, 구조체도 가능 – 상관없이 모든 메소드를 지원한다.

NSInvocation 인스턴스를 생성하려면 먼저 대상 메소드에 대한 NSMethodSignature 인스턴스를 알아야 한다.

NSObject 클래스는 -methodSignatureForSelector: 메소드를 구현하므로 어느 객체건 해당 메소드를 통해 NSMethodSignature 인스턴스를 확인할 수 있다. 객체의 인스턴스를 당장 알지 못한다면 NSObject 의 +instanceMethodSignatureForSelector: 메소드를 통해 확인 가능하다.

위와 같은 방법으로 NSMethodSignature 인스턴스를 확보하면, NSInvocation 클래스의 +invocationWithMethodSignature: 메소드를 통해 NSInvocation 인스턴스를 생성할 수 있다.

NSInvocation을 이용하면 호출 대상과 인자, 반환 값은 모두 개별적으로 지정해야 한다. (NSInvocation Class Reference)

NSInvocation 을 이용한 메소드 호출 예시는 아래와 같다.

NSString *prose = @"Do nine men interpreter?";
NSMethodSignature *signature;
NSInvocation *invocation;
NSString *count;

signature = [prose methodSignatureForSelector:@selector(substringWithRange:)];
invocation = [NSInvocation invocationWithMethodSignature:signature];

[invocation setSelector:@selector(substringWithRange:)];

NSRange range = NSMakeRange(3,4);
[invocation setArgument:&range atIndex:2];

[invocation invokeWithTarget:prose];
[invocation getReturnValue:&count];

인자값과 반환값을 지정할 때는 인자 값 자체가 아닌 인자 값의 포인터로 지정한다. 즉 인자 값이 객체 포인터 타입인 경우 포인터에 대한 포인터를 지정해야 한다. 위 예시에서 반환 값으로 &count 를 지정하였다.

setArgument: atIndex: 메소드에서 index 값은 2부터 사용한다. 0과 1번은 각각 self 변수와 _cmd(셀렉터) 로 예약되어있기 떄문이다. self 와 _cmd  지정은 NSInvocation 의 setTarget: 과 setSelector: 를 이용한다. 위 예처럼 -invokeWithTarget을 사용하여 대상을 같이 지정할 수도 있다.

NSMethodSignature 에서 메소드 selector 값을 사용하는데 NSInvocation 에서 setSelector: 로 메소드를 다시 지정하는 것이 이상하게 보인다. 그러나 NSMethodSignature 는 특정 메시지와 연결돼 있지 않고 단순히 메소드 구성 정보만을 갖고 있어서 NSInvocation 객체를 생성할 때 메소드의 셀렉터를 지정해야 한다.

따라서 NSInvocation 타겟값 설정을 통해 다른 객체의 완전하게 동일한 메소드를 호출할 수도 있다. (콜백 함수 호출에서 사용할 수 있음)

메소드 직접 호출

직접 작성한 모든 메소드는 결국 C 함수로 컴파일되고, 각 함수를 가리키는 포인터는 클래스의 Class 객체에서 호출 테이블 형태로 관리된다. 컴파일러는 이렇게 컴파일된 개별 메소드의 이름을 외부로 공개하지 않기 때문에 컴파일된 메소드의 이름을 알 수 없다.

하지만 Objective-C에서 각 함수의 포인터를 알아낼 수 있으며, 일반적인 메시지 전달 과정을 무시하고 해당 함수를 직접 호출할 수 있다.

이러한 직접 호출 기법은 굉장히 기술적인 부분이 많지만 다른 언어에서 Objective-C 메소드를 호출할 필요가 있거나, 메시지 전송 절차가 성능에 미치는 영향을 최소화할 필요가 있을 때 유용하다.

NSObject 의 -methodForSelector: 를 이용하면 메시지를 전달받아 실행될 때 소스의 실제 코드가 보관된 메모리 주소를 알아낼 수 있다. 또한 +instanceMethodForSelector: 메소드는 특정 인스턴스에 메시지가 전달됐을 때 실행될 함수의 주소를 알려준다. (NSObject Class Reference )

개별 객체는 객체 외부에서 변경할 수 있으며, 따라서 동일한 클래스의 다른 객체에서 실행되는 코드와 다를 수 있다. 심지어 Class 객체가 특정 메시지로 실행되리라고 판단하고 있는 내용도 바뀔 수 있다. (메소드 직접 호출에 대한 추가적인 상세 내용은 차후에 정리 예정)

*Reference : Learn Objective-C for Java Developers

%d bloggers like this: