객체 캡쳐하기
: 객체는 블록에서 캡쳐되면 블록 구조체의 멤버 변수로 id __strong object;형태로 선언하고 객체를 소유한다.
원래 C구조체는 __strong 속성이 부여된 멤버 변수를 가질 수 없다. 그 이유는 컴파일러가 구조체를 언제 초기화하고 언제 폐기하는지의 적절한 메모리 관리 방식을 가지고 있지 않기 때문이다. 그러나, Block 구조체는 Objective-C객체로 취급되기 때문에 컴파일러가 적절한 메모리 관리 방식을 가지고 있다. 그래서 Block 구조체는 멤버 변수로 __strong 속성이 부여된 멤버변수를 가질 수 있다.

다만, __weak 속성의 객체가 캡쳐될 경우 블록 구조체는 id _weak object; 형태 멤버변수를 선언하고 객체를 가리킨다. 밖에서 객체가 release되면 블록 구조체의 id _weak object는 nil이 된다. 

__block id __weak object 객체를 캡쳐하면 __block 구조체 변수는 가리키고 블록이 힙으로 복사될 때 __block변수는 소유를 하지만 __weak 객체가 block 구조체의 할당될 때 __weak속성으로 할당되기 때문에 객체가 release되면 nil이 할당된다.

블록 순환 참조
: __strong 속성의 객체형 지역 변수가 블록에서 사용되면 블록은 해당 객체의 소유권을 갖는다. 하지만 이 때문에 순환 참조가 발생할 수 있다. 

@interface MyObject : NSObject
{
    blk_t blk;
}
@end

@implementation MyObject

- (instancetype)init
{
    self = [super init];

    if (self)
    {
        blk =^{ NSLog(@"self = %@", self); };
    }

    return self;    

}

- (void)dealloc
{
    NSLog(@"dealloc");
}

@end

int main()
{
    {
        id o = [[MyObject alloc] init];
    }   
}
이 경우 순환참조가 발생하여 "dealloc" 메세지는 절대 발생하지 않는다.
blk가 self를 strongly하게 캡쳐하면서 MyObject 인스턴스의 retainCount는 2가 되고, MyObject 인스턴스 지역 변수의 범위를 벗어난다고 해도 retainCount는 1이 되어 dealloc되지 않는다.

self를 캡쳐할 때,

id __weak sWeakSelf = self;

if (self)
{
    blk =^{ NSLog(@"sWeakSelf = %@", sWeakSelf); };
}

이렇게 캡쳐해야 순환참조 발생을 예방할 수 있다. 

그러나, self를 블록에서 사용하지 않더라도 순환 참조가 발생할 수 있다. 다음 코드를 보자.

@interface MyObject : NSObject
{
    blk_t blk;
    id obj;
}
@end

@implementation MyObject

- (instancetype)init
{
    self = [super init];

    if (self)
    {
        blk =^{ NSLog(@"
obj = %@", obj); };
    }

    return self;    

}

- (void)dealloc
{
    NSLog(@"dealloc");
}

@end

이 경우도 순환 참조가 발생한다. init메서드에서 obj란 self->obj이기 때문에 self가 캡쳐된다.
순환 참조를 피하기 위해서 블록안에서 self 캡쳐를 못하게 하는 방법이 있다.

id sObj = obj;

if (self)
{
    blk =^{ NSLog(@"sO
bj = %@", sObj); };
}

이러면 블록 안에 코드에서는 self가 들어가지 않는다. 그러므로 self를 캡쳐하지 않는다.

* ARC를 사용하지 않을 때의 블록 순환 참조에 대한 방법은 생략하겠다. 


Copy/Release

블록은 C 언어에서 확장된 것이기 때문에 C언어 에서도 블록을 사용할 수 있다. 이 경우 Objective-C의 copy, release메서드 대신에 Block_copy와 Block_release메서드를 사용한다. Objective-C의 런타임은 스택 메모리 영역에 있는 블록을 retain할 때, Block_copy메서드를 사용한다.(스택 메모리 영역의 있는 객체에 retain 메세지를 보내봤자 아무일도 일어나지 않기 때문이다.) Block_copy메서드는 스택 메모리 영역에 있는 객체를 힙 영역으로 복사하고 retainCount를 하나 증가 시킨다.
C언어에서 Block을 retain, release 할 때, Block_copy, Block_release 메서드를 사용하기 바란다.

'IOS > Objective-C' 카테고리의 다른 글

Naming Conventions  (0) 2017.02.18
Objective-C Runtime Programing Guide  (0) 2017.02.17
Block의 모든것(3)  (0) 2017.01.02
Block의 모든것(2)  (0) 2017.01.02
Block의 모든것(1)  (0) 2017.01.02
Posted by 홍성곤
,