Block(Closure) Tips
使用 Block 的時候謹記以下幾點:
1.Block類型:全局塊(Global Block)和堆塊(Heap Block),以及棧塊(Stack Block)。
2.變量捕獲: 默認無法修改變量,需要添加?__block?修飾符
3.避免循環引用。
推薦文章:
1.官方文檔:
快速上手:Working with Blocks,進階:Blocks Programming Topics
2.優秀博客:
Deep into Block: A look inside blocks:?Episode 1,?Episode 2,?Episode 3
底層實現:談 Objective-C Block 的實現 - By 唐巧
類型探討:Objective-C Blocks Quiz
啥都有:對 Objective-C 中 Block 的追探
類型
之前看文檔或是其他人寫的文章都講述了三種 Block 類型,但直到看到上面的測試,我才意識 Block 類型是如何決定的。經過一些實踐,簡單來說,在 Objective-C 中:
1.Block 中沒有使用外圍變量的話,在開啟 ARC 的條件下,因為不需要依賴其他狀態,其使用的內存區域在編譯期就可以確定,將會被編譯為全局塊;而沒有開啟 ARC 時,總是被編譯為棧塊。
在唐巧的博客中使用 Clang 來研究 Block ,這里也學習了一下,但是基本上這些塊都是被編譯為棧塊,按照唐巧的說法,在 ARC 下,是被編譯為全局塊的。
《Effective Objective-C 2.0》給出如下的例子:聲明了一個 Block,但需要根據條件來選擇合適的實現。
void (^block)(); if(條件 A) {block = ^{NSlog(@"Block A");} }else{ block = ^{ NSLog(@"Block B"); } } block()上面這段代碼的問題在于,這兩個塊只在相應的 if 或 else 語句范圍內有效,離開了相應的范圍后,編譯器有可能覆寫塊所在的內存。這樣的代碼可以編譯,但在運行時可能出錯。書中提出,這里可以使用 copy 操作將塊拷貝到堆上,這樣一來,塊可以在定義它的范圍外使用。正確寫法如下:
void (^block)(); if(條件 A) {block = [^{NSlog(@"Block A");} copy]; }else{ block = [^{ NSLog(@"Block B"); } copy]; } block()書中沒有提及此處是否開啟了 ARC;我將上述代碼編寫在 C 文件中,使用 Clang 將之轉化為 cpp 實現后,發現這里是個棧塊;而按照上一條的說法,開啟 ARC 后,這里將會被編譯為全局塊。那么在開啟 ARC 的條件下,這段代碼是否有問題呢?答案是沒有,因為原來的代碼的問題在于塊的內存在棧中,而開啟 ARC 下,塊編譯為全局塊,不存在這個問題。
在?Objective-C Blocks Quiz?的 Example C中,提到:
That’s correct. Since the block doesn’t capture any variables in its closure, it doesn’t need any state set up at runtime. it gets compiled as an NSGlobalBlock. It’s neither on the stack nor the heap, but part of the code segment, like any C function. This works both with and without ARC.
2.使用了外圍變量的話,若開啟了 ARC,則只會被編譯為堆塊,內存是分配在堆上的;若沒有開啟 ARC,函數中定義的 Block 將會編譯為棧塊(如今除了舊項目很少有不開啟 ARC 的吧)。
總結下:開啟 ARC 的條件下,將不會有棧塊,這樣可以省去不少麻煩,但是?Objective-C Blocks Quiz?的最后也提到LLVM的一位維護者說:
We consider this to be a compiler bug, and it has been fixed for months in the open-source clang repository. What that means for any hypothetical future Xcode release, I cannot say. :)
保險點,開啟 ARC。
生命周期
棧塊,顧名思義,離開了定義它的函數范圍就被收回了;堆塊,就像普通的對象一樣采用引用計數機制;全局塊,在應用的整個生命周期都存在。
變量捕獲 + 循環引用
在聲明塊的范圍內,所有變量都可以被塊捕獲(就是可以使用)。默認情況下,不可以在塊里修改外圍的變量,因為塊拷貝了一份變量到它的內存中,對于對象則是拷貝了對象的地址;若想修改,需要在外圍變量前面添加修飾符?__block。是否添加?__block?修飾符,源代碼會有很大不同,可以在唐巧的博客里看到。另外,Block 最著名的問題就是循環引用,就是由于互相保持著對方的引用,所以 ARC 拿這倆沒辦法,將會一直存在;復雜一點的,多個對象對其他對象的引用形成了一個圈,ARC 也是沒辦法。一般的解決辦法是,在形成的引用圈的一處使用弱引用,這樣就有機會打破強引用圈。
常見的循環引用陷阱是在類中定義了塊變量,然后在塊中使用了類實例的屬性。而在訪問屬性的同時,塊實質上隱式地捕獲了當前實例,這樣一來就造成了循環引用。
在 Objective-C 中,解決方法通常如下:
又或者使用?typeof,不過這種高級技巧,可讀性就不敢保證了。今天這條微博引起了一些討論:
是否會捕獲 self
這個代碼的關鍵在于?typeof(self),因為它在塊的內部出現,那么塊是否捕獲了?self呢?答案是不會,因為?typeof是個編譯符號,在編譯期間起作用,而不是運行時,因此這么寫不會造成循環引用的問題。
在 Swift 中,針對這個問題有了更加優雅的解決方案:捕獲列表(Capture List)。在閉包參數前添加列表,從屬關系以及捕獲對象成對為一組值,多組值用「,」隔開。
無參數:
有參數:
var someClosure: (Int, String) -> String = {[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) in //closure body }?
轉載于:https://www.cnblogs.com/rosee-1224/p/5167879.html
總結
以上是生活随笔為你收集整理的Block(Closure) Tips的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android listview ite
- 下一篇: 【概率与期望】[UVA11021]Tri