매버릭스 업데이트 후 개발 중인 맥 앱에서 발생하는 문제

OSX 매버릭스로 업데이트한 후 XCode로 개발 중인 유틸리티 프로그램의 일부 기능이 작동하지 않는 현상이 발생한다. 원래 Alpha 버전으로 곧 배포할 예정이었는데, 이 문제로 배포가 늦어질 것 같다.

문제는 Objective-C와 Cocoa를 사용하여 전역 마우스 및 키보드 후킹(hook global mouse and keyboard event)을 사용하는 코드가 있는데, 그 중 키보드 이벤트만 캡춰링(capturing)할 수 없는 현상이 발생한다.

해결 방법

의외로 해결 방법은 간단하다. 먼저 OSX 매버릭스에서 변경된 보안 사항에 대해서 알 필요가 있다. 기술적인 내용은 아니므로 쉬엄쉬엄 다음의 글을 참고하기 바란다.

OS X 10.9 매버릭스(Mavericks)의 바뀐 메뉴 구조 때문에 정상 작동하지 않는 앱 실행하기 (보조 장비에 대한 접근 활성화)

즉, 기본적으로 매버릭스를 설치하면 ‘손쉬운 사용’에 의해 응용 프로그램이 맥이나 기타 장비를 제어하는 것이 차단된다. 필자는 매버릭스로 업데이트 이전에 ‘손쉬운 사용’을 활성화하여 장비에 접근할 수 있도록 설정되어 있었다. 하지만, 업데이트 후에 ‘손쉬운 사용’의 활성화 방법이 변경되면서 이 설정이 초기화 된 것으로 볼 수 있다.

해결 방법은 ‘시스템 환경 설정 -> 보안 및 개인정보’로 들어간 후, ‘손쉬운 사용’ 항목에서 Xcode를 체크해 주면 된다. 그럼 특정 응용 프로그램에서 사용하는 APIs 가 블럭 당하지 않는다.

[그림1] 손쉬운 사용의 Xcode 항목을 체크하면 APIs 가 블럭 당하지 않는다

Posted by 땡초 POWERUMC

댓글을 달아 주세요

반 객체지향(Half-of-OOP)

가장 먼저 명심하자. 오브젝티브-C는 C언어의 슈퍼셋(Super Set)이고 C++의 객체 지향과는 거리가 멀다. 오브젝티브-C 언어는 마치 객체지향 프로그래밍(OOP, Object-Oriented Programming) 처럼 보이지만 객체지향 언어가 아니다. 그렇다고 완전히 함수형 언어도 아니다.

하지만, 오브젝티브-C는 객체지향의 가장 대표적인 특징인 상속(Inheritence)이 가능하고 인터페이스 구현이 가능하다. ANSI-C 입장에서 바라보면 상속과 인터페이스 구현은 함수형 언어로서 가당치도 않은 언어적 특성임에 틀림 없을 것이다. (이 문장의 인터페이스는 오브젝티브-C의 @protocol을 의미함.)

물론, C 언어에서도 구조체(struct), 포인터(pointer) 등을 조합하여 객체처럼 다룰 수 있지만, 오브젝티브-C 처럼 상속과 인터페이스의 구현은 객체지향 언어가 아님에도 불구하고 완전히 객체지향 프로그래밍 처럼 개발하는데 모자람이 없을 지경이다.

정리해 보면 오브젝티브-C의 객체지향 특징은 다음과 같다.

1. 상속성(Inheritance)

상속성은 부모의 특징을 물려 받음과 동시에 재정의(override), 부모 호출(super 또는 base)를 지원한다. 더 자세히 말할 필요는 없을 것 같아서 걍~ 넘어간다.

// 오브젝티브-C 의 상속
@interface MFAppController : NSObject
{
   // .. 생략 ..
}

2. @protocol - 인터페이스 구현

오브젝티브-C의 프로토콜(protocol)은 C#과 Java의 interface와 같지만, 한 가지 다른 점은 인터페이스에 정의된 메서드 구현이 필수가 아니라는 점이다. 프로토콜의 모든 메서드를 굳이 구현하기 싫으면 하지 않아도 오브젝티브-C는 너그럽게 용서해 준다.

어떻게 이런 것이 가능할까? 지난 아티클 ‘[Objective-C] 아름다운을 추구하는 오브젝티브-C 언어 1/N’ 에서 다룬 것 처럼 메시지를 기반으로 메서드 호출을 위임하는 언어적인 특성 때문에 가능하다. 모든 프로토콜의 메서드를 정의하든 말든 컴파일러는 컴파일 할 뿐이고, 런타임에서 메시지를 통해 메서드를 호출해서 메서드가 구현되지 않았으면 예외를 발생하던가 아니면 씹던가 하게 된다. (단, @optional과 @required 참고)

// 오브젝티브-C 프로토콜 및 다중 상속
@protocol Animal

   -  (void)say;

@end

@protocol Duck <Animal, NSObject>

    - (void)swim;

@end

3. @category - 확장 메서드

카테고리(@category)는 C#에서의 확장 메서드(Extension Methods)와 똑같다. 확장 메서드는 2008년 C# 3.0에 언어적인 특징으로 포함되었다. 이와 더불어 람다 표현식(Lambda Expression), 이 두 가지는 C# 코드로 표현한 라인 수를 급격히 줄일 수 있었고, 더 섬세한 객체지향 프로그램에 집중할 수 있게 해준다.

@interface NSArray (ConvertableArray)
- (NSDictionary*)ConvertToDictionary;
@end

@implementation NSArray (ConvertableArray)
- (NSDictionary*)ConvertToDictionary
{
   // .. 실제 구현 생략 ..
   return nil;
}
@end

int main(int argc, const char * argv[])
{
   @autoreleasepool {

       NSArray* arr = [NSArray arrayWithObjects:@"A", @"B", nil];
       NSDictionary* dic = [arr ConvertToDictionary];

       // TODO
   }
   return 0;
}

같은 코드로 C# 으로 변환하면 다음 코드와 같다.

public static class ConvertableArray
{
    public static IDictionary ConvertToDictionary(this IList list)
    {
        // .. 구현 생략 ..
        return null;
    }
}

class MainClass
{
    public static void Main (string[] args)
    {
        var arr = new List<String> { "A", "B" };
        var dic = arr.ConvertToDictionary ();
    }
}

하지만, 내부적으로 처리되는 매커니즘은 완전히 틀리다. 무엇이 그렇게도 완전히 틀릴까?

  • C# 확장 메서드는 정적 클래스의 정적 메서드로만 표현할 수 있다.
  • 오브젝티브-C 카테고리는 인스턴스 메서드이다.

그러므로 오브젝티브-C의 카테고리(@category)는 C#의 확장 메서드와 달리 인스턴스화 된 카테고리 메서드가 호출될 때 마다 참조 카운터가 증가한다. NSArray 객체가 인스턴스화 되었기 때문에 카테고리 메서드를 호출할 수 있다는 이야기가 된다.

카테고리의 메서드는 디버거를 통해 다음과 같이 역어셈블리 된다. 역어셈블리 코드를 좀 더 보기 편하기 위해 로컬 변수를 선언하여 살펴 보았다.

먼저 48만큼 메모리 사이즈를 확보한 다음 넘어온 값과 로컬 변수를 초기화하거나 값을 대입한다. rbp-8( =rdi)self 가 들어간 것이고, rbp-16 (=rsi)는 메서드 정보가 들어간다. rbp-20 (=$10)은 INT32 정수 값이 할당된다. 마지막으로 +48로 원래 스택 포인터로 돌아와 빈 값을 리턴하게 된다. 스택이 8씩 자라는 것은 필자가 소스코드를 64비트 대상으로 컴파일 했기 때문이다.

0x100000d40:  pushq  %rbp
0x100000d41:  movq   %rsp, %rbp
0x100000d44:  subq   $48, %rsp
0x100000d48:  movq   %rdi, -8(%rbp)
0x100000d4c:  movq   %rsi, -16(%rbp)
0x100000d50:  movl   $10, -20(%rbp)
0x100000d57:  movq   -8(%rbp), %rsi
0x100000d5b:  movq   %rsi, %rdi
0x100000d5e:  callq  0x100000eac               ; symbol stub for: objc_retain
0x100000d63:  leaq   -32(%rbp), %rdi
0x100000d67:  movabsq$0, %rsi
0x100000d71:  movq   %rax, -32(%rbp)
0x100000d75:  movl   $1, -36(%rbp)
0x100000d7c:  callq  0x100000eb8               ; symbol stub for: objc_storeStrong
0x100000d81:  movabsq$0, %rax
0x100000d8b:  addq   $48, %rsp
0x100000d8f:  popq   %rbp
0x100000d90:  ret    



Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. porcokang 2015.01.09 10:50 Address Modify/Delete Reply

    글을 쓰다 만듯한 포스트 입니다. 하지만 잘 읽었습니다.

아름답고 자연스러운 오브젝티브-C

필자가 오브젝티브-C(Objective-C)를 접한 것은 올해 초, 갑갑한 문법적인 표현(Syntax)을 보니 코드를 보기가 싫어졌었다. 하지만 많은 iOS 개발자가 생겨나고 맥킨토시(Macintosh)를 쓰면서 자연스럽게 맥용 응용 프로그램에 관심이 생기기 시작했다. 처음에는 리눅스와 대부분의 운영체제를 지원하는 Qt(큐티) 프레임워크를 봐오다가, 코코아(Cocoa) 를 알게 되면서 맥킨토시에 가장 아름다운 UI 프레임워크인 것을 느끼게 되었다고 할까.

오브젝티브-C는 매우 깊은 역사가 있다. 이 역사에 대해서는 다음의 위키피디아(Wikipedia) 를 참고하기 바란다. 필자도 이 언어에 대한 깊은 역사를 이렇다 할 만큼 자신 있게 설명해 주기 힘들 것 같다.

.. 생략 .. [1]
오브젝티브-C는 스텝스톤(Stepstone)이라는 회사에서 일하고 있던 브래드 콕스(Brad Cox)와 톰 러브(Tom Love)라는 두 연구원이 만들었다. 역시 1980년대 초의 일이다. 이 두 사람은 1981년 ITT의 프로그래밍 기술 센터(Programming Technology Center)에서 일하던 시절 스몰토크를 처음 접했다. 콕스는 소프트웨어 설계와 프로그래밍에 있어서 재사용성의 문제에 흥미를 느끼게 되었고, 스몰토크같은 프로그래밍 언어가 ITT 시스템 개발자들이 개발을 진행할 강력한 환경을 만드는 데 필요불가결한 요소임을 깨닫게 되었다. 콕스는 C 컴파일러를 고쳐 스몰토크의 기능 일부를 추가하기 시작했고, 곧 그 스스로 OOPC라고 불렀던 C의 객체지향 버전을 내놓게 되었다. 한편 러브는 1982년에 Schlumberger Research에 채용되었고, Smalltalk-80의 최초 상업적 버전을 써 볼 기회를 가지게 되었다. Smalltalk-80은 이후 그들이 낳은 정신적 자식의 성장에 큰 영향을 끼쳤다.
.. 생략 ..

필자가 오브젝티브-C를 접하면서 ‘빙고!’를 외쳤다. 현대적인 언어의 대부분이 C언어, 그리고 바로 이 오브젝티브-C 언어의 영감을 받았다고 해도 무리는 아니다. (단, 현대적인 언어의 정의는 스스로 정의를 내리기 바란다.)

그렇다면 왜 오브젝티브-C 언어가 아름다운지 보자.

아름다움을 만들어 준 메시지 기반 언어

오브젝티브-C의 가장 기초적인 문법을 일반적인 객체 지향 언어의 문법과 비교해 보자.

// 일반적인 객체지향 언어들의 표현
void* obj = data->get_data(); // 또는
void* obj = data.get_data(); // 또는
Object obj = data.get_data();

// 오브젝티브-C 언어
NSObject* obj = [data get_data];

위의 일반적인 객체지향 언어와 오브젝티브-C 언어는 표현하는 방법에 있어 다른 점이 보인다. 자, 무엇이 다른지 한번 살펴보자.

  1. 문법 표현 방법 : 가장 차이가 나는 점은 식의 표현을 대괄호([ ])로 묶여 있다. (메서드 호출 시)
  2. 메서드 호출 방법 : 포인터(->) 또는 참조(.)를 통해 호출하지 않고 공백(a space)으로 메서드를 가리켜 호출한다.

위 코드가 컴파일 된다면 더욱 명확하게 차이점을 볼 수 있다.

일반적인 객체지향 언어의 경우 컴파일 된 어셈블리(기계어) 코드는 메서드가 메모리 상에 상주하고 있는 주소를 통해 직접 메모리를 엑세스하여 메서드를 실행한다.
오브젝티브-C의 경우 바로 이것이 오브젝티브-C 언어의 특징인 메시지를 통해 메서드를 호출하는 방법이다. data 객체는 get_data() 메서드를 직접 호출하지 않고 다른 대리자(위임)를 통해 메서드를 실행한다.

그렇다면 오브젝티브-C는 대리자를 통해 메서드의 실행을 위임함으로서 장점은 뭘까?

  1. NullPoint 예외(오브젝티브-C의 nil 또는 NULL)나 기타 치명적인 오류를 언어적인 레벨에서 차단한다.
  2. 관리언어(Managed Languages)인 자바, C# 등에서 즐겨 쓰는 Code Interception 등의 기법과 유사한 것들을 쉽게 확장할 수 있는 포인트를 제공한다.
  3. LLVM과 같은 가상 머신(Virtual Machine) 매커니즘을 손쉽게 적용할 수 있다. (clang)
딱히 바로 생각나는 것은 몇 안되지만, 더 많은 장점이 있음은 분명하다. 그리고 오브젝티브-C의 메시지 기반으로 작동하는 언어적인 특성을 분석해보자

오브젝티브-C 메시지 기반 메서드 호출

1. objc_msgSend 함수의 메커니즘

오브젝티브-C는 objc_msgSend() 함수를 이용하여 메시지를 보낸다. 이것은 함수만 해당되는 사항은 아니다. 위에서 살펴본 것과 같이 식의 표현을 대괄호([ ... ]) 안에 코드로 표현하는데 이 대괄호 안에 들어가는 모든 객체건 뭐건 간에 메시지를 통해 값을 가져오거나 메서드를 호출하게 된다.

아래 보이는 어셈블리 코드는 필자가 오브젝티브-C로 작성한 맥용 데스크탑 응용 프로그램 중 일부를 어셈블리 레벨에서 디버깅 하는 스크린샷이다. 다음에 사용된 코드의 일부는 다음과 같다.

[self.sidebar setTarget:self];

sidebar는 인스턴스화 된 객체(Object)이다. 오브젝티브-C는 메서드 호출에서만 메시지를 보내는 것이 아니라는 것이다. 그리고 objc_retainAutoreleasedReturnValue() 함수는 매우 긴 이름이지만, 맨 앞 글자의 retain만 주목해서 보면 된다. sidebar 인스턴스는 현재 컨텍스트(Context)에서 참조하므로 참조 카운터(References Count)를 1 증가 시킨다.


2. objc_msgSend 함수 내부에서 전달받은 메시지

아래의 스크린샷은 objc_msgSend() 함수의 내부에서 RIP 레지스터가 가리키는 메모리 주소의 HEX 이다. '73 69 64 65 62 61 72 00'(sidebar) 문자열은 objc_msgSend() 함수에 의해 RTTI(Run-time Type Information) 정보로 객체를 가져오기 위한 메시지라고 보면 된다. lldb의 're r' 명령을 통해 레지스터의 값들을 확인할 수 있다.

얼핏 보아도 아래의 스크린샷의 메모리 공간은 인스턴스 객체의 메시지와 메서드의 메시지들을 몰아서 외부에서 참조할 수 있도록 나열한 것임을 알 수 있다.


3. objc_msgSend() 메서드의 시그너처가 선언된 곳에서 objc_msgSend 호출

lldb 디버거를 통해 ni 명령으로 objc_msgSend() 함수에 진입하면 메시지 전달 함수가 메모리 상에 상주해 있는 곳으로 이동 시킨다.


4. 컨텍스트(Context)에서 전달된 메시지를 순회하여 검색/실행

전달되는 메시지를 통해 인스턴스의 메타데이터를 사용하여 실제로 객체를 가져오게 된다. 아래의 네모난 상자의 명령들이 바로 객체를 찾아오기 위한 루프(Loop)이다. 


5. 메시지에 의해 처리된 객체는 일부 캐싱하여 성능 향상을 꾀함

재미있는 점은 매번 메시지의 정보를 순회하면서 찾지 않고, 한번 찾은 정보는 별도로 캐싱 하고 있는 공간에서 검색하게 된다.


지금까지는 sidebar 인스턴스를 가져오기 위한 과정이었다. 이제 이 인스턴스 객체의 메서드를 호출하는 과정인데, objc_sendMsg()를 통해 메시지를 전달하여 찾은 메서드를 실행하게 된다.



Posted by 땡초 POWERUMC

댓글을 달아 주세요