如果大家還有映像的話,我們在前面講解結構體 _block_impl的時候,里面有一個成員叫isa,這個代表了block在內存區域中的分布。如果你看了一些關于block的文章,isa會有三種取值:
isa = &_NSConcreteStackBlock;
isa = &_NSConcreteMallocBlock;
isa = &_NSConcreteGlobalBlock;
但是clang出來的文件,里面都是第一種,說明這并不是block在內存中的真正分布。真正的分布,我們可以通過打印來確定。
第二張圖的打印結果是NSMallocBlock,同時我又試了其他的幾種情況,發現都是NSMallocBlock,說明在ARC環境下,棧上的block默認都會被拷貝到堆上,也就是說,在ARC環境下,block只有兩種類型:NSGlobalBlock 和 NSMallocBlock。那么到底有沒有特殊的情況呢?后來查查資料,發現還真有。
執行上面的代碼的時候,直接crash了,從錯誤的提示可以看出,是某個東西的內存過早的釋放了。我們仔細觀察一下控制臺,發現數組的第一個元素是NSMallocBlock類型,這是被分配在堆上的block;數組的第二個元素有點兒問題:發現它的內存地址跟argv的格式比較像,而argv是函數的參數,內存是被分配在棧內存上的,所以第二個block也是被分配在棧內存上的,并沒有被拷貝到堆內存上。然后我們換一種寫法:
發現可以正常運行,并且打印的結果也是我們想要的。
我們再看一下上面的數組的初始化函數
+ (instancetype)arrayWithObjects:(ObjectType)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
這個函數是一個可變函數,只有第一個參數被顯示的申明為ObjectType類型,也就是id類型,其他的參數并沒有被顯示的申明為id類型。這也驗證了第一種情況下第一個block被分配在堆上,第二個block被分配在棧上。而我們的第二種寫法是,先申明一下block,在block到底是什么一文中,我們已經說了,block其實就是一個函數指針,也可以說它是一個id類型,所以在第二種寫法下,兩個block都被顯示的申明為id類型,所以都被分配在堆上,所以第二種情況沒有問題。