<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          iOS愛上底層-Block實現(xiàn)與原理

          共 5666字,需瀏覽 12分鐘

           ·

          2021-11-13 20:09


          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????


          作者丨淡墨飄香

          來源:掘金
          鏈接:

          https://juejin.im/post/5eba9de86fb9a04351050ea7

          前言

          很多人在面試的時候都會被問到Block,那么Block分為哪幾種類型呢?其實Block共有6種類型,其中三種常用級別,分別是:_NSConcreteGlobalBlock _NSConcreteStackBlock _NSConcreteMallocBlock,三種系統(tǒng)級別 ,分別是_NSConcreteAutoBlock _NSConcreteFinalizingBlock _NSConcreteWeakBlockVariable,本次只介紹3種常用類型。

          Block常見的三種類型

          • 全局Block(__NSGlobalBlock__)


          • 堆Block(__NSMallocBlock__)


          • 棧Block(__NSStackBlock__)


          Block的本質(zhì)是什么?


          把左邊OC(左邊)的代碼編譯成C++(右邊),我們會發(fā)現(xiàn)原本的block在C++里面被拆解成一個叫__main_block_impl_0的構(gòu)造函數(shù),我們在往上看可以發(fā)現(xiàn),它其實就是一個結(jié)構(gòu)體對象。而這個構(gòu)造函數(shù)傳了兩個值__main_block_func_0__main_block_desc_0_DATA__main_block_func_0:就是Block所執(zhí)行的內(nèi)容,在C++底層則變成了一個函數(shù)。__main_block_desc_0_DATA:有兩個成員:reservedBlock_size

          Block是如何捕獲外界變量的?


          我們創(chuàng)建一個局部變量a=10,然后看C++代碼,可以發(fā)現(xiàn)在__main_block_impl_0結(jié)構(gòu)體中創(chuàng)建了一個屬性int a,并且在函數(shù)實現(xiàn)中創(chuàng)建了一個臨時變量a,將結(jié)構(gòu)體中的屬性賦值給他。由于這次拷貝是值拷貝,所以在函數(shù)里面不能對當(dāng)前的屬性進(jìn)行修改。為了能夠改變a的值,要加上__block,如下圖:




          我們可以發(fā)現(xiàn)__block修飾的臨時變量在C++中變成了一個結(jié)構(gòu)體__Block_byref_a_0。并且在調(diào)用函數(shù)的時候,傳的是a的指針,并且在函數(shù)的實現(xiàn)中,__Block_byref_a_0 *a = __cself->a則是進(jìn)行一次指針拷貝,所以用__block修飾的變量可以在Block內(nèi)部進(jìn)行修改。

          解決Block循環(huán)引用的幾種方式

              //循環(huán)引用
          self.name = @"1234";
          self.block = ^{
          NSLog(@"%@",self.name);
          };
          self.block();

          很多人都知道Block會引起循環(huán)引用,如同上面這段代碼,當(dāng)self持有block,block持有self,就產(chǎn)生了循環(huán)引用。接下來就介紹3種解決循環(huán)引用的方式
          1、__block:我們只需要在self.name的下面創(chuàng)建一個中介者vc(__block ViewController *vc = self),然后將block里面的self.name替換成vc.name,用完之后將vc置為nil即可。(俗稱中介者模式)

              // 循環(huán)引用
          self.name = @"1234";
          __block ViewController *vc = self;
          self.block = ^{
          NSLog(@"%@",vc.name);
          };
          self.block();

          2、傳參:我們只需要將當(dāng)前的self當(dāng)做參數(shù)傳入到block里面,block內(nèi)部會創(chuàng)建一個臨時變量vc,此時我們就打破了互相持有,就會解決循環(huán)引用的問題了

              // 循環(huán)引用
          self.name = @"1234";
          self.block = ^(ViewController *vc){
          NSLog(@"%@",vc.name);
          };
          self.block(self);

          3、weak and strong:這種方式也是最多人用的,既創(chuàng)建weak,但是有一個問題,如果添加一個延遲執(zhí)行,weakSelf就會提前釋放,導(dǎo)致訪問不到外界變量,所以我們又需要在blcok里面strong一下

          // 循環(huán)引用
          self.name = @"1234";
          __weak typeof(self) weakSelf = self;
          self.block = ^{
          __strong typeof(weakSelf) strongSelf = weakSelf;
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          NSLog(@"%@",strongSelf.name);
          });
          };
          self.block();

          Block打破循環(huán)引的原理

          在匯編里面,Block會掉用一個叫_Block_object_assign的api。而解決循環(huán)引用的關(guān)鍵之處也在下面的代碼中。

          void _Block_object_assign(void *destArg, const void *object, const int flags) {
          const void **dest = (const void **)destArg;
          switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_OBJECT:
          _Block_retain_object(object);
          *dest = object;
          break;

          case BLOCK_FIELD_IS_BLOCK:
          *dest = _Block_copy(object);
          break;

          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
          *dest = _Block_byref_copy(object);
          break;

          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
          *dest = object;
          break;

          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
          *dest = object;
          break;

          default:
          break;
          }
          }

          __weak為例。我們?yōu)榱私鉀Q循環(huán)引用,通常都會寫上這樣一句代碼:__weak typeof(self) weakSelf = self;,將self加入到弱引用技術(shù)表,但是這并不足以解釋打破循環(huán)引用的原因。而是weakSelfself并不是同一個點,他們指向的空間都是同一個。而最關(guān)鍵的是這一行代碼const void **dest = (const void **)destArg;,他copy的是weakSelf的指針地址,而不是self,所以weakSelf被釋放的時候,不會影響到self。

          Block是如何從棧到堆?

          void *_Block_copy(const void *arg) {
          struct Block_layout *aBlock;

          if (!arg) return NULL;

          // The following would be better done as a switch statement
          aBlock = (struct Block_layout *)arg;
          if (aBlock->flags & BLOCK_NEEDS_FREE) {
          // latches on high
          latching_incr_int(&aBlock->flags);
          return aBlock;
          }
          else if (aBlock->flags & BLOCK_IS_GLOBAL) {
          return aBlock;
          }
          else {
          // Its a stack block. Make a copy.
          struct Block_layout *result =
          (struct Block_layout *)malloc(aBlock->descriptor->size);
          if (!result) return NULL;
          memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
          #if __has_feature(ptrauth_calls)
          // Resign the invoke pointer as it uses address authentication.
          result->invoke = aBlock->invoke;
          #endif
          // reset refcount
          result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
          result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
          _Block_call_copy_helper(result, aBlock);
          // Set isa last so memory analysis tools see a fully-initialized object.
          result->isa = _NSConcreteMallocBlock;
          return result;
          }
          }

          這里就是當(dāng)前Block從棧->堆的過程。1、aBlock->flags & BLOCK_NEEDS_FREE會判斷當(dāng)前Block的引用計數(shù)器,因為block的引用計數(shù)器是不受runtime下層處理的,所以它由自己來進(jìn)行管理。而且這里的引用計數(shù)器是+2,而不是+1,因為+1會對別的屬性有影響,所以這里是+2(如下)。

          static int32_t latching_incr_int(volatile int32_t *where) {
          while (1) {
          int32_t old_value = *where;
          if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
          return BLOCK_REFCOUNT_MASK;
          }
          if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
          return old_value+2;
          }
          }
          }

          2、aBlock->flags & BLOCK_IS_GLOBAL判斷當(dāng)前的Block是否為全局變量,是的話就直接返回
          3、else就是將block復(fù)制到棧中,首先會創(chuàng)建一個新的結(jié)構(gòu)體result,然后將舊的Block全部拷貝到新的Block中,然后isa就被標(biāo)記為_NSConcreteMallocBlock

          總結(jié)

          1、解決block循環(huán)引用的思路就是中介者模式。
          2、Block的本質(zhì)就是結(jié)構(gòu)體
          3、當(dāng)Block捕獲到外界變量時,他就會從全局block變成堆block

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點這里好文分享給更多人↓↓

          瀏覽 17
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天日日干 | 懂色av一区二区 动漫操逼视频网站 | 色男人男人天堂 | 成人网站视频大香蕉 | 蘑菇视频网站入口色鸡 |