Block的好處,我總結了下主要有2點:1.用於回撥特別方便,2.可以延長物件的作用區域。但是,Block的記憶體管理這個模組一直不是很清楚,這個週末好好的看了下Block的原理,有些許心得。
為了效能,預設Block都是分配在stack上面的,所以它的作用區域就是當前函式。
#include <stdio.h> int main() { int i = 1024; void (^blk)(void) = ^ { printf("%d\n", i); }; blk(); return 0; }
在blk這個block裡面是不能修改i的。Why?我們可以通過clang看看編譯器處理後的這塊程式碼
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int i; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int i = __cself->i; // bound by copy printf("%d\n", i); } 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() { int i = 1024; void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i); ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk); return 0; }
struct __block_impl是Block的一個內部結構體,原型是
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };
每個block都有個預設的建構函式
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) 所以只能讀取i,而不能修改i,當你試圖修改它時,編譯器就在預處理階段直接報錯。
只要在i前加__Block變數就可以在Block裡面修改i值了,此時由值型別變為引用型別
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *i; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_i, int flags=0) : i(_i) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *i = __cself->i; // bound by copy printf("%d\n", (*i)); } 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() { static int i = 1024; void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &i); ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk); return 0; }
上面的程式碼塊是將int i的型別修改為__Block int i = 1024;後編譯器生成程式碼塊,可以看到__main_block_impl_0中的 i型別已經改變為int *,所以我們可以修改它的值。
所以只要沒對Block進行copy操作,它一直存在stack裡面。不管是否有__block修飾符
要想延長Block的作於域,我們可以對它進行copy操作,apple提供的介面是Block_Copy()方法
/* Copy, or bump refcount, of a block. If really copying, call the copy helper if present. */ static void *_Block_copy_internal(const void *arg, const int flags) { struct Block_layout *aBlock; const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; //printf("_Block_copy_internal(%p, %x)\n", arg, flags); if (!arg) return NULL; // The following would be better done as a switch statement aBlock = (struct Block_layout *)arg; if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; } else if (aBlock->flags & BLOCK_IS_GC) { // GC refcounting is expensive so do most refcounting here. if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 1)) { // Tell collector to hang on this - it will bump the GC refcount version _Block_setHasRefcount(aBlock, true); } return aBlock; } else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; } // Its a stack block. Make a copy. if (!isGC) { struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return (void *)0; memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 1; result->isa = _NSConcreteMallocBlock; if (result->flags & BLOCK_HAS_COPY_DISPOSE) { //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock); (*aBlock->descriptor->copy)(result, aBlock); // do fixup } return result; } else { // Under GC want allocation with refcount 1 so we ask for "true" if wantsOne // This allows the copy helper routines to make non-refcounted block copies under GC unsigned long int flags = aBlock->flags; bool hasCTOR = (flags & BLOCK_HAS_CTOR) != 0; struct Block_layout *result = _Block_allocator(aBlock->descriptor->size, wantsOne, hasCTOR); if (!result) return (void *)0; memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // reset refcount // if we copy a malloc block to a GC block then we need to clear NEEDS_FREE. flags &= ~(BLOCK_NEEDS_FREE|BLOCK_REFCOUNT_MASK); // XXX not needed if (wantsOne) flags |= BLOCK_IS_GC | 1; else flags |= BLOCK_IS_GC; result->flags = flags; if (flags & BLOCK_HAS_COPY_DISPOSE) { //printf("calling block copy helper...\n"); (*aBlock->descriptor->copy)(result, aBlock); // do fixup } if (hasCTOR) { result->isa = _NSConcreteFinalizingBlock; } else { result->isa = _NSConcreteAutoBlock; } return result; } }
通過觀察apple提供的block原始碼,我們可以看到copy方法將block從statck拷貝到heap裡面,所以它的作用區域延長了
待完成:1.block與oc的混合
2.__block修飾符與oc的混合
總結
1.block預設都是分配在stack,當copy後,它移到heap裡
2.block中的變數預設是不能修改的,只有新增__Block修飾符後才能修改
3.block中有oc物件時,會_Block_retain_object(object),直到block銷燬後才會_Block_release_object(object);
4.對block進行copy時
-
If you access an instance variable by reference, a strong reference is made to
self
; -
If you access an instance variable by value, a strong reference is made to the variable.
它會將self進行copy,此時改物件的dealloc方法不會執行(因為它的引用計數歸0),解決此問題有2種方法:在block執行完成後面立即Block_Release(),或者將改變數宣告為__Block型別(Why?)
繼續補充block的oc的混合
// // main.m // block // // Created by lijian on 13-8-9. // Copyright (c) 2013年 YOUKU. All rights reserved. // #import <Foundation/Foundation.h> int main (int argc, const char * argv[]) { NSMutableString *str = [NSMutableString stringWithFormat:@"lijian"]; void (^blk)(void) = ^ { NSLog(@"%@", str); }; blk(); return 0; }
編譯器生成程式碼為
// // main.m // block // // Created by lijian on 13-8-9. // Copyright (c) 2013年 lijian. All rights reserved. // #include <Foundation/Foundation.h> int main(int, const char **); struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSMutableString *str; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSMutableString *str = __cself->str; // bound by copy NSLog((NSString *)&__NSConstantStringImpl_main_m_1, str); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);} static struct __main_block_desc_0 { unsigned long reserved; unsigned long Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main (int argc, const char * argv[]) { NSMutableString *str = ((id (*)(id, SEL, NSString *, ...))(void *)objc_msgSend)(objc_getClass("NSMutableString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl_main_m_0); void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344); ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk); return 0; }
blk的原型為
void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344);
其中570425344 = BLOCK_HAS_COPY_DISPOSE | BLOCK_HAS_DESCRIPTOR;
通過建構函式,我們看到仍然是值傳遞,所以blk中是能不修改str的。
至於上面的__main_block_copy_0和__main_block_dispose_0 就是用於Block_Copy()和Block_Release();
當我將str的型別修改為__block NSMutableString時,生成如下程式碼
int main(int, const char **); struct __Block_byref_str_0 { void *__isa; __Block_byref_str_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSMutableString *str; }; static void __Block_byref_id_object_copy_131(void *dst, void *src) { _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131); } static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void * *) ((char*)src + 40), 131); } struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_str_0 *str; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_str_0 *_str, int flags=0) : str(_str->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_str_0 *str = __cself->str; // bound by ref (str->__forwarding->str) = ((id (*)(id, SEL, NSString *, ...))(void *)objc_msgSend)(objc_getClass("NSMutableString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl_main2_m_1); NSLog((NSString *)&__NSConstantStringImpl_main2_m_2, (str->__forwarding->str)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 8/*BLOCK_FIELD_IS_BYREF*/);} static struct __main_block_desc_0 { unsigned long reserved; unsigned long Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main (int argc, const char * argv[]) { __block __Block_byref_str_0 str = {(void*)0,(__Block_byref_str_0 *)&str, 33554432, sizeof(__Block_byref_str_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((id (*)(id, SEL, NSString *, ...))(void *)objc_msgSend)(objc_getClass("NSMutableString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl_main2_m_0)}; void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (struct __Block_byref_str_0 *)&str, 570425344); ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk); return 0; }
可以看到str的型別實際上是__Block_byref_str_0,其中33554432 = BLOCK_HAS_COPY_DISPOSE = (1 << 25)
而blk的建構函式中傳遞的是__Block_byref_str_0型別的指標,所以我們能在blk中修改str