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

1.詳情描述一下UIView 與CALayer 的關(guān)系,drawRect 一定會影響性能嗎?UIDynamics 與 UIKit Animation 的最本質(zhì)的區(qū)別是什么?
解答:
Q1. UIView 與CALayer 的關(guān)系?
- UIView可以響應(yīng)事件,CALayer不可以。
- UIKit使用UIResponder作為響應(yīng)對象,來響應(yīng)系統(tǒng)傳遞過來的事件并進行處理。在 UIResponder中定義了處理各種事件和事件傳遞的接口。
UIApplication、UIViewController、UIView、和所有從UIView派生出來的UIKit類(包括UIWindow)都直接或間接地繼承自UIResponder類。
CALayer直接繼承 NSObject,并沒有相應(yīng)的處理事件的接口。
- UIKit使用UIResponder作為響應(yīng)對象,來響應(yīng)系統(tǒng)傳遞過來的事件并進行處理。在 UIResponder中定義了處理各種事件和事件傳遞的接口。
- UIView 是 CALayer的delegate
- 在給 UIView 的 Layer 做動畫的時候,View 作為 Layer 的代理,Layer 通過 actionForLayer:forKey:向 View請求相應(yīng)的 action(動畫行為)
- UIView主要處理事件,CALayer負責(zé)繪制
- 每個 UIView 內(nèi)部都有一個 CALayer 在背后提供內(nèi)容的繪制和顯示,并且 UIView 的尺寸樣式都由內(nèi)部的 Layer 所提供。兩者都有樹狀層級結(jié)構(gòu),layer 內(nèi)部有 SubLayers,View 內(nèi)部有 SubViews.但是 Layer 比 View 多了個AnchorPoint
- 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的目的。
- 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ū)別?
- Objective-C提供了運行期動態(tài)綁定機制,而C++是編譯靜態(tài)綁定,并且通過嵌入類(多重繼承)和虛函數(shù)(虛表)來模擬實現(xiàn)。
- Objective-C在語言層次上支持動態(tài)消息轉(zhuǎn)發(fā),其函數(shù)調(diào)用語法為[objece message],而且C++為object->function()。
- 兩者的語義也不同,在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]時,會做下面的事
- 編譯器會先構(gòu)造一個__rw_objc_super的結(jié)構(gòu)體
- 然后去superClass的方法列表中找方法
- 找到之后由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)全局變量,全局變量。
- 靜態(tài)全局變量,全局變量由于作用域的原因,于是可以直接在Block里面被改變。他們也都存儲在全局區(qū)。

- 靜態(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)來訪問到變量值。

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指針就只指向自己了。

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的自動變量只能在里面被訪問,并不能改變值。

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

而剩下的靜態(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表用來存儲弱引用對象.


