欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

iOS底層實(shí)例解析Swift閉包及OC閉包

 更新時(shí)間:2022年11月15日 10:41:52   作者:Yakamoz  
這篇文章主要為大家介紹了iOS底層實(shí)例解析Swift閉包及OC閉包,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

基礎(chǔ)

Block是?個(gè)自包含的(捕獲了上下?的常量或者是變量的)函數(shù)代碼塊,可以在代碼中被傳遞和使用。

全局和嵌套函數(shù)實(shí)際上也是特殊的閉包,閉包采用如下三種形式之一:

  • 全局函數(shù)是一個(gè)有名字但不會捕獲任何值的閉包
  • 嵌套函數(shù)是一個(gè)有名字并可以捕獲其封閉函數(shù)域內(nèi)值的閉包
  • 閉包表達(dá)式是一個(gè)利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包

OC-Block

分類

NSGlobalBlock

  • 位于全局區(qū)
  • 在Block內(nèi)部不使用外部變量,或者只使用靜態(tài)變量和全局變量

NSMallocBlock

  • 位于堆區(qū)
  • 被強(qiáng)持有
  • 在Block內(nèi)部使用局部變量或OC屬性,可以賦值給強(qiáng)引用/copy修飾的變量

NSStackBlock

  • 位于棧區(qū)
  • 沒有被強(qiáng)持有
  • 在Block內(nèi)部使用局部變量或OC屬性,不能賦值給強(qiáng)引用/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>

下面重點(diǎn)介紹堆Block。

NSMallocBlock

Block拷貝到堆Block的時(shí)機(jī):

  • 手動copy
  • Block作為返回值
  • 被強(qiáng)引用/copy修飾
  • 系統(tǒng)API包含using Block

所以總結(jié)一下堆Block判斷依據(jù):

  • Block內(nèi)部有沒有使用外部變量
  • 使用的變量類型?局部變量/OC屬性/全局變量/靜態(tài)變量
  • 有沒有被強(qiáng)引用/copy修飾

源碼探究

我們創(chuàng)建一個(gè)捕獲了局部變量的block

#import <Foundation/Foundation.h>
void test() {
    int a = 10;
    void(^Malloc)(void) = ^{
        NSLog(@"%d",a);
    };
}

執(zhí)行clang -rewrite-objc main.m -o main.cpp命令,查看main.cpp文件可以看到Malloc閉包的結(jié)構(gòu)如下。

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
	// 內(nèi)部存儲了變量a
  int a;
	/// 初始化函數(shù)。包含三個(gè)參數(shù)
	// - Parameters:
  ///   - fp: 函數(shù)指針
  ///   - 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;
  }
};
// 創(chuàng)建Malloc閉包,傳入?yún)?shù)如下
// 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實(shí)現(xiàn)如下
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
			NSLog(···);
    }

打開llvm可以看到,該block原本是在棧上,調(diào)用了objc_retainBlock方法,而在該方法中實(shí)際調(diào)用了_Block_copy方法。

在Block.h的源碼中可以找到_Block_copy方法,其官方注釋是“創(chuàng)建一個(gè)基于堆的Block副本,或者簡單地添加一個(gè)對現(xiàn)有Block的引用。”,從而將這個(gè)棧block拷貝到了堆上,下面我們根據(jù)該方法的源碼來探究一下堆Block的原理。(只截取重點(diǎn)代碼)

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;
		···
		// 類型強(qiáng)轉(zhuǎn)為Block_layout
    aBlock = (struct Block_layout *)arg;
		···
    // Its a stack block.  Make a copy.
		// 分配內(nèi)存
		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重新標(biāo)記為Malloc Block
		result->isa = _NSConcreteMallocBlock;
		_Block_call_copy_helper(result, aBlock);
		return result;
}

Block底層結(jié)構(gòu)為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;
};

總結(jié):

Block在運(yùn)行時(shí)才會被copy,在堆上開辟內(nèi)存空間。

循環(huán)引用

解決方案

__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();
}
// 可以看到傳入的第三個(gè)參數(shù),是__Block_byref_a_0結(jié)構(gòu)體類型的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結(jié)構(gòu)體中存儲的變量也是__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結(jié)構(gòu)體
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding; // 指針指向原始值
 int __flags;
 int __size;
 int a; // 值拷貝存儲
};

總結(jié) __block 原理

  • 創(chuàng)建__Block_byref_a_0 結(jié)構(gòu)體
  • 傳給block指針地址
  • block內(nèi)修改的是與原始值同一片的內(nèi)存空間

注意點(diǎn)

根據(jù)上述分析我們可以得出結(jié)論,如果在OC的block中捕獲了沒有加__block 的外部變量,在編譯時(shí)就會將變量值傳入(值拷貝),如果捕獲了加__block 的外部變量,則會獲取到變量指針對應(yīng)的內(nèi)存空間的地址。代碼驗(yàn)證如下

int a = 1;
__block int b = 2;
void(^Malloc)(void) = ^{
    NSLog(@"a,%d",a);
    NSLog(@"b,%d",b);
};
a = 3;
b = 4;
Malloc();
// 輸出結(jié)果如下
// a,1
// b,4

Swift-Closure

  • Swift 的閉包表達(dá)式擁有簡潔的風(fēng)格,并鼓勵(lì)在常見場景中進(jìn)行語法優(yōu)化,主要優(yōu)化如下:
    • 利用上下文推斷參數(shù)類型和返回值類型
    • 隱式返回單表達(dá)式閉包(單表達(dá)式閉包可以省略 return 關(guān)鍵字)
    • 參數(shù)名稱縮寫,可以用0,0,0,1表示
    • 尾隨閉包語法:如果函數(shù)的最后一個(gè)參數(shù)是閉包,則閉包可以寫在形參小括號的外面。為了增強(qiáng)函數(shù)的可讀性。
  • Swift 的閉包是一個(gè)引用類型,驗(yàn)證如下。我們知道Swift的引用類型在創(chuàng)建時(shí)都會調(diào)用swift_allocObject方法
// 未調(diào)用swift_allocObject
let closure1 = { () -> () in
    print("closure1")
}
// 調(diào)用swift_allocObject
let a = 10
let closure2 = { () -> () in
    print("closure2 \(a)")
}

捕獲值

  • 在閉包中如果通過[variable1, variabla2]的形式捕獲外部變量,捕獲到的變量為let類型,即不可變
  • 在閉包中如果直接捕獲外部變量,獲取的是指針,也就是說在閉包內(nèi)修改變量值的話,原始變量也會被改變。
  • 如果捕獲的是指針類型(Class),無論是否用[],在閉包內(nèi)對該變量進(jìn)行修改,都會影響到原始變量

簡單驗(yàn)證如下:

var variable = 10
let closure = { () -> () in
    variable += 1
    print("closure \(variable)")
}
closure() // closure 11
print(variable) // 11

可見直接獲取變量的話,會修改到原始值。

如果改成下面這樣會編譯報(bào)錯(cuò)”可變運(yùn)算符的左側(cè)不可變”

var variable = 10
let closure = { [variable] () -> () in
    variable += 1
    print("closure \(variable)")
}
closure()
print(variable)

捕獲指針類型驗(yàn)證

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

以上就是iOS底層實(shí)例解析Swift閉包及OC閉包的詳細(xì)內(nèi)容,更多關(guān)于iOS底層Swift OC閉包的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • iOS如何將UIButton中的圖片與文字上下對齊詳解

    iOS如何將UIButton中的圖片與文字上下對齊詳解

    對于UIButton實(shí)現(xiàn)上顯示圖片,下顯示文字這個(gè)需求估計(jì)各位iOS開發(fā)者們都不陌生,所以下面這篇文章主要給大家介紹了關(guān)于iOS如何將UIButton中圖片與文字上下對齊的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-10-10
  • iOS基于UIScrollView實(shí)現(xiàn)滑動引導(dǎo)頁

    iOS基于UIScrollView實(shí)現(xiàn)滑動引導(dǎo)頁

    這篇文章主要為大家詳細(xì)介紹了iOS基于UIScrollView實(shí)現(xiàn)滑動引導(dǎo)頁的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • 詳解iOS中集成ijkplayer視頻直播框架

    詳解iOS中集成ijkplayer視頻直播框架

    ijkplayer 是一款做視頻直播的框架, 基于ffmpeg, 支持Android和iOS,本文將詳細(xì)的講一下在iOS中如何集成ijkplayer, 即便以前從沒有接觸過,按著下面做也可以集成成功!下面跟著小編一起來看下吧
    2016-12-12
  • iOS?xcconfig編寫示例教程

    iOS?xcconfig編寫示例教程

    這篇文章主要為大家介紹了iOS?xcconfig編寫示例教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • iOS實(shí)現(xiàn)簡單的頭部縮放功能

    iOS實(shí)現(xiàn)簡單的頭部縮放功能

    這篇文章主要介紹了iOS 簡單的頭部縮放效果,頭部伴隨模糊效果放大縮小,并在一定位置時(shí)懸停充當(dāng)導(dǎo)航欄,本文給大家提供實(shí)現(xiàn)思路,需要的朋友可以參考下
    2018-08-08
  • iOS如何定義名為任意的變量詳解

    iOS如何定義名為任意的變量詳解

    這篇文章主要給大家介紹了關(guān)于iOS如何定義名為任意的變量的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對各位iOS開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-05-05
  • iOS開發(fā)添加新手引導(dǎo)效果

    iOS開發(fā)添加新手引導(dǎo)效果

    這篇文章主要介紹了iOS開發(fā)添加新手引導(dǎo)效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05
  • iOS小組件開發(fā)之WidgetKit功能講解

    iOS小組件開發(fā)之WidgetKit功能講解

    這篇文章主要為大家介紹了iOS小組件開發(fā)WidgetKit功能講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • IOS實(shí)現(xiàn)百度地圖自定義大頭針和氣泡樣式

    IOS實(shí)現(xiàn)百度地圖自定義大頭針和氣泡樣式

    這篇文章主要介紹了 IOS百度地圖自定義大頭針和氣泡的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下
    2016-12-12
  • iOS開發(fā)教程之登錄與訪客的邏輯實(shí)現(xiàn)

    iOS開發(fā)教程之登錄與訪客的邏輯實(shí)現(xiàn)

    這篇文章主要給大家介紹了關(guān)于iOS開發(fā)教程之登錄與訪客的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-04-04

最新評論