網站首頁 編程語言 正文
基礎
Block是?個自包含的(捕獲了上下?的常量或者是變量的)函數代碼塊,可以在代碼中被傳遞和使用。
全局和嵌套函數實際上也是特殊的閉包,閉包采用如下三種形式之一:
- 全局函數是一個有名字但不會捕獲任何值的閉包
- 嵌套函數是一個有名字并可以捕獲其封閉函數域內值的閉包
- 閉包表達式是一個利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包
OC-Block
分類
NSGlobalBlock
- 位于全局區
- 在Block內部不使用外部變量,或者只使用靜態變量和全局變量
NSMallocBlock
- 位于堆區
- 被強持有
- 在Block內部使用局部變量或OC屬性,可以賦值給強引用/copy修飾的變量
NSStackBlock
- 位于棧區
- 沒有被強持有
- 在Block內部使用局部變量或OC屬性,不能賦值給強引用/copy修飾的變量
如下簡單demo code所示
int a = 10; // 局部變量 void(^Global)(void) = ^{ NSLog(@"Global"); }; void(^Malloc)(void) = ^{ NSLog(@"Malloc,%d",a); }; void(^__weak Stack)(void) = ^{ NSLog(@"Stack,%d",a); }; NSLog(@"%@",Global); // <__NSGlobalBlock__: 0x101aa80b0> NSLog(@"%@",Malloc); // <__NSMallocBlock__: 0x600003187900> NSLog(@"%@",Stack); // <__NSStackBlock__: 0x7ff7b12c22f0>
下面重點介紹堆Block。
NSMallocBlock
Block拷貝到堆Block的時機:
- 手動copy
- Block作為返回值
- 被強引用/copy修飾
- 系統API包含using Block
所以總結一下堆Block判斷依據:
- Block內部有沒有使用外部變量
- 使用的變量類型?局部變量/OC屬性/全局變量/靜態變量
- 有沒有被強引用/copy修飾
源碼探究
我們創建一個捕獲了局部變量的block
#import <Foundation/Foundation.h> void test() { int a = 10; void(^Malloc)(void) = ^{ NSLog(@"%d",a); }; }
執行clang -rewrite-objc main.m -o main.cpp
命令,查看main.cpp文件可以看到Malloc閉包的結構如下。
struct __test_block_impl_0 { struct __block_impl impl; struct __test_block_desc_0* Desc; // 內部存儲了變量a int a; /// 初始化函數。包含三個參數 // - Parameters: /// - fp: 函數指針 /// - desc: 描述 /// - _a: flag __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; // 創建Malloc閉包,傳入參數如下 // fp: (void *)__test_block_func_0 // desc: &__test_block_desc_0_DATA // _a: 變量a的值(值拷貝) void(*Malloc)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a)); // __test_block_func_0實現如下 static void __test_block_func_0(struct __test_block_impl_0 *__cself) { int a = __cself->a; // bound by copy NSLog(···); }
打開llvm可以看到,該block原本是在棧上,調用了objc_retainBlock
方法,而在該方法中實際調用了_Block_copy
方法。
在Block.h的源碼中可以找到_Block_copy
方法,其官方注釋是“創建一個基于堆的Block副本,或者簡單地添加一個對現有Block的引用。”,從而將這個棧block拷貝到了堆上,下面我們根據該方法的源碼來探究一下堆Block的原理。(只截取重點代碼)
void *_Block_copy(const void *arg) { return _Block_copy_internal(arg, true); } static void *_Block_copy_internal(const void *arg, const bool wantsOne) { struct Block_layout *aBlock; ··· // 類型強轉為Block_layout aBlock = (struct Block_layout *)arg; ··· // Its a stack block. Make a copy. // 分配內存 struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return NULL; memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1 // isa重新標記為Malloc Block result->isa = _NSConcreteMallocBlock; _Block_call_copy_helper(result, aBlock); return result; }
Block底層結構為Block_layout
struct Block_layout { void *isa; // isa指針 volatile int32_t flags; // contains ref count int32_t reserved; // 保留位 void (*invoke)(void *, ...); // call out funtion struct Block_descriptor_1 *descriptor; };
總結:
Block在運行時才會被copy,在堆上開辟內存空間。
循環引用
解決方案
__weak
+ __strong
思路: 在block里短暫持有self的生命周期。(weak
自動置空)
self.name = @"YK"; __weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(self) strongSelf = weakSelf; strongSelf.callFunc(); };
__block
思路: 值拷貝。(手動置空)
我們有如下代碼,生成cpp文件看一下
#import <Foundation/Foundation.h> void test() { __block int a = 10; void(^Malloc)(void) = ^{ a++; NSLog(@"%d",a); }; Malloc(); }
// 可以看到傳入的第三個參數,是__Block_byref_a_0結構體類型的a變量地址,而不是上面講過的直接存儲int類型 void(*Malloc)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); // __test_block_impl_0結構體中存儲的變量也是__Block_byref_a_0類型 struct __test_block_impl_0 { struct __block_impl impl; struct __test_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; // 初始化__Block_byref_a_0如下 __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0, (__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10}; // __Block_byref_a_0結構體 struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; // 指針指向原始值 int __flags; int __size; int a; // 值拷貝存儲 };
總結 __block
原理
- 創建
__Block_byref_a_0
結構體 - 傳給block指針地址
- block內修改的是與原始值同一片的內存空間
注意點
根據上述分析我們可以得出結論,如果在OC的block中捕獲了沒有加__block
的外部變量,在編譯時就會將變量值傳入(值拷貝),如果捕獲了加__block
的外部變量,則會獲取到變量指針對應的內存空間的地址。代碼驗證如下
int a = 1; __block int b = 2; void(^Malloc)(void) = ^{ NSLog(@"a,%d",a); NSLog(@"b,%d",b); }; a = 3; b = 4; Malloc(); // 輸出結果如下 // a,1 // b,4
Swift-Closure
- Swift 的閉包表達式擁有簡潔的風格,并鼓勵在常見場景中進行語法優化,主要優化如下:
- 利用上下文推斷參數類型和返回值類型
- 隱式返回單表達式閉包(單表達式閉包可以省略?
return
?關鍵字) - 參數名稱縮寫,可以用0,0,0,1表示
- 尾隨閉包語法:如果函數的最后一個參數是閉包,則閉包可以寫在形參小括號的外面。為了增強函數的可讀性。
- Swift 的閉包是一個引用類型,驗證如下。我們知道Swift的引用類型在創建時都會調用
swift_allocObject
方法
// 未調用swift_allocObject let closure1 = { () -> () in print("closure1") } // 調用swift_allocObject let a = 10 let closure2 = { () -> () in print("closure2 \(a)") }
捕獲值
- 在閉包中如果通過
[variable1, variabla2]
的形式捕獲外部變量,捕獲到的變量為let
類型,即不可變 - 在閉包中如果直接捕獲外部變量,獲取的是指針,也就是說在閉包內修改變量值的話,原始變量也會被改變。
- 如果捕獲的是指針類型(
Class
),無論是否用[],在閉包內對該變量進行修改,都會影響到原始變量
簡單驗證如下:
var variable = 10 let closure = { () -> () in variable += 1 print("closure \(variable)") } closure() // closure 11 print(variable) // 11
可見直接獲取變量的話,會修改到原始值。
如果改成下面這樣會編譯報錯”可變運算符的左側不可變”
var variable = 10 let closure = { [variable] () -> () in variable += 1 print("closure \(variable)") } closure() print(variable)
捕獲指針類型驗證
class YKClass { var name = "old" } let demoS = YKStruct() let demoC = YKClass() let closure1 = { [demoC] () -> () in demoC.name = "new" print("closure1 \(demoC.name)") } closure1() // closure1 new print(demoC.name) // new let closure2 = { () -> () in demoC.name = "new2" print("closure2 \(demoC.name)") } closure2() // closure2 new2 print(demoC.name) // new2
原文鏈接:https://juejin.cn/post/7165696168160067620
相關推薦
- 2022-07-22 Maven項目編譯運行后target/classes目錄下沒有xml和properties文件
- 2022-04-16 python修改全局變量可以不加global嗎?_python
- 2022-11-04 go-cqhttp環境配置及安裝過程_Golang
- 2022-06-19 C++深入探究引用的本質與意義_C 語言
- 2023-07-14 如何限制請求的并發數量
- 2022-08-26 Python?Decorator裝飾器的創建方法及常用場景分析_python
- 2023-03-16 Android虛擬機Dalvik和ART科普_Android
- 2023-01-15 利用Python實現讀取Word文檔里的Excel附件_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支