iOS進階面試題(一)

后10題面試題見iOS進階面試題(二)

面試題.png

1.詳情描述一下UIView 與CALayer 的關(guān)系,drawRect 一定會影響性能嗎?UIDynamics 與 UIKit Animation 的最本質(zhì)的區(qū)別是什么?

解答:

Q1. UIView 與CALayer 的關(guān)系?
  1. UIView可以響應(yīng)事件,CALayer不可以。
    • UIKit使用UIResponder作為響應(yīng)對象,來響應(yīng)系統(tǒng)傳遞過來的事件并進行處理。在 UIResponder中定義了處理各種事件和事件傳遞的接口。
      UIApplication、UIViewController、UIView、和所有從UIView派生出來的UIKit類(包括UIWindow)都直接或間接地繼承自UIResponder類。
      CALayer直接繼承 NSObject,并沒有相應(yīng)的處理事件的接口。
  2. UIView 是 CALayer的delegate
    • 在給 UIView 的 Layer 做動畫的時候,View 作為 Layer 的代理,Layer 通過 actionForLayer:forKey:向 View請求相應(yīng)的 action(動畫行為)
  3. UIView主要處理事件,CALayer負責(zé)繪制
  4. 每個 UIView 內(nèi)部都有一個 CALayer 在背后提供內(nèi)容的繪制和顯示,并且 UIView 的尺寸樣式都由內(nèi)部的 Layer 所提供。兩者都有樹狀層級結(jié)構(gòu),layer 內(nèi)部有 SubLayers,View 內(nèi)部有 SubViews.但是 Layer 比 View 多了個AnchorPoint
  5. UIView 和 CALayer 不是線程安全的,并且只能在主線程創(chuàng)建、訪問和銷毀。

下面是拓展知識可以略過

  • 屏幕顯示圖像原理


    屏幕顯示圖像原理.png

通常來說,計算機系統(tǒng)中 CPU、GPU、顯示器是以上面這種方式協(xié)同工作的。CPU 計算好顯示內(nèi)容提交到 GPU,GPU 渲染完成后將渲染結(jié)果放入幀緩沖區(qū),隨后視頻控制器會按照 VSync 信號逐行讀取幀緩沖區(qū)的數(shù)據(jù),經(jīng)過可能的數(shù)模轉(zhuǎn)換傳遞給顯示器顯示。

  • 卡頓產(chǎn)生的原因和解決方案


    卡頓產(chǎn)生的原因.png

    在 VSync 信號到來后,系統(tǒng)圖形服務(wù)會通過 CADisplayLink 等機制通知 App,App 主線程開始在 CPU 中計算顯示內(nèi)容,比如視圖的創(chuàng)建、布局計算、圖片解碼、文本繪制等。隨后 CPU 會將計算好的內(nèi)容提交到 GPU 去,由 GPU 進行變換、合成、渲染。隨后 GPU 會把渲染結(jié)果提交到幀緩沖區(qū)去,等待下一次 VSync 信號到來時顯示到屏幕上。由于垂直同步的機制,如果在一個 VSync 時間內(nèi),CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會被丟棄,等待下一次機會再顯示,而這時顯示屏?xí)A糁暗膬?nèi)容不變。這就是界面卡頓的原因。
    從上面的圖中可以看到,CPU 和 GPU 不論哪個阻礙了顯示流程,都會造成掉幀現(xiàn)象。所以開發(fā)時,也需要分別對 CPU 和 GPU 壓力進行評估和優(yōu)化。

  • GPU 資源消耗原因和解決方案
    相對于 CPU 來說,GPU 能干的事情比較單一:接收提交的紋理(Texture)和頂點描述(三角形),應(yīng)用變換(transform)、混合并渲染,然后輸出到屏幕上。通常你所能看到的內(nèi)容,主要也就是紋理(圖片)和形狀(三角模擬的矢量圖形)兩類。

Q2. drawRect 一定會影響性能嗎?

Core Graphics繪制 ,如果對視圖實現(xiàn)了-drawRect:方法,或者CALayerDelegate的-drawLayer:inContext:方法,那么在繪制任何東西之前都會產(chǎn)生一個巨大的性能開銷。為了支持對圖層內(nèi)容的任意繪制,Core Animation必須創(chuàng)建一個內(nèi)存中等大小的寄宿圖片。然后一旦繪制結(jié)束之后,必須把圖片數(shù)據(jù)通過IPC傳到渲染服務(wù)器。在此基礎(chǔ)上,Core Graphics繪制就會變得十分緩慢,所以在一個對性能十分挑剔的場景下這樣做十分不好。

Q3. UIDynamics 與 UIKit Animation 的最本質(zhì)的區(qū)別是什么?

動力系統(tǒng)的引入,并不是替代CoreAnimation,而是對模擬現(xiàn)實世界物體運動的補充,比如,碰撞,重力,懸掛等等。所以說,UIKit動力系統(tǒng)的引入,大大簡化了一些交互動畫(不需要自己實現(xiàn)一些模擬現(xiàn)實世界物理動力系統(tǒng)的動畫),豐富了UI設(shè)計。

1. 如何用 UIImageView 顯示超大分辨率的圖?如果要支持縮放呢?

解答:

  • Q1 一般的顯示超大圖:
    肯定是需要預(yù)先處理的也就是壓縮圖片.

壓縮:壓縮圖片我們希望可以保證壓縮的速度夠快及內(nèi)存消耗的盡可能小。在此感謝github上的OTLargeImageReader的作者,壓縮過程中內(nèi)存控制和速度都很好。

//先從內(nèi)存中查找,查找不到再解碼,避免重復(fù)解碼
UIImage *cacheImage = [self.photoBrowser cacheImageWithPhoto:_photo];
if (cacheImage == nil) {
    //不存在,解碼
    [self.photoBrowser showHUDWithSuperBigPhoto];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        CGSize compressSize = CGSizeMake(XXPhotoCompressPixelMax, XXPhotoCompressPixelMax);
        if (image.size.width > image.size.height) {
            compressSize = CGSizeMake(XXPhotoCompressPixelMax, XXPhotoCompressPixelMax*image.size.height/image.size.width);
        }
        else {
            compressSize = CGSizeMake(XXPhotoCompressPixelMax*image.size.width/image.size.height, XXPhotoCompressPixelMax);
        }
        UIImage *compressedImage = [image imageByScalingProportionallyToSize:compressSize];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.photoBrowser cacheImageWithPhoto:_photo image:compressedImage];
            self.showImageView.image = compressedImage;
            [self.photoBrowser hideHUDWithSuperBigPhoto];
            [self resetSize];
        });
    });
}
else {
    //直接使用
    self.showImageView.image = cacheImage;
}
  • Q2 支持縮放呢:
    解決方案是,裁剪只顯示屏幕能看見的部分,這樣大圖縮放起來也不會模糊了。
- (void)didCutImage {
    if (_orImage) {
        if (self.scrollView.contentSize.width >= kScreenWidth &&
            self.scrollView.contentSize.height >= kScreenHeight) {
            CGFloat multipleF = _orImage.size.width/self.scrollView.contentSize.width;
            CGFloat width = kScreenWidth*multipleF;
            CGFloat height = kScreenHeight *multipleF;
            //如果剪切的尺寸過大,不處理
            if (width > XXPhotoPixelMax ||
                height > XXPhotoPixelMax) {
                return;
            }
            //如果剪切的尺寸過大,不處理
            //裁剪展示視圖
            if (_bigCupImageView) {
                _bigCupImageView.frame = CGRectMake(self.scrollView.contentOffset.x, self.scrollView.contentOffset.y, kScreenWidth, kScreenHeight);
            }
            else {
                [self.scrollView addSubview:self.bigCupImageView];
            }
            //裁剪展示視圖
            CGImageRef cgRef = _orImage.CGImage;
            CGImageRef imageRef = CGImageCreateWithImageInRect(cgRef, CGRectMake(self.scrollView.contentOffset.x *multipleF   ,self.scrollView.contentOffset.y *multipleF, width, height));
            UIImage *thumbScale = [UIImage imageWithCGImage:imageRef];
            CGImageRelease(imageRef);
            self.bigCupImageView.image = thumbScale;
        }
    }
}

在這個過程中,仍需要注意的是,何時展示與隱藏剪切出來的圖片。
覆蓋圖片的添加與移除:
添加:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [self performSelector:@selector(didCutImage) withObject:nil afterDelay:.5];
}

移除:

- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view {
    if (_bigCupImageView) {
        [_bigCupImageView removeFromSuperview];
        _bigCupImageView = nil;
    }
}

3.了解 fishhook 嗎?說說為什么 fishhook 不能修改非動態(tài)連接庫中的符號?

解答:
1.Q1:簡單來說Fishhook就是hook函數(shù)的一種工具,當然它hook的原理和我們熟知的Method Swizzle 方式是不一樣的,它是Facebook提供的一個動態(tài)修改鏈接mach-O文件的工具。

  • Method Swizzle 是利用OC的Runtime特性,動態(tài)改變SEL(方法編號)和IMP(方法實現(xiàn))的對應(yīng)關(guān)系,達到OC方法調(diào)用流程改變的目的。主要用于OC方法。
  • Fishhook 是利用MachO文件加載原理,通過修改懶加載和非懶加載兩個表的指針達到C函數(shù)HOOK的目的。
  1. Q2:
  • 為什么fishhook可以交換C函數(shù)
    fishhook 是通過改變dyld加載動態(tài)庫所用的指針指向的函數(shù)地址,來實現(xiàn)的函數(shù)交換 ,所以C函數(shù)通過dyld動態(tài)加載動態(tài)庫也展現(xiàn)出C函數(shù)動態(tài)的一面。
  • 為什么我們自己寫的C函數(shù)fishhook交換不了
    我們自己寫的C函數(shù)不在系統(tǒng)動態(tài)庫緩存區(qū),而是存在我們自己的MachO文件當中,不經(jīng)過dyld加載,直接編譯成匯編語言,在MachO中的TEXT段中。所以fishhook的原理不能交換我們自己寫的C函數(shù)。

4.C++ 調(diào)用虛方法與 Objective-C 發(fā)消息有什么區(qū)別?

  1. Objective-C提供了運行期動態(tài)綁定機制,而C++是編譯靜態(tài)綁定,并且通過嵌入類(多重繼承)和虛函數(shù)(虛表)來模擬實現(xiàn)。
  2. Objective-C在語言層次上支持動態(tài)消息轉(zhuǎn)發(fā),其函數(shù)調(diào)用語法為[objece message],而且C++為object->function()。
  3. 兩者的語義也不同,在Objective-C里是說發(fā)送消息給 一個對象,至于這個對象能不能處理消息以及是處理還是轉(zhuǎn)發(fā)都不crash,而在C++里是說對象進行了某個操作,如果對象沒有這個操作的話,要么編譯會報錯(靜態(tài)綁定),要么程序會crash掉的(動態(tài)綁定)。
    4.C++生成的是機器代碼,因此C++需要虛函數(shù)來聲明方法,然后編譯器針對性的編譯實現(xiàn)函數(shù)的虛基表(實質(zhì)就是一個代理,查找函數(shù)的實際地址)來實現(xiàn)多態(tài),實質(zhì)如此實現(xiàn)也會帶來不小的性能損耗。而OC更加動態(tài)一些,當然性能損耗也更厲害一些,不過這種性能損耗,帶來的是更靈活以及更簡單的實現(xiàn),提高了開發(fā)效率。

5.了解 placement new 嗎?Objective-C 中如何實現(xiàn)這個功能?

解答:
Q1:在C++中因為可以對運算符進行重載,所以只需對operator new()進行placement重載就能實現(xiàn)在事先分配好的內(nèi)存空間上調(diào)用構(gòu)造函數(shù)來動態(tài)創(chuàng)建對象,通過不太常用的對析構(gòu)函數(shù)的顯式調(diào)用來銷毀對象。在這種情況下不涉及內(nèi)存空間的分配和釋放,從而不會導(dǎo)致動態(tài)的存儲管理,不會對系統(tǒng)的長期穩(wěn)定運行帶來影響。
Q2:Objective-C 中如何實現(xiàn)這個功能?
Objective-C中所有的對象都是指針,所有的對象都繼承NSObject,修改isa 的指向即可,感覺沒有必要實現(xiàn)C++的這個功能,如果想實現(xiàn)的是肯定是可以的應(yīng)為都是源自C的嘛。

6.如何在 ARC 環(huán)境下用 C++ 標準庫容器來管理 Objective-C 對象?

解答:參考
C++ 開發(fā)者使用 Objective-C 和 ARC 的重要提示

// C++
class A {
public:

   NSObject* s;
   A();
};     

A :: A()
 {
 s = 0; // 可能會奔潰,這是常發(fā)生在發(fā)布模式下!
 }

讓我們來看看這里發(fā)生了什么. s = 0 這一行將 0 賦值給了一個變量,而因此不管這個變量之前取值是什么,首先都會被釋放掉,所以編譯器在賦值之前執(zhí)行了一次 [s release] . 如果 s 之前已經(jīng)是 0 了,假設(shè)是在調(diào)試構(gòu)建的話,不會發(fā)生任何不好的事情; 如果 s 是一個nil的話,使用[s release] 是再好也不過的. 然而,在發(fā)布模式下, s可能是一個野指針,所以它可能在被“初始化”為0之前包含任何值(這很危險是不是?).

在C++中,這并不是一個問題,因為它里面是不存在ARC的. 而在Objective-C中編譯器并沒有辦法了解這是一次"賦值" 還是一次 "初始化" (如果是后者,它就不會發(fā)送發(fā)布消息).

下面是正確的方式:

// C++
class A {
public:
   NSObject* s;
   A();
};     

A :: A() :s(0) // 現(xiàn)在編譯器就知道確定 it's 是一次初始化了, 一次就不存在 [s release]
 {
 }

現(xiàn)在編譯器就不會去嘗試調(diào)用一個 [s release] 因為它知道它是這個對象的第一次初始化. 請小心!

7. id、self、super 它們從語法上有什么區(qū)別?

  • id 類型是iOS中一種特殊的動態(tài)數(shù)據(jù)類型,其存在價值:
    id是一種通用的對象類型,她可以用類存儲屬于任何類的對象,可以理解為萬能指針
    NSObject 和id都可以指向任何對象
    NSObject對象會驚醒編譯時檢查(需要強制類型轉(zhuǎn)換)
    id不需要強制類型轉(zhuǎn)換,id可以直接使用
    編譯器看到id以后,認為是動態(tài)類型,不在檢查類型

  • self 調(diào)用自己方法,super 調(diào)用父類方法

  • self是類隱藏參數(shù),super是預(yù)編譯指令
    【self class】和【super class】輸出是一樣的

objc_msgSend(id self,SEL _cmd)

這個函數(shù),在當前類結(jié)構(gòu)中找到方法并且調(diào)用

  • [super message] 會轉(zhuǎn)化為
id objc_msgSendSuper(struct __rw_objc_super *super, SEL op, …) 

對比發(fā)現(xiàn)這里除了函數(shù)名加了super以外,第一個參數(shù)由self變成了一個結(jié)構(gòu)體,下面讓我們來解開這個結(jié)構(gòu)體的真面目

struct __rw_objc_super { 
    struct objc_object *object; //代表當前類的對象
    struct objc_object *superClass; 
    __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};

這個結(jié)構(gòu)體中有兩個參數(shù):object的對象和一個superClass的結(jié)構(gòu)體指針,這里的object相當于上面的self
在執(zhí)行[super message]時,會做下面的事

  1. 編譯器會先構(gòu)造一個__rw_objc_super的結(jié)構(gòu)體
  2. 然后去superClass的方法列表中找方法
  3. 找到之后由object調(diào)用。
    所以當你用[self Class]和[super Class]打印類的時候,打印的都是同一個類,因為他們只是查找方法的位置不同,但是調(diào)用方法的類/對象是一樣的.

8.isa 是什么?是指向 Class 對象本身的指針嗎?

  • 一個對象(Object)的isa指向了這個對象的類(Class),而這個對象的類(Class)的isa指向了metaclass。


    image.png
  • 示例:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        //輸出1
        NSLog(@"%d", [p class] == object_getClass(p));
        //輸出0
        NSLog(@"%d", class_isMetaClass(object_getClass(p)));
        //輸出1
        NSLog(@"%d", class_isMetaClass(object_getClass([Person class])));
        //輸出0
        NSLog(@"%d", object_getClass(p) == object_getClass([Person class]));
    }
    return 0;
}

通過代碼可以看出,一個實例對象通過class方法獲取的Class就是它的isa指針指向的類對象,而類對象不是元類,類對象的isa指針指向的對象是元類。

9.block 修改捕獲變量除了用 __block 還可以怎么做?有哪些局限性?

解答:

1. 自動變量是以值傳遞方式傳遞到Block的構(gòu)造函數(shù)里面去的。Block只捕獲Block中會用到的變量。由于只捕獲了自動變量的值,并非內(nèi)存地址,所以Block內(nèi)部不能改變自動變量的值。Block捕獲的外部變量可以改變值的是靜態(tài)變量,靜態(tài)全局變量,全局變量。
    1. 靜態(tài)全局變量,全局變量由于作用域的原因,于是可以直接在Block里面被改變。他們也都存儲在全局區(qū)。
image.png
    1. 靜態(tài)變量傳遞給Block是內(nèi)存地址值,所以能在Block里面直接改變值。蘋果要求我們在自動變量前加入 __block關(guān)鍵字(__block storage-class-specifier存儲域類說明符),就可以在Block里面改變外部自動變量的值了。

總結(jié): 在Block中改變變量值有2種方式, 一是傳遞內(nèi)存地址指針到Block中, 二是改變存儲區(qū)方式(__block)。

  • 實例:
#import <Foundation/Foundation.h>
 
int main(int argc, const char * argv[]) {
 
  NSMutableString * str = [[NSMutableString alloc]initWithString:@"Hello,"];
 
        void (^myBlock)(void) = ^{
            [str appendString:@"World!"];
            NSLog(@"Block中 str = %@",str);
        };
 
    NSLog(@"Block外 str = %@",str);
 
    myBlock();
 
    return 0;
}
  • 輸出
Block 外  str = Hello,
Block 中  str = Hello,World!

源碼.cpp

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableString *str;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSMutableString *str = __cself->str; // bound by copy
 
            ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_1);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_2,str);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
 
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
 
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
 
int main(int argc, const char * argv[]) {
    NSMutableString * str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_0);
 
        void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344));
 
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_3,str);
 
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
 
    return 0;
}

在__main_block_func_0里面可以看到傳遞的是指針。所以成功改變了變量的值。

2. OC中,一般Block就分為以下3種,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock。

2.1.從捕獲外部變量的角度上來看

  • _NSConcreteStackBlock:
    只用到外部局部變量、成員屬性變量,且沒有強指針引用的block都是StackBlock。
    StackBlock的生命周期由系統(tǒng)控制的,一旦返回之后,就被系統(tǒng)銷毀了。

  • _NSConcreteMallocBlock:
    有強指針引用或copy修飾的成員屬性引用的block會被復(fù)制一份到堆中成為MallocBlock,沒有強指針引用即銷毀,生命周期由程序員控制

  • _NSConcreteGlobalBlock:
    沒有用到外界變量或只用到全局變量、靜態(tài)變量的block為_NSConcreteGlobalBlock,生命周期從創(chuàng)建到應(yīng)用程序結(jié)束。

2.2 .從持有對象的角度上來看:

  • _NSConcreteStackBlock是不持有對象的。
//以下是在MRC下執(zhí)行的
    NSObject * obj = [[NSObject alloc]init];
    NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);
 
    void (^myBlock)(void) = ^{
        NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
    };
 
    NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);
 
    myBlock();

輸出:

1.Block外 obj = 1
2.Block外 obj = 1
Block中 obj = 1
  • _NSConcreteMallocBlock是持有對象的。
//以下是在MRC下執(zhí)行的
    NSObject * obj = [[NSObject alloc]init];
    NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);
 
    void (^myBlock)(void) = [^{
        NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
    }copy];
 
    NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);
 
    myBlock();
 
    [myBlock release];
 
    NSLog(@"3.Block外 obj = %lu",(unsigned long)obj.retainCount);

輸出

1.Block外 obj = 1
2.Block外 obj = 2
Block中 obj = 2
3.Block外 obj = 1
  • _NSConcreteGlobalBlock也不持有對象
//以下是在MRC下執(zhí)行的
    void (^myBlock)(void) = ^{
 
        NSObject * obj = [[NSObject alloc]init];
        NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
    };
 
    myBlock();

輸出

Block 中 obj = 1

由于_NSConcreteStackBlock所屬的變量域一旦結(jié)束,那么該Block就會被銷毀。在ARC環(huán)境下,編譯器會自動的判斷,把Block自動的從棧copy到堆。比如當Block作為函數(shù)返回值的時候,肯定會copy到堆上。

1.手動調(diào)用copy
2.Block是函數(shù)的返回值
3.Block被強引用,Block被賦值給__strong或者id類型
4.調(diào)用系統(tǒng)API入?yún)⒅泻衭singBlcok的方法

以上4種情況,系統(tǒng)都會默認調(diào)用copy方法把Block賦復(fù)制

但是當Block為函數(shù)參數(shù)的時候,就需要我們手動的copy一份到堆上了。這里除去系統(tǒng)的API我們不需要管,比如GCD等方法中本身帶usingBlock的方法,其他我們自定義的方法傳遞Block為參數(shù)的時候都需要手動copy一份到堆上。

copy函數(shù)把Block從棧上拷貝到堆上,dispose函數(shù)是把堆上的函數(shù)在廢棄的時候銷毀掉。

3..Block中__block實現(xiàn)原理

1.普通非對象的變量

#import <Foundation/Foundation.h>
 
int main(int argc, const char * argv[]) {
 
    __block int i = 0;
 
    void (^myBlock)(void) = ^{
        i ++;
        NSLog(@"%d",i);
    };
 
    myBlock();
 
    return 0;
}

把上述代碼用clang轉(zhuǎn)換成源碼。

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};
 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref
 
        (i->__forwarding->i) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
 
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
 
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
 
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
 
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
 
    return 0;
}

從源碼我們能發(fā)現(xiàn),帶有 __block的變量也被轉(zhuǎn)化成了一個結(jié)構(gòu)體__Block_byref_i_0,這個結(jié)構(gòu)體有5個成員變量。第一個是isa指針,第二個是指向自身類型的__forwarding指針,第三個是一個標記flag,第四個是它的大小,第五個是變量值,名字和變量名同名。

__forwarding指針這里的作用就是針對堆的Block,把原來__forwarding指針指向自己,換成指向_NSConcreteMallocBlock上復(fù)制之后的__block自己。然后堆上的變量的__forwarding再指向自己。這樣不管__block怎么復(fù)制到堆上,還是在棧上,都可以通過(i->__forwarding->i)來訪問到變量值。


image.png

ARC環(huán)境下,一旦Block賦值就會觸發(fā)copy,__block就會copy到堆上,Block也是__NSMallocBlock。ARC環(huán)境下也是存在__NSStackBlock的時候,這種情況下,__block就在棧上。

MRC環(huán)境下,只有copy,__block才會被復(fù)制到堆上,否則,__block一直都在棧上,block也只是__NSStackBlock,這個時候__forwarding指針就只指向自己了。


image.png

2.對象的變量

//以下代碼是在ARC下執(zhí)行的
#import <Foundation/Foundation.h>
 
int main(int argc, const char * argv[]) {
 
    __block id block_obj = [[NSObject alloc]init];
    id obj = [[NSObject alloc]init];
 
    NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
 
    void (^myBlock)(void) = ^{
        NSLog(@"***Block中****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
    };
 
    myBlock();
 
    return 0;
}

輸出:

block_obj = [<NSObject: 0x100b027d0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b03b50> , 0x7fff5fbff7b8]
Block****中********block_obj = [<NSObject: 0x100b027d0> , 0x100f000a8] , obj = [<NSObject: 0x100b03b50> , 0x100f00070]

我們把上面的代碼轉(zhuǎn)換成源碼研究一下:

struct __Block_byref_block_obj_0 {
  void *__isa;
__Block_byref_block_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id block_obj;
};
 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id obj;
  __Block_byref_block_obj_0 *block_obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
  id obj = __cself->obj; // bound by copy
 
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , obj , &obj);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
 
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
 
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
 
 
int main(int argc, const char * argv[]) {
 
    __attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
 
    id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , obj , &obj);
 
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));
 
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
 
    return 0;
}

總結(jié):

在MRC環(huán)境下,__block根本不會對指針所指向的對象執(zhí)行copy操作,而只是把指針進行的復(fù)制。
而在ARC環(huán)境下,對于聲明為__block的外部對象,在block內(nèi)部會進行retain,以至于在block環(huán)境內(nèi)不安全的引用外部對象,所以才會產(chǎn)生循環(huán)引用的問題!

5種變量,自動變量,函數(shù)參數(shù) ,靜態(tài)變量,靜態(tài)全局變量,全局變量,如果嚴格的來說,捕獲是必須在Block結(jié)構(gòu)體__main_block_impl_0里面有成員變量的話,Block能捕獲的變量就只有帶有自動變量和靜態(tài)變量了。捕獲進Block的對象會被Block持有。

對于非對象的變量來說,
自動變量的值,被copy進了Block,不帶__block的自動變量只能在里面被訪問,并不能改變值。


image.png

帶__block的自動變量 和 靜態(tài)變量 就是直接地址訪問。所以在Block里面可以直接改變變量的值。


image.png

而剩下的靜態(tài)全局變量,全局變量,函數(shù)參數(shù),也是可以在直接在Block中改變變量值的,但是他們并沒有變成Block結(jié)構(gòu)體__main_block_impl_0的成員變量,因為他們的作用域大,所以可以直接更改他們的值。

值得注意的是,靜態(tài)全局變量,全局變量,函數(shù)參數(shù)他們并不會被Block持有,也就是說不會增加retainCount值。

對于對象來說,

在MRC環(huán)境下,__block根本不會對指針所指向的對象執(zhí)行copy操作,而只是把指針進行的復(fù)制。
而在ARC環(huán)境下,對于聲明為__block的外部對象,在block內(nèi)部會進行retain,以至于在block環(huán)境內(nèi)能安全的引用外部對象。

10. NSDictionary 與 NSHashTable 有什么區(qū)別,它們的使用場景是怎樣的?

我們使用集合(NSArray,NSMutableArray,NSDictionary,NSMutableDictionary,NSSet,NSMutableSet)存儲對象時會對其強引用,有時候我們不想這樣子,怎么辦呢?
那就使用NSHashTable這個集合吧,它的使用方法與NSSet完全相似,不同的是,它的一種初始化方式是weakObjectsHashTable,專門用來存儲弱引用對象,不會持有它,那個對象的所有人消失了,這個對象也會從這個集合中消失!
常用用法:

- (BOOL)containsObject:(id)anObject
Returns a Boolean value that indicates whether the hash table contains a given object.
返回一個bool值,用來指示這個hash表中是否包括了你給與的對象.
- (void)addObject:(id)object
Adds a given object to the hash table.
將一個對象添加進hash表中.
- (void)removeObject:(id)object
Removes a given object from the hash table.
從hash表中移除你給定的對象.
+ (id)weakObjectsHashTable
Returns a new hash table for storing weak references to its contents.
返回一個hash表用來存儲弱引用對象.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容