block 원리 상세 설명(3)

이 시리즈의 블로그는'Pro Multithreading and Memory Management for iOS and OS X with ARC'에서 요약한 것이다.
만약 제 블로그가 당신에게 도움이 된다고 생각하신다면, 저의 시나닷컴 웨이보 마이크로케이를 팔로우를 통해 저를 지지해 주십시오. 감사합니다.
지난 글에서 우리는 Block과 기초 변수의 메모리 관리에 대해 많이 이야기했다. 이어서 Block과 대상의 메모리 관리, 예를 들어 Block이 자주 부딪치는 순환 인용 문제 등에 대해 이야기했다.

객체 가져오기


선례대로 코드를 가볍게 떼어내서 Block이 어떻게 외부 대상을 얻었는지 봅시다
   
   
   
   
  1. /********************** capturing objects **********************/
  2. typedef void (^blk_t)(id obj);
  3. blk_t blk;
  4. - (void)viewDidLoad
  5. {
  6. [self captureObject];
  7. blk([[NSObject alloc] init]);
  8. blk([[NSObject alloc] init]);
  9. blk([[NSObject alloc] init]);
  10. }
  11. - (void)captureObject
  12. {
  13. id array = [[NSMutableArray alloc] init];
  14. blk = [^(id obj) {
  15. [array addObject:obj];
  16. NSLog(@"array count = %ld", [array count]);
  17. } copy];
  18. }

   
   
   
   
  1. /* a struct for the Block and some functions */
  2. struct __main_block_impl_0
  3. {
  4. struct __block_impl impl;
  5. struct __main_block_desc_0 *Desc;
  6. id __strong array;
  7. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong _array, int flags=0) : array(_array)
  8. {
  9. impl.isa = &_NSConcreteStackBlock;
  10. impl.Flags = flags;
  11. impl.FuncPtr = fp;
  12. Desc = desc;
  13. }
  14. };
  15. static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj)
  16. {
  17. id __strong array = __cself->array;
  18. [array addObject:obj];
  19. NSLog(@"array count = %ld", [array count]);
  20. }
  21. static void __main_block_copy_0(struct __main_block_impl_0 *dst, __main_block_impl_0 *src)
  22. {
  23. _Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
  24. }
  25. static void __main_block_dispose_0(struct __main_block_impl_0 *src)
  26. {
  27. _Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
  28. }
  29. struct static struct __main_block_desc_0
  30. {
  31. unsigned long reserved;
  32. unsigned long Block_size;
  33. void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  34. void (*dispose)(struct __main_block_impl_0*);
  35. } __main_block_desc_0_DATA = { 0,
  36. sizeof(struct __main_block_impl_0),
  37. __main_block_copy_0,
  38. __main_block_dispose_0
  39. };
  40. /* Block literal and executing the Block */
  41. blk_t blk;
  42. {
  43. id __strong array = [[NSMutableArray alloc] init];
  44. blk = &__main_block_impl_0(__main_block_func_0,
  45. &__main_block_desc_0_DATA,
  46. array,
  47. 0x22000000);
  48. blk = [blk copy];
  49. }
  50. (*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
  51. (*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
  52. (*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);

, ,array은 폐기되고 강제 인용은 효력을 상실하며 NSMutableArray류의 실례 대상은 방출되고 폐기된다.이 위험한 고비에 Block은 copy 방법을 제때에 호출하여 _Block_object_assign에서 array의 값을 Block 구성원 변수에 부여하고 보유했다.따라서 위 코드는 정상적으로 운행할 수 있고 인쇄된 array count은 순서대로 증가한다.
총괄 코드가 정상적으로 운행될 수 있는 원인은 Block이 copy 방법을 호출하여 을 가지고 있다는 데 있다strong 수식의 외부 변수는 외부 대상이 역할 영역을 초과한 후에도 계속 살아남고 코드가 정상적으로 실행되도록 합니다.
다음 경우 Block은 스택에서 스택으로 복제됩니다.

  • Block이 copy 방법을 호출할 때 Block이 창고에 있으면 무더기로 복사됩니다.
  • 은 Block이 함수로 되돌아올 때 컴파일러가 자동으로 Block을 _Block_copy 함수로 하고 효과는 Block이 copy 방법을 직접 호출하는 것과 같다.
  • Block이 에 할당될 때strong id 형식의 대상이나 Block의 구성원 변수일 때 컴파일러는 자동으로 Block을 _Block_copy 함수로 하고 효과는 Block이 copy 방법을 직접 호출하는 것과 같다.
  • Block이 usingBlock의 Cocoa Framework 방법이나 GCD의 API를 매개 변수로 전달될 때이러한 방법은 내부에서 전달된 Block을 copy 또는 _Block_copy으로 복사한다.

  • 사실 그 다음 세 가지 상황은 지난 글의 Block 자동 복사에 대해 설명했습니다.
    그 외에 수동으로 호출해야 한다.
    확장 읽기: Objective-C 구조체의strong 구성원 변수__main_block_impl_0 구조체에 무슨 이상이 있는지 알아차렸습니까?C 구조체에 __strong 키워드 수식의 변수가 나타났다.
    통상적으로 Objective-C의 컴파일러는 C 구조체의 초기화와 방출 시간을 측정할 수 없기 때문에 효과적인 메모리 관리를 할 수 없기 때문에 Objective-C의 C 구조체 구성원은 __strong, __weak 등 키워드로 수식할 수 없다.그러나runtime 라이브러리는 실행할 때 Block의 메모리 변화를 감지할 수 있다. 예를 들어 Block이 언제 창고에서 더미로 복사되고 언제 더미에서 방출되는지 등이다. 따라서 상기 구조체 구성원 변수가 __strong으로 수식되는 상황이 발생한다.

    __block 변수 및 객체


    __block 설명자는 모든 종류의 자동 변수를 수식할 수 있습니다.다음은 작은 예를 하나 더 보겠습니다. 아, 즐거운 코드 시간이 또 왔습니다.
       
       
       
       
    1. /******* block *******/
    2. __block id obj = [[NSObject alloc] init];

    ARC ,  __strong, 즉

       
       
       
       
    1. __block id __strong obj = [[NSObject alloc] init];
       
       
       
       
    1. /******* block *******/
    2. /* struct for __block variable */
    3. struct __Block_byref_obj_0
    4. {
    5. void *__isa;
    6. __Block_byref_obj_0 *__forwarding;
    7. int __flags;
    8. int __size;
    9. void (*__Block_byref_id_object_copy)(void*, void*);
    10. void (*__Block_byref_id_object_dispose)(void*);
    11. __strong id obj;
    12. };
    13. static void __Block_byref_id_object_copy_131(void *dst, void *src)
    14. {
    15. _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    16. }
    17. static void __Block_byref_id_object_dispose_131(void *src)
    18. {
    19. _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    20. }
    21. /* __block variable declaration */
    22. __Block_byref_obj_0 obj = { 0,
    23. &obj,
    24. 0x2000000,
    25. sizeof(__Block_byref_obj_0),
    26. __Block_byref_id_object_copy_131,
    27. __Block_byref_id_object_dispose_131,
    28. [[NSObject alloc] init]
    29. };

    __block id __strong obj의 작용과 id __strong obj의 작용은 매우 유사하다.__block id __strong obj이 창고에서 더미로 복사되었을 때 _Block_object_assign이 호출되었고 Block은 obj을 보유했다.__block id __strong obj이 더미에서 폐기되었을 때 _Block_object_dispose은 이 대상을 방출하기 위해 호출되었고 Block 인용은 사라졌습니다.
    따라서 쌓인 __strong 수식자 수식의 __block 대상 유형의 변수와 Block에서 얻은 __strong 수식자 수식의 대상 유형의 변수라면 컴파일러는 그들의 메모리를 적절하게 관리할 수 있다.
    위의 __strong__weak으로 바꾸면 어떻게 될까요?

       
       
       
       
    1. /********************** capturing __weak objects **********************/
    2. typedef void (^blk_t)(id obj);
    3. blk_t blk;
    4. - (void)viewDidLoad
    5. {
    6. [self captureObject];
    7. blk([[NSObject alloc] init]);
    8. blk([[NSObject alloc] init]);
    9. blk([[NSObject alloc] init]);
    10. }
    11. - (void)captureObject
    12. {
    13. id array = [[NSMutableArray alloc] init];
    14. id __weak array2 = array;
    15. blk = [^(id obj) {
    16. [array2 addObject:obj];
    17. NSLog(@"array2 count = %ld", [array2 count]);
    18. } copy];
    19. }

       
       
       
       
    1. array2 count = 0
    2. array2 count = 0
    3. array2 count = 0

    array2은 약한 인용이다. 변수 작용역이 끝나면 array이 가리키는 대상 메모리가 방출되고 array2은 닐을 가리키며 닐 대상에게 count 메시지를 보내면 결과 0으로 돌아간다.__weak__unsafe_unretained으로 바꾸면?__unsafe_unretained 수식의 대상 변수 바늘은 일반 바늘과 같다.이 수식자를 사용할 때 주의해야 할 점은 바늘이 가리키는 대상의 메모리가 방출될 때 바늘 변수가nil로 설정되지 않는다는 것이다.따라서 이 수식자를 사용할 때, 버려진 메모리를 가리키는 바늘을 걸어서 버려진 대상의 메모리에 접근하지 않도록 주의해야 한다. 그렇지 않으면 프로그램이 붕괴될 것이다.__unsafe_unretained__autoreleasing으로 바꾸면 어떨까요?오류가 발생할 수 있습니다. 컴파일러는 당신이 이렇게 하는 것을 허락하지 않습니다.하면, 만약, 만약...

       
       
       
       
    1. __block id __autoreleasing obj = [[NSObject alloc] init];

    컴파일러는 아래의 오류를 보고합니다. __block__autoreleasing을 동시에 사용할 수 없다는 뜻입니다.
    error: __block variables cannot have __autoreleasing ownership __block id __autoreleasing obj = [[NSObject alloc] init];

    순환 참조


    천신만고 끝에 마침내 중요한 연극이 왔다.Block을 조심하지 않으면 순환 인용이 일어나 메모리가 유출되기 쉽다.뭐가 유출된 거지?앞의 학습을 통해 여러분은 구두에 밑창이 있을 것입니다. 다음은 이 유출 지역에 함께 들어가서 어디에 문제가 있는지 봅시다!
    즐거운 코드 시간
       
       
       
       
    1. // ARC enabled
    2. /************** MyObject Class **************/
    3. typedef void (^blk_t)(void);
    4. @interface MyObject : NSObject
    5. {
    6. blk_t blk_;
    7. }
    8. @end
    9. @implementation MyObject
    10. - (id)init
    11. {
    12. self = [super init];
    13. blk_ = ^{NSLog(@"self = %@", self);};
    14. return self;
    15. }
    16. - (void)dealloc
    17. {
    18. NSLog(@"dealloc");
    19. }
    20. @end
    21. /************** main function **************/
    22. int main()
    23. {
    24. id myObject = [[MyObject alloc] init];
    25. NSLog(@"%@", myObject);
    26. return 0;
    27. }

     self__strong으로 수식되었다. ARC에서 컴파일러가 자동으로 코드 중의 Block을 창고에서 더미로 복사할 때 Block은 self을 강제로 인용하고 보유한다. self도 마침 Block을 강제로 인용하고 보유하여 전설의 순환 인용을 만들었다.
    순환 인용의 존재로 인해 main() 함수가 끝날 때 메모리는 여전히 방출할 수 없다. 즉, 메모리 유출이다.컴파일러도 경고 메시지를 줄 것이다
    warning: capturing 'self' strongly in this block is likely to lead to a retain cycle [-Warc-retain-cycles]  blk_ = ^{NSLog(@"self = %@", self);}; 
    note: Block will be retained by an object strongly retained by the captured object  blk_ = ^{NSLog(@"self = %@", self);};
    이러한 상황을 피하기 위해 변수 성명 시 __weak 수식부호로 변수 self을 수식하여 Block이 self을 강하게 인용하지 않도록 하여 순환을 깨뜨릴 수 있다.iOS4와 Snow Leopard는 weak에 대한 지원이 충분하지 않기 때문에 __unsafe_unretained으로 대체할 수 있다.

       
       
       
       
    1. - (id)init
    2. {
    3. self = [super init];
    4. id __weak tmp = self;
    5. blk_ = ^{NSLog(@"self = %@", tmp);};
    6. return self;
    7. }

       
       
       
       
    1. @interface MyObject : NSObject
    2. {
    3. blk_t blk_;
    4. id obj_;
    5. }
    6. @end
    7. @implementation MyObject
    8. - (id)init
    9. {
    10. self = [super init];
    11. blk_ = ^{ NSLog(@"obj_ = %@", obj_); };
    12. return self;
    13. }
    14. ...
    15. ...
    16. @end

    , self, 。 ,obj_self->obj_에 해당하기 때문에 위의 코드는

       
       
       
       
    1. blk_ = ^{ NSLog(@"obj_ = %@", self->obj_); };

    그래서 이 예는 __weak으로 init 방법에 한 줄을 추가하면 된다
       
       
       
       
    1. id __weak obj = obj_;

    순환 인용을 해독하는 방법도 있습니다. 사용Block 수식 대상, Block에서 대상을nil로 설정하면 다음과 같습니다.
       
       
       
       
    1. typedef void (^blk_t)(void);
    2. @interface MyObject : NSObject
    3. {
    4. blk_t blk_;
    5. }
    6. @end
    7. @implementation MyObject
    8. - (id)init
    9. {
    10. self = [super init];
    11. __block id tmp = self;
    12. blk_ = ^{
    13. NSLog(@"self = %@", tmp);
    14. tmp = nil;
    15. };
    16. return self;
    17. }
    18. - (void)execBlock
    19. {
    20. blk_();
    21. }
    22. - (void)dealloc
    23. {
    24. NSLog(@"dealloc");
    25. }
    26. @end
    27. int main()
    28. {
    29. id object = [[MyObject alloc] init];
    30. [object execBlock];
    31. return 0;
    32. }

    ,  execBlock 방법은 순환 인용이 없고 실행하지 않으면 순환 인용이 있어 음미할 만하다.한편, 사용Block은 매우 위험합니다. 만일 코드에서 Block을 실행하지 않으면 순환 인용을 초래하고 컴파일러가 검사할 수 없습니다.한편, 사용Block은 을 통해block 변수는 객체의 라이프 사이클을 제어하며, 이 지원되지 않기 때문에 매우 오래된 MRC 코드에 있을 수 있습니다.weak, 대신 이 방법을 사용할 수 있습니다unsafe_unretained, 바늘을 걸지 않도록 합니다.
    또 하나 언급할 만한 것은 MRC 아래에서 을 사용하는 것이다Block 설명자도 순환 인용을 피할 수 있습니다.Block이 창고에서 무더기로 복사될 때block 대상 형식의 변수는retain에 의해 되지 않습니다block 설명자의 대상 형식의 변수는retian에 의해 설명됩니다.바로 때문에Block은 ARC와 MRC에서 큰 차이를 보이기 때문에 우리는 코드를 쓸 때 ARC인지 MRC인지 반드시 구분해야 한다.
    비록 ARC가 이렇게 보급되었음에도 불구하고 우리는 MRC의 물건을 관리하지 않을 수 있지만 ARC와 MRC는 모두 인용계수를 바탕으로 하는 메모리 관리이다. 그 본질은 하나의 것이다. 단지 ARC가 컴파일링 기간에 자동적으로 메모리 인용계수를 관리했기 때문에 시스템은 적당한 시기에 메모리를 보존하고 적당한 시기에 메모리를 방출할 수 있다.
    순환 인용은 여기까지입니다. 물건이 많지 않습니다.이전의 지식점을 알면 순환 인용은 앞의 지식점의 자연 연장점에 불과하다는 것을 알 수 있다.

    Copy 및 Release


    ARC 에서 Block 을 수동으로 복제하고 해제해야 하는 경우가 있습니다.MRC 의 경우 copyrelease 을 사용하여 복제 및 방출 가능
       
       
       
       
    1. void (^blk_on_heap)(void) = [blk_on_stack copy];
    2. [blk_on_heap release];

    더미로 복사하면 retain으로 Block을 보유할 수 있습니다
       
       
       
       
    1. [blk_on_heap retain];

    그러나 Block이 창고에 있으면 retain을 사용하면 효과가 없기 때문에 copy 방법으로 Block을 소지하는 것을 추천합니다.
    Block은 C 언어의 확장이기 때문에 C에서 Block의 문법을 사용할 수 있습니다.예를 들어 위의 예에서 Block_copyBlock_release 함수를 직접 사용하여 copyrelease 방법을 대체할 수 있다
       
       
       
       
    1. void (^blk_on_heap)(void) = Block_copy(blk_on_stack);
    2. Block_release(blk_on_heap);
    Block_copy의 역할은 이전에 보았던 _Block_copy 함수에 해당하며, Objective-C runtime 라이브러리가 실행될 때 Block을 복사하는 데 사용하는 것이 바로 이 함수이다.같은 이치로 Block을 방출할 때runtime는 Block_release 함수를 호출했다.
    마지막으로 Block을 정리한 글이 있는데 아주 괜찮습니다. 여러분께 추천합니다.http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/
    본문은 다음과 같습니다.https://www.zybuluo.com/MicroCai/note/58470

    좋은 웹페이지 즐겨찾기