[블록 구현]
여기서 설명되는 코드는 ARC 환경이라고 가정한다.
블록의 내부
: 블록구문은 뭔가 특별하다고 생각할지 모르지만, 컴파일러는 블록을 평범한 C 언어 코드로 변환한 다음 똑같이 컴파일한다.
소스코드 변환하기
: 블록 구문을 포함하는 소스코드는 -rewrite-objc 컴파일러 옵션을 사용해서 표준 c++ 소스코드로 변환할 수 있다. 변환된 소스코드가 C++로 쓰여졌기는 하지만, 구조체를 위해 생성자를 사용할 때를 제외하고는 거의 C로 쓰여졌다.
(clang -rewrite-objc file_name_of_the_source_code)
int main()
{
void (^blk)(void) = ^{ printf("Block\n"); };
blk();
return 0;
}
---> C++소스로 변환
struct __block_impl
{
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 //실제 블록 구조체 라고 보면 됨.
{
struct __block_impl impl;
struct __main_block_desc_0 *Desk;
__main_block_impl_0 (void *fp, struct __main_block_desc_0 *desk, int flags = 0)
{
impl.isa = &NSConcreteStatckBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
static struct __main_block_desc_0
{
unsigned long reserved; //여유 공간
unsigned long Block_size; //블록 크기
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
int main()
{
void (*blk)(void) =
(void) (*)(void)&__main_block_impl_0(
(void *)__main_block_func_), &__main_block_desc_0_DATA);
((void *)(struct __block_impl *)
(struct __block_impl *)blk) -> FuncPtr)((struct __block_impl *) blk);
return 0;
}
엄청나게 코드가 늘어났다! -_- 추후에 하나하나 자세히 들여다 볼것이다. 일단 선행되는 개념부터 알아보겠다.
Objective-C 에서 self
MyObject의 인스턴스 메서드라고 하자.
- (void)method:(int)arg
{
NSLog(@"%p %d\n", self, arg);
}
------> 컴파일러가 C함수로 변환한다.
void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)
{
NSLog(@"%p %d\n", self, arg);
}
이제 메서드를 호출하는 경우를 보자.
MyObjecdt *obj = [[MyObject alloc] init];
[obj method:10];
------> 컴파일러가 C함수로 변환한다.
MyObject *obj = objc_msgSend( objc_getClass("MyObject"), self_registerName("alloc"));
obj = objc_msgSend(obj, sel_registerName("init"));
objc_msgSend(obj, sel_registerName("method:", 10));
세번째 줄에서 MyObject의 인스턴스 메서드로 "mehod"라는 이름으로 등록되 있는 함수 포인터를 찾아 호출한다. 그러면 void _I_MyObject_method_(struct MyObject *self, SEL _smd, int arg) 함수가 호출될 것이다. 이때 첫번째 인자로 넘어온 obj를 함수의 첫번째 인자 self에 대입한다.
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
를 보면 __cself에 블록 구조체인 struct __main_block_impl_0의 포인터를 인자로 받고 있다. 이 함수에서는 __cself가 사용되지 않았지만 블록에 변수가 캡쳐되어 블록안에서 사용된다면 캡쳐된 변수를 __cself를 통해 가져올 것이다. 이해가 안된다면 밑에 설명을 계속 보자.
생성자를 제외한 블록 구조체이다.
struct __main_block_impl_0
{
struct __block_impl impl;
struct __main_block_desc_0 *Desk;
};
블록 구조체는 블록에 대한 정보를 가지고 있는 2개의 구조체를 멤버변수로 가진다.
main 함수에서 호출되는 부분을 살펴보자.
void (*blk)(void) =
(void) (*)(void)&__main_block_impl_0(
(void *)__main_block_func_), &__main_block_desc_0_DATA);
----> 형변환 코드를 제외해 보자.
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
코드를 보면 블록 구조체가 적절한 값(함수 포인터, desc 구조체)으로 초기화 되어 변수에 할당 되었다.
이제 __block_impl 부분을 블록 구조체에 넣어서 확장해보면 다음과 같이 쓸 수 있다.
struct __main_block_impl_0
{
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0 *Desk;
}
이 구조체는 다음과 같이 초기화 되었다.
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
그리고 실제 블록이 호출되는 부분을 살펴보자.
((void *)(struct __block_impl *)
(struct __block_impl *)blk) -> impl.FuncPtr)((struct __block_impl *) blk);
-----> 형 변환 코드를 제거해보자.
(*blk->impl.FuncPtr)(blk);
블록 구조체의 저장되어 있는 함수포인터를 호출 하면서 자신(블록 구조체)을 인자로 넘겨주고 있다. 이는 __cself로 전달된다.
결국 Block의 구조를 보면 Objective-C 객체의 구조랑 똑같다. struct __main_block_impl_0은 우리가 objective-C객체의 구조체라고 알고있는 struct objc_object의 확장형이라고 보면된다. 즉, 블록 역시 Objective-C 객체라고 볼 수 있다. 블록 구조체의 isa값 _NSConcreteStackBlock 역시 Objective-C 클래스 구조체라고 알려져있는 class_t 구조체의 인스턴스를 가리킨다. 즉, _NSConcreteStackBlock은 블록의 클래스라고 보면되고, 블록을 Objective-C 객체로 인식하고 처리하기 위한 정보를 담고 있다.
지역 변수 캡쳐
: 블록이 생성될 때, 블록의 매개변수로 지역변수가 전달되면 블록 구조체의 멤버변수가 생성되고 매개변수로 넘어온 값이 저장된다. 즉, 생성된 시점에 값이 블록 구조체의 멤버변수로 저장되기 때문에 블록 생성 이후에 값이 변경된다 하더라도 블록 구조체의 멤버변수의 값이 바뀌지 않는다.
'IOS > Objective-C' 카테고리의 다른 글
Block의 모든것(4) (0) | 2017.01.07 |
---|---|
Block의 모든것(3) (0) | 2017.01.02 |
Block의 모든것(1) (0) | 2017.01.02 |
ARC 규칙 (0) | 2017.01.01 |
GCD(Grand Central Dispatch)(2) (0) | 2016.12.19 |