日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

iOS底層實例解析Swift閉包及OC閉包_IOS

作者:Yakamoz ? 更新時間: 2022-12-13 編程語言

基礎

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

欄目分類
最近更新