說明:閱讀本文章,請參考之前的block文章加以理解; 一、棧區block分析 //代碼 //列印 分析: <1>block代碼內部引用的Person實例對象先於輸出語句銷毀,因為per僅限於大括弧內,但此時block銷毀了沒有?往下看; <2>上述block代碼塊並沒有被指針持有,接下來看看指針持 ...
說明:閱讀本文章,請參考之前的block文章加以理解;
一、棧區block分析
//代碼
//ARC void test1() { { Person *per = [[Person alloc] init]; per.age = 10; ^{ NSLog(@"age:%d", per.age); }; } NSLog(@"-------1"); }
//列印
2019-01-14 17:24:12.118653+0800 MJ_TEST[6638:285938] Person dealloc 2019-01-14 17:24:12.118934+0800 MJ_TEST[6638:285938] -------1 Program ended with exit code: 0
分析:
<1>block代碼內部引用的Person實例對象先於輸出語句銷毀,因為per僅限於大括弧內,但此時block銷毀了沒有?往下看;
<2>上述block代碼塊並沒有被指針持有,接下來看看指針持有的情況;
//代碼
typedef void(^MyBlock)(void); //ARC void test2() { MyBlock block; { Person *per = [[Person alloc] init]; per.age = 10; block = ^{ NSLog(@"age:%d", per.age); }; } NSLog(@"-------1"); }
//列印
2019-01-14 17:34:58.473267+0800 MJ_TEST[6824:293129] -------1 2019-01-14 17:34:58.473705+0800 MJ_TEST[6824:293129] Person dealloc Program ended with exit code: 0
分析:Person實例對象後於輸出語句銷毀,為什麼有指針持有,順序就變了?
<1>等號左邊:是一個auto類型的局部的block指針變數,存放在棧區;等號右邊:是一個block代碼塊(對象),也是一個局部對象,存放在棧區;
<2>在ARC模式下,如果有指針持有(預設是強指針,修飾符為__strong)一個局部的block對象,系統會自動copy該block對象從棧區到堆區;
補充:其他三種情況——block作為函數返回值、含usingBlock方法(如數據的枚舉方法)、GCD的應用(自己可以驗證,此處不再贅述);
那麼,我們再看看MRC的情況
//列印————test1()和test2()
2019-01-14 17:56:46.641171+0800 MJ_TEST[7171:306788] -------1 Program ended with exit code: 0
分析:為什麼per對象沒有銷毀?——因為需要手動釋放;
//代碼
[per release];
//列印————test1()和test2()
2019-01-14 17:59:39.091313+0800 MJ_TEST[7243:309139] Person dealloc 2019-01-14 17:59:39.091974+0800 MJ_TEST[7243:309139] -------1 2019-01-14 17:59:39.092013+0800 MJ_TEST[7243:309139] Person dealloc 2019-01-14 17:59:39.092086+0800 MJ_TEST[7243:309139] -------1 Program ended with exit code: 0
分析:
<1>此時的block對象的作用域在第一個大括弧範圍內,超出則被釋放;
<2>Person實例對象被捕獲到block對象結構體體中,同時其作用域也僅限於第一個大括弧內,因此超出同樣被釋放;
二、堆區block分析
1)類型分析——ARC
//strong類型
執行上述test2()方法,我們知道系統會自動將block對象從棧區copy到堆區;同時,Person實例對象會被捕捉到block對象的結構體中,如下
struct __test2_block_impl_0 { struct __block_impl impl; struct __test2_block_desc_0* Desc; Person *per; __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *_per, int flags=0) : per(_per) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
分析:可以看到per是一個指針變數,而該指針變數預設修飾符為__strong;修改代碼
__strong Person *per = [[Person alloc] init];
clang命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m
說明:
<1>該命令行,只針對ARC模式下,MRC模式下,如果有release語句會報錯;
<2>該命令行,是解決ARC模式下,實例對象為__weak類型,轉成C++代碼;
struct __test2_block_impl_0 { struct __block_impl impl; struct __test2_block_desc_0* Desc; Person *__strong per; __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *__strong _per, int flags=0) : per(_per) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
分析:因此,一般的指針變數,預設修飾符為__strong;
//weak類型
//代碼
//ARC void test2() { MyBlock block; { Person *per = [[Person alloc] init]; per.age = 10; __weak Person *weakPer = per; block = ^{ NSLog(@"age:%d", weakPer.age); }; // [per release]; } NSLog(@"-------1"); }
//C++代碼
struct __test2_block_impl_0 { struct __block_impl impl; struct __test2_block_desc_0* Desc; Person *__weak weakPer; __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
//列印
2019-01-15 10:21:36.280746+0800 MJ_TEST[997:45906] Person dealloc 2019-01-15 10:21:36.281063+0800 MJ_TEST[997:45906] -------1 Program ended with exit code: 0
分析:
<1>此時的per由weakPer弱指針指向,並被捕捉到block對象結構體中;
<2>結合上述強指針引用Person實例對象的列印結果,weak修飾的指針變數先於輸出語句銷毀,可以肯定系統是沒有對block對象copy到堆區的;
那針對不同類型指針的引用,系統是如果判斷操作的呢?往下看
2)調用原理
//C++代碼
typedef void(*MyBlock)(void); struct __test2_block_impl_0 { struct __block_impl impl; struct __test2_block_desc_0* Desc; Person *__weak weakPer; __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __test2_block_func_0(struct __test2_block_impl_0 *__cself) { Person *__weak weakPer = __cself->weakPer; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_3e374f_mi_2, ((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPer, sel_registerName("age"))); } static void __test2_block_copy_0(struct __test2_block_impl_0*dst, struct __test2_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPer, (void*)src->weakPer, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __test2_block_dispose_0(struct __test2_block_impl_0*src) {_Block_object_dispose((void*)src->weakPer, 3/*BLOCK_FIELD_IS_OBJECT*/);} static struct __test2_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*); void (*dispose)(struct __test2_block_impl_0*); } __test2_block_desc_0_DATA = { 0, sizeof(struct __test2_block_impl_0), __test2_block_copy_0, __test2_block_dispose_0}; void test2() { MyBlock block; { Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)per, sel_registerName("setAge:"), 10); __attribute__((objc_ownership(weak))) Person *weakPer = per; block = ((void (*)())&__test2_block_impl_0((void *)__test2_block_func_0, &__test2_block_desc_0_DATA, weakPer, 570425344)); } NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_3e374f_mi_3); } int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; test2(); } return 0; }
分析:
<1>根據前面的文章分析,我們發現block描述結構體(__test2_block_desc_0)中多了兩個函數指針的成員變數
void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*); void (*dispose)(struct __test2_block_impl_0*);
其中,copy函數指針指向__test2_block_copy_0函數,dispose指向__test2_block_dispose_0;
<2>__test2_block_copy_0函數主要是通過_Block_object_assign函數來確定對per對象是否強引用,其根據就是per的引用類型——如果是__strong類型,則block對象對per對對象進行強引用(per的生命周期可控);如果是__weak類型,則進行弱引用(per的生命周期不可控);
<3>當block對像從堆區銷毀時,會調用__test2_block_dispose_0函數會自動釋放引用的per對象(相當於release)——註:嚴格意義上,此處的釋放指的斷開是block對象對per的引用即retainCount減1,至於per對象所占的記憶體是否被釋放(回收)則在所不問(也許還有其他的指針變數引用),只有retainCount變為0零,其記憶體才會被回收;
補充:當block訪問的外部的auto類型的局部數據為對象時,則會產生上述兩個函數指針;如果是非實例對象(如基礎數據類型),則不會有上述兩個函數指針——原因:實例對象一般是在堆區開闢的記憶體,需要對其進行記憶體管理————註:如果是__block修飾前述變數(包括實例對象),也會產生上述兩個指針函數,具體後面文章會寫到!
//代碼
void test3() { int age = 10; ^{ NSLog(@"%d", age); }; }
//clang
static struct __test3_block_desc_0 { size_t reserved; size_t Block_size; } __test3_block_desc_0_DATA = { 0, sizeof(struct __test3_block_impl_0)};
三、結論
【1】棧區block:不論是ARC還是MRC模式,指向該block對象的指針變數,不會對引用的auto類型的局部的實例對象進行強引用;
【2】堆區block:不論是ARC還是MRC模式,指向該block對象的指針變數,根據引用的auto類型的局部的實例對象的引用類型,通過調用block結構體中的copy函數指針來調用_Block_object_assign函數,來決定對實例對象是否進行強引用——__strong類型強引用,__weak類型弱引用;
【3】堆區block釋放:系統會通過調用block結構體中的dispose函數指針來調用__test2_block_dispose_0函數,自動釋放引用的外部auto類型的局部實例對象;
說明:
<1>block對象本身,即代碼塊(位於等號右邊),非block指針變數(位於等號左邊);
<2>所謂的強引用,類似於retain操作即保留實例對象(所占記憶體不隨作用域限制而被自動回收),保證手動管理記憶體釋放,達到可控的目的;
四、拓展——GCD引用分析
1)
//代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *per = [[Person alloc] init]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*3), dispatch_get_main_queue(), ^{ NSLog(@"%@", per); }); NSLog(@"touchesBegan"); }
//列印
2019-01-15 11:56:19.867668+0800 GCD_Refrence[1558:90109] touchesBegan 2019-01-15 11:56:22.867823+0800 GCD_Refrence[1558:90109] <Person: 0x600003bb8cf0> 2019-01-15 11:56:22.867996+0800 GCD_Refrence[1558:90109] Person dealloc
2)
//代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *per = [[Person alloc] init]; __weak Person *weakPer = per; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*3), dispatch_get_main_queue(), ^{ NSLog(@"%@", weakPer); }); NSLog(@"touchesBegan"); }
//列印
2019-01-15 11:58:58.381396+0800 GCD_Refrence[1619:93062] touchesBegan 2019-01-15 11:58:58.381583+0800 GCD_Refrence[1619:93062] Person dealloc 2019-01-15 11:59:01.381697+0800 GCD_Refrence[1619:93062] (null)
3)
//代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *per = [[Person alloc] init]; __weak Person *weakPer = per; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{ NSLog(@"%@", per); }); }); NSLog(@"touchesBegan"); }
//列印
2019-01-15 12:00:44.996108+0800 GCD_Refrence[1653:94592] touchesBegan 2019-01-15 12:00:48.088426+0800 GCD_Refrence[1653:94592] <Person: 0x6000010f4a20> 2019-01-15 12:00:48.088664+0800 GCD_Refrence[1653:94592] Person dealloc
4)
//代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *per = [[Person alloc] init]; __weak Person *weakPer = per; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{ NSLog(@"%@", weakPer); }); }); NSLog(@"touchesBegan"); }
//列印
2019-01-15 12:01:42.122836+0800 GCD_Refrence[1672:95546] touchesBegan 2019-01-15 12:01:42.123038+0800 GCD_Refrence[1672:95546] Person dealloc 2019-01-15 12:01:45.123256+0800 GCD_Refrence[1672:95546] (null)
5)
//代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *per = [[Person alloc] init]; __weak Person *weakPer = per; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{ NSLog(@"%@", weakPer); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{ NSLog(@"%@", per); }); }); NSLog(@"touchesBegan"); }
//列印
2019-01-15 12:02:50.591355+0800 GCD_Refrence[1693:96581] touchesBegan 2019-01-15 12:02:51.685830+0800 GCD_Refrence[1693:96581] <Person: 0x6000033a4470> 2019-01-15 12:02:53.686541+0800 GCD_Refrence[1693:96581] <Person: 0x6000033a4470> 2019-01-15 12:02:53.686810+0800 GCD_Refrence[1693:96581] Person dealloc
6)
//代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *per = [[Person alloc] init]; __weak Person *weakPer = per; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{ NSLog(@"%@", per); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{ NSLog(@"%@", weakPer); }); }); NSLog(@"touchesBegan"); }
//列印
2019-01-15 12:03:47.349637+0800 GCD_Refrence[1715:97486] touchesBegan 2019-01-15 12:03:48.447971+0800 GCD_Refrence[1715:97486] <Person: 0x600000163900> 2019-01-15 12:03:48.448271+0800 GCD_Refrence[1715:97486] Person dealloc 2019-01-15 12:03:50.448553+0800 GCD_Refrence[1715:97486] (null)
7)
//代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *per = [[Person alloc] init]; __weak Person *weakPer = per; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{ NSLog(@"%@", per); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{ NSLog(@"%@", per); }); }); NSLog(@"touchesBegan"); }
//列印
2019-01-15 12:04:37.584067+0800 GCD_Refrence[1735:98332] touchesBegan 2019-01-15 12:04:38.679922+0800 GCD_Refrence[1735:98332] <Person: 0x600003bd7570> 2019-01-15 12:04:40.876560+0800 GCD_Refrence[1735:98332] <Person: 0x600003bd7570> 2019-01-15 12:04:40.876803+0800 GCD_Refrence[1735:98332] Person dealloc
分析:
<1>ARC環境下,使用GCD時,系統自動將block對象從棧區copy到堆區;
<2>根據以上列印結果,發現如果是單純的對per進行強引用,則延時3秒後per對象才銷毀;如果是弱引用,則立即銷毀,再次使用時為空(此時已經被釋放);
<3>如果既對per強引用又有弱引用,在嵌套的GCD使用中,始終以強引用為準,弱引用不影響強引用;
結論:同一個auto類型的局部的實例對象,既有強引用,也有弱引用,以強引用為準;