中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

『libextobjc』Objctive-C 協(xié)議的默認(rèn)實(shí)現(xiàn)

2018-07-20    來源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬Linux鏡像隨意使用

繼續(xù)閱讀 libextobjc 的源碼,看到一個(gè)非常有趣的實(shí)現(xiàn)—— Objective-C 的 protocol 默認(rèn)實(shí)現(xiàn)。當(dāng)然,這不比 Swift 的 extension 默認(rèn)實(shí)現(xiàn),Objective-C 在這方面沒有 Swift 強(qiáng)大,并不能完全的實(shí)現(xiàn) POP,但是這不妨給我們提供一種思路。

首先,列舉一下當(dāng)面對(duì)這個(gè)問題時(shí),都有哪些疑問:

  • 會(huì)用到方法注入,但是什么時(shí)候注入?
  • 以什么形式獲取默認(rèn)實(shí)現(xiàn)的 SEL 與 IMP?
  • 怎樣減少性能開銷?

然后,我們來一起看看源碼的實(shí)現(xiàn)思路( 因?yàn)楦⒅貙?shí)現(xiàn)步驟和思想,所以在文章中不會(huì)出現(xiàn)大量的源碼,大家最好自行對(duì)照源碼進(jìn)行閱讀 )。

用法

// MyProtocol.h

@protocol MyProtocol

@concrete
- (void)runTest;

@end
  
  
// MyProtocol.m
@concreteprotocol(MyProtocol)

- (void)runTest {
    NSLog(@"%s", __FUNCTION__);
}

@end

在 EXTConcreteProtocol.h 中能找到 concrete 與 concreteprotocol 這對(duì)宏。 concrete 很簡(jiǎn)單,也很巧妙,它就是用于修飾 protocol 方法的 optional 。用 concrete 宏的好處有兩個(gè):

  1. 沿用 optional 的作用,防止遵循該 protocol 的類因?yàn)闆]實(shí)現(xiàn)代理方法,而報(bào)警告。
  2. 語義更加清晰,能直接表情這以下的方法是已經(jīng)默認(rèn)實(shí)現(xiàn)了的。

然后來看看 concreteprotocol 的定義,首先, concreteprotocol 會(huì)將傳入的 protocol name 與字符串 _ProtocolMethodContainer 拼接,即 MyProtocol_ProtocolMethodContainer 。因?yàn)樵创a不易閱讀,我將它簡(jiǎn)化了一下(去掉注釋與報(bào)錯(cuò)信息):

被框住的代碼就是 concreteprotocol(MyProtocol) 展開的部分。這段代碼能解答之前我們的兩個(gè)疑問:

  1. 什么時(shí)候注入:無論是在 +load 還是在被 __attribute__((constructor)) 修飾的函數(shù)中,至少能保證注入是發(fā)生在 main 函數(shù)之前的(關(guān)于 +load 與 __attribute__((constructor)) 的執(zhí)行順序,請(qǐng)參考我之前的文章: 『Apple API』/ / attribute / )。
  2. 以怎樣的形式獲取 SEL 與 IMP:這個(gè)宏直接為 protocol 擴(kuò)展了一個(gè)容器類,所以默認(rèn)實(shí)現(xiàn)的方法都是存在這個(gè)類中的,之后要進(jìn)行注入,方法的 SEL 與 IMP 也應(yīng)該是從這個(gè)容器類中進(jìn)行獲取。

所以,根據(jù)調(diào)用順序,我們接下來分成兩步來分析整個(gè)實(shí)現(xiàn)。

ext_addConcreteProtocol

首先被調(diào)用的是 +load 中的 ext_addConcreteProtocol 函數(shù)。這個(gè)函數(shù)接收兩個(gè)參數(shù):當(dāng)前的 protocol 對(duì)象,以及對(duì)應(yīng)的容器類。實(shí)現(xiàn)中又調(diào)用了另外一個(gè):

BOOL ext_addConcreteProtocol (Protocol *protocol, Class containerClass) {
    return ext_loadSpecialProtocol(protocol, ^(Class destinationClass){
        ext_injectConcreteProtocol(protocol, containerClass, destinationClass);
    });
}

ext_loadSpecialProtocol 函數(shù)接收兩個(gè)參數(shù):當(dāng)前 protocol,以及一個(gè)參數(shù)為 Class destinationClass 的 block。我們先不看這個(gè) block 的具體實(shí)現(xiàn),先來看看 ext_loadSpecialProtocol 都做了些什么。

ext_loadSpecialProtocol

同樣,我簡(jiǎn)化了 ext_loadSpecialProtocol 的實(shí)現(xiàn),代碼大多用注釋描述來代替:

// protocol: 當(dāng)前默認(rèn)實(shí)現(xiàn)的 protocol
// void (^injectionBehavior)(Class destinationClass): 傳入的 block

BOOL ext_loadSpecialProtocol (Protocol *protocol, void (^injectionBehavior)(Class destinationClass)) {
    // 判斷默認(rèn)實(shí)現(xiàn)的 protocol 個(gè)數(shù)是否大于 SIZE_MAX
    // specialProtocolCount 即默認(rèn)實(shí)現(xiàn)的 protocol 的計(jì)數(shù)
    if (specialProtocolCount == SIZE_MAX) {
        return NO;
    }

    // specialProtocolCapacity 為數(shù)組總?cè)萘?    if (specialProtocolCount >= specialProtocolCapacity) {
        // 如果未超過 SIZE_MAX 則進(jìn)行動(dòng)態(tài)擴(kuò)容
        // 將動(dòng)態(tài)擴(kuò)容后的數(shù)組頭指針交給 specialProtocols
    }

    // 將參數(shù)的 block copy 到堆,并賦值給 copiedBlock
    ext_specialProtocolInjectionBlock copiedBlock = [injectionBehavior copy];

    // 將 protocol, block, 和 NO 組裝成 struct
    // 然后將 struct 追加到數(shù)組中
    specialProtocols[specialProtocolCount] = (EXTSpecialProtocol){
        .protocol = protocol,
        .injectionBlock = (__bridge_retained void *)copiedBlock,
        .ready = NO
    };

    // 默認(rèn)實(shí)現(xiàn) protocol 的個(gè)數(shù)自增
    ++specialProtocolCount;

    // success!
    return YES;
}

所以,整個(gè)函數(shù)走下來,作用就是將 {protocol, block} 追加到數(shù)組中。

也就是說,在執(zhí)行 __attribute__((constructor)) 修飾的方法以前,所有默認(rèn)實(shí)現(xiàn)的 protocol,都會(huì)被加到這個(gè)數(shù)組中。

接下來,我們來看 +load 之后做了什么。

ext_loadConcreteProtocol

同樣, ext_loadConcreteProtocol 內(nèi)部也調(diào)用了另一個(gè)函數(shù):

void ext_loadConcreteProtocol (Protocol *protocol) {
    ext_specialProtocolReadyForInjection(protocol);
}

整個(gè)函數(shù)的實(shí)現(xiàn)很簡(jiǎn)單,用于確保 +load 中加入數(shù)組的所有 protocol 都能找到:

void ext_specialProtocolReadyForInjection (Protocol *protocol) {
  // 循環(huán)遍歷數(shù)組
  for (size_t i = 0;i < specialProtocolCount;++i) {
    // 如果數(shù)組的 protocol 是當(dāng)前 protocl
    if (specialProtocols[i].protocol == protocol) {
      // 并且這個(gè) protocol 還未被遍歷過(也就是 ready 標(biāo)識(shí))
      if (!specialProtocols[i].ready) {
        // 則進(jìn)行標(biāo)記
        specialProtocols[i].ready = YES;
        // ready 標(biāo)識(shí)計(jì)數(shù)自增
        // !!! 當(dāng)所有的 protocol 均 ready 之后
        // 再調(diào)用 ext_injectSpecialProtocols
        if (++specialProtocolsReady == specialProtocolCount)
          ext_injectSpecialProtocols();
      }

      break;
    }
  }
}

在 ext_specialProtocolReadyForInjection 的實(shí)現(xiàn)中, if (++specialProtocolsReady == specialProtocolCount) 這個(gè)判斷比較有趣,它能回答我們的第三個(gè)問題(如何節(jié)省開銷):

  1. +load 與 __attribute__((constructor)) 的優(yōu)先級(jí)能使得所有 protocol 加入完成以后,再進(jìn)行處理。
  2. ready 計(jì)數(shù) specialProtocolsReady 使得所有默認(rèn)實(shí)現(xiàn)均判斷無誤后,再進(jìn)行注入。

到此,好像已經(jīng)完事具備,馬上就可以進(jìn)行注入了。但蒼天饒過誰,我們還有很重要的一個(gè)問題沒有考慮。

ext_injectSpecialProtocols

優(yōu)先級(jí)問題:如果 protocolA <ProtocolB> ,也就是 protocolA 遵循 protocolB ,那么誰的優(yōu)先級(jí)更高呢?除此之外,如果遵循 protocol 的 class,自己也實(shí)現(xiàn)了默認(rèn)方法呢?

這個(gè)問題,在 ext_injectSpecialProtocols 函數(shù)中能得到答案:

static void ext_injectSpecialProtocols (void) {
    
  qsort_b(specialProtocols, specialProtocolCount, sizeof(EXTSpecialProtocol), ^(const void *a, const void *b){
    // 根據(jù) a 是否 comform b,對(duì)整個(gè)數(shù)組進(jìn)行排序 (protocol_conformsToProtocol)
  });

  // 通過 objc_getClassList 獲得所有類列表
  
  // 兩個(gè) for 循環(huán)嵌套
  // 對(duì)類列表以及 protocol 列表進(jìn)行遍歷
  // 如果 class comform protocol
  // 則調(diào)用之前 struct 中的注入 block,進(jìn)行注入
  for (size_t i = 0;i < specialProtocolCount;++i) {
    Protocol *protocol = specialProtocols[i].protocol;
    
    for (unsigned classIndex = 0;classIndex < classCount;++classIndex) {
      Class class = allClasses[classIndex];

      if (!class_conformsToProtocol(class, protocol))
          continue;

      // 遵循 protocol 的 class 即為注入的目標(biāo) class
      injectionBlock(class);
    }
  }
}

所以,整個(gè)方法的任務(wù)也很清晰:

  1. 對(duì) protocol 進(jìn)行優(yōu)先級(jí)排序,給出具體注入的先后順序,防止方法覆蓋或無法注入。
  2. 獲取全部 class 列表。
  3. 兩層循環(huán)遍歷,將 class 與其遵循的 protocol 進(jìn)行匹配。
  4. 調(diào)用 struct 中的 block,并將目標(biāo) class 傳出,進(jìn)行注入。

接下來,終于到了最后一步,來看看 block 中的注入方法的實(shí)現(xiàn)。

ext_injectConcreteProtocol

block 是在 +load 中就已經(jīng)賦值了,而 block 的實(shí)現(xiàn),就是直接調(diào)用了 ext_injectConcreteProtocol 函數(shù):

// 函數(shù)有三個(gè)參數(shù)
// protocol
// containerClass: 實(shí)現(xiàn)了 protocol 方法的 容器類
// class: 兩層循環(huán)嵌套中,找到的要注入的目標(biāo) class
static void ext_injectConcreteProtocol (Protocol *protocol, Class containerClass, Class class) {
    
	// 獲取 容器類 中的實(shí)例方法列表
 	// 獲取 容器類 meta class 中的類方法列表

	// 循環(huán)注入實(shí)例方法
    for (unsigned methodIndex = 0;methodIndex < imethodCount;++methodIndex) {
        // 獲取方法 SEL
      	// 獲取方法 IMP
      	// 判斷 目標(biāo)類 是否存在該方法
      	// 進(jìn)行注入
    }
  	// 循環(huán)注入類方法同理
}

到此,方法注入就已經(jīng)全部完成了。

總結(jié)

面向過程的過完了整個(gè)源碼,從頭再來梳理一下:

  1. 注入實(shí)現(xiàn)思路的重點(diǎn)在于,使用宏為 protocol 擴(kuò)展了一個(gè)容器類。
  2. 容器類中,利用 +load 與 __attribute__((constructor)) 的特性,將注入流程分為了兩個(gè)部分。
  3. 在 +load 中,將 protocol,執(zhí)行注入的 block 打包成 struct,然后將 struct 裝進(jìn)數(shù)組。
  4. 當(dāng)執(zhí)行到 __attribute__((constructor)) 時(shí),也就表示所有類的 +load 都已經(jīng)執(zhí)行過了,再對(duì)數(shù)組進(jìn)行優(yōu)先級(jí)排序。
  5. 排序完成后,兩層循環(huán)嵌套,查找遵循了 protocol 的 class。
  6. 調(diào)用 block 執(zhí)行注入。

 

來自:http://www.saitjr.com/ios/『libextobjc』objctive-c-協(xié)議的默認(rèn)實(shí)現(xiàn).html

 

標(biāo)簽: ssl 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:iOS中實(shí)現(xiàn)一個(gè)支持小數(shù)的星星評(píng)分組件

下一篇:云端的SRE發(fā)展與實(shí)踐