一、設計模式是什么?你知道哪些設計模式,并簡要敘述?
設計模式是一種編碼經驗,就是用比較成熟的邏輯去處理某一類類型的事情。
1. MVC模式:Model View Control,把模型、視圖、控制器層進行解耦編寫
2. MVVM模式:Model View ViewModel 把模型、視圖、業(yè)務進行解耦編寫
3. 單例模式:通過static關鍵詞,聲明全局變量。在整個進程運行期間只會被賦值一次。
4. 觀察者模式:KVO是典型的通知模式,觀察某個屬性的狀態(tài),狀態(tài)發(fā)生變化時通知觀察者。
5. 委托模式:代理 + 協(xié)議的組合。實現1對1的反向傳值操作。
6. 工廠模式:通過一個類方法,批量的根據已有的模板生產對象。
二、MVC和MVVM的區(qū)別
1. MVVM是對胖模型進行的拆分,其本質是給控制器減負,將一些弱業(yè)務邏輯放到VM中去處理。
2. MVC是一切設計的基礎,所有新的設計模式都是基于MVC進行的改進。
三、#import跟 #include 有什么區(qū)別,@class呢,#import<> 跟 #import””有什么區(qū)別?
1. #import 是OC導入頭文件的關鍵字, #include是C/C++導入狗文件的關鍵字,使用#import頭文件會自動導入一次,不會重復導入。
2. @class告訴編輯器某個類的聲明,當執(zhí)行時,才去查看類的實現文件,可以解決頭文件的相互包含。
3. #import<> 用來包含系統(tǒng)的頭文件,#import""用來包含用戶頭文件。
四、@property 的本質是什么?ivar、getter、setter 是如何生成并添加到這個類中的
@property = ivar + getter + setter;
“屬性” (property)有兩大概念:ivar(實例變量)、getter+setter(存取方法)
五、@property中有哪些屬性關鍵字?/ @property 后面可以有哪些修飾符?
屬性可以擁有的特質分為四類:
1.原子性--- nonatomic 特質
2.讀/寫權限---readwrite(讀寫)、readonly (只讀)
3.內存管理語義---assign、strong、 weak、unsafe_unretained、copy
4.方法名---getter=<name> 、setter=<name>
5.不常用的:nonnull,null_resettable,nullable
六、屬性關鍵字 readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那種情況下用?
1). readwrite 是可讀可寫特性。需要生成getter方法和setter方法。
2). readonly 是只讀特性。只會生成getter方法,不會生成setter方法,不希望屬性在類外改變。
3). assign 是賦值特性。setter方法將傳入參數賦值給實例變量;僅設置變量時,assign用于基本數據類型。
4). retain(MRC)/strong(ARC) 表示持有特性。setter方法將傳入參數先保留,再賦值,傳入參數的retaincount會+1。
5). copy 表示拷貝特性。setter方法將傳入對象復制一份,需要完全一份新的變量時。
6). nonatomic 非原子操作。決定編譯器生成的setter和getter方法是否是原子操作,atomic表示多線程安全,一般使用nonatomic,效率高。
7). atomic讀寫安全,但效率低,不是絕對的安全,比如操作數組,增加或移除,這種情況可以使用互斥鎖來保證線程安全
8). weak不改變修飾對象的引用計數,對象釋放后,weak指針自動置為空
七、什么情況使用 weak 關鍵字,相比 assign 有什么不同?
1.在 ARC 中,在有可能出現循環(huán)引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性。
2.自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當然,也可以使用strong。
IBOutlet連出來的視圖屬性為什么可以被設置成weak?
因為父控件的subViews數組已經對它有一個強引用。
不同點:
assign 可以用非 OC 對象,而 weak 必須用于 OC 對象。
weak 表明該屬性定義了一種“非擁有關系”。在屬性所指的對象銷毀時,屬性值會自動清空(nil)。
八、怎么用 copy 關鍵字?
用途:
1. NSString、NSArray、NSDictionary 等等經常使用copy關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary;
2. block 也經常使用 copy 關鍵字。
說明:
block 使用 copy 是從 MRC 遺留下來的“傳統(tǒng)”,在 MRC 中,方法內部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū).在 ARC 中寫不寫都行:對于 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅,還能時刻提醒我們:編譯器自動對 block 進行了 copy 操作。如果不寫 copy ,該類的調用者有可能會忘記或者根本不知道“編譯器會自動對 block 進行了 copy 操作”,他們有可能會在調用之前自行拷貝屬性值。這種操作多余而低效。
九、用@property聲明的 NSString / NSArray / NSDictionary 經常使用 copy 關鍵字,為什么?如果改用strong關鍵字,可能造成什么問題?
用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作(就是把可變的賦值給不可變的),為確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
1. 因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本。
2. 如果我們使用是 strong ,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。
//總結:使用copy的目的是,防止把可變類型的對象賦值給不可變類型的對象時,可變類型對象的值發(fā)送變化會無意間篡改不可變類型對象原來的值。
例如:
使用strong關鍵字

結果為:
allDataMArr = (
1,
2,
3,
e
)
dataArr = (
1,
2,
3,
e
)
不可變的數組使用了strong關鍵字,當給可變數組添加了一個字符串的時候,不可變數組同時變了。
這是由于當不可變數組使用strong關鍵字的時候,allDataMArr給dataArr賦值是dataArr對象的指針指向的內存區(qū)域是原來allDataMArr指針指向的內存區(qū)域,只是在該內存區(qū)域的引用計數上加1,并沒有重新拷貝這塊內存區(qū)域,所以當allDataMArr修改了這塊內存區(qū)域的時候,dataArr打印的值同樣發(fā)生了變化。
使用copy關鍵字

結果為:
allDataMArr = (
1,
2,
3,
e
)
dataArr = (
1,
2,
3
)
不可變的數組使用了copy關鍵字,當給可變數組添加了一個字符串的時候,不可變數組不影響。
這是由于當不可變數組使用copy關鍵字的時候,allDataMArr給dataArr賦值是dataArr對象的指針指向的內存區(qū)域拷貝了allDataMArr指針指向的內存區(qū)域,和allDataMArr沒有關系了這時,所以當allDataMArr修改了這塊內存區(qū)域的時候,dataArr打印的值不會發(fā)生了變化。
十、淺拷貝和深拷貝的區(qū)別?
淺拷貝:只復制指向對象的指針,而不復制引用對象本身。
深拷貝:復制引用對象本身。內存中存在了兩份獨立對象本身,當修改A時,A_copy不變。
十一、copy和mutableCopy
1. copy返回的是不可變獨享
2. mutableCopy 返回的是可變對象
一、非集合對象的copy與mutableCopy
在非集合類對象中,對不可變對象進行copy操作,是指針復制,mutableCopy操作是內容復制;
對可變對象進行copy和mutableCopy都是內容復制。用代碼簡單表示如下:
NSString *str = @"hello word!";
NSString *strCopy = [str copy] // 指針復制,strCopy與str的地址一樣
NSMutableString *strMCopy = [str mutableCopy] // 內容復制,strMCopy與str的地址不一樣
NSMutableString *mutableStr = [NSMutableString stringWithString: @"hello word!"];
NSString *strCopy = [mutableStr copy] // 內容復制
NSMutableString *strMCopy = [mutableStr mutableCopy] // 內容復制
二、集合類對象的copy與mutableCopy (同上)
在集合類對象中,對不可變對象進行copy操作,是指針復制,mutableCopy操作是內容復制;
對可變對象進行copy和mutableCopy都是內容復制。但是:集合對象的內容復制僅限于對象本身,對集合內的對象元素仍然是指針復制。(即單層內容復制)
NSArray *arr = @[@[@"a", @"b"], @[@"c", @"d"];
NSArray *copyArr = [arr copy]; // 指針復制
NSMutableArray *mCopyArr = [arr mutableCopy]; //單層內容復制
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArr = [mutableArr copy]; // 單層內容復制
NSMutableArray *mCopyArr = [mutableArr mutableCopy]; // 單層內容復制
【總結一句話】:
只有對不可變對象進行copy操作是指針復制(淺復制),其它情況都是內容復制(深復制)!
十二、這個寫法會出什么問題:@property (nonatomic, copy) NSMutableArray *arr;
問題:添加,刪除,修改數組內的元素的時候,程序會因為找不到對應的方法而崩潰。
原因:是因為 copy 就是復制一個不可變 NSArray 的對象,不能對 NSArray 對象進行添加/修改。
十三、如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?
若想令自己所寫的對象具有拷貝功能,則需實現 NSCopying 協(xié)議。如果自定義的對象分為可變版本與不可變版本,那么就要同時實現 NSCopying 與 NSMutableCopying 協(xié)議。
具體步驟:
1. 需聲明該類遵從 NSCopying 協(xié)議
2. 實現 NSCopying 協(xié)議的方法。
// 該協(xié)議只有一個方法:
- (id)copyWithZone:(NSZone *)zone;
// 注意:使用 copy 修飾符,調用的是copy方法,其實真正需要實現的是 “copyWithZone” 方法。
十四、MRC中寫一個 setter 方法用于完成 @property (nonatomic, retain) NSString *name,寫一個 setter 方法用于完成 @property (nonatomic, copy) NSString *name
// retain
- (void)setName:(NSString *)str {
[str retain];
[_name release];
_name = str;
}
// copy
- (void)setName:(NSString *)str {
id t = [str copy];
[_name release];
_name = t;
}
十五、@synthesize 和 @dynamic 分別有什么作用?
@property有兩個對應的詞,一個是@synthesize(合成實例變量),一個是@dynamic。
如果@synthesize和@dynamic都沒有寫,那么默認的就是 @synthesize var = _var;
// 在類的實現代碼里通過 @synthesize 語法可以來指定實例變量的名字。(@synthesize var = _newVar;)
1. @synthesize 的語義是如果你沒有手動實現setter方法和getter方法,那么編譯器會自動為你加上這兩個方法。
2. @dynamic 告訴編譯器,屬性的setter與getter方法由用戶自己實現,不自動生成(如,@dynamic var)。
十六、NSInteger和int區(qū)別
NSInteger是基本數據類型Int或者Long的別名(NSInteger的定義typedef long NSInteger),它的區(qū)別在于,NSInteger會根據系統(tǒng)是32位還是64位來決定是本身是int還是long。
十七、KVC的底層實現?
當一個對象調用setValue方法時,方法內部會做以下的操作:
1. 檢查是否存在相應的key和set方法,如果存在,就調用set方法
2. 如果set方法不存在,就會查找key相同名稱并且?guī)聞澗€的成員變量,如果有,則直接給成員變量屬性賦值。
3. 如果沒有找到_key, 就會查找相同名稱的屬性key,如果有就直接賦值。
4. 如果還沒有找到,則調用vauleForUndefinedKey:和
setVaule:forUndefinedKey:方法。
十八、推送原理
1.由App向iOS設備發(fā)送一個注冊通知,用戶需要同意系統(tǒng)發(fā)送推送。
2.iOS向APNs遠程推送服務器發(fā)送App的Bundle Id和設備的UDID。
3.APNs根據設備的UDID和App的Bundle Id生成deviceToken再發(fā)回給App。
4.App再將deviceToken發(fā)送給遠程推送服務器(自己的服務器), 由服務器保存在數據庫中。
5.當自己的服務器想發(fā)送推送時, 在遠程推送服務器中輸入要發(fā)送的消息并選擇發(fā)給哪些用戶的deviceToken,由遠程推送服務器發(fā)送給APNs。
6.APNs根據deviceToken發(fā)送給對應的用戶。
APNs 服務器就是蘋果專門做遠程推送的服務器。
deviceToken是由APNs生成的一個專門找到你某個手機上的App的一個標識碼。
deviceToken 可能會變,如果你更改了你項目的bundle Identifier或者APNs服務器更新了可能會變。
十九、KVO?
KVO(key-Value-Observing):鍵值觀察機制 他提供了觀察某一屬性變化的方法,極大的簡化了代碼。
有四種基本使用方式:
1.KVO的基本使用
[self.a addObserver:self forKeyPath:@“name” options:NSKeyValueObservingOptionNew context:nil];
2. KVO的自動觸發(fā)模式和手動觸發(fā)模式
是否使用自動觸發(fā)模式取決于下面方法的返回值
+ (BOOL)automaticallyNotifiesObserversOfName {
return NO;
}
當此方法如果返回NO,表示關閉了自動觸發(fā)模式,需要手動觸發(fā)需要調用如下:
[self.a willChangeValueForKey:@“name”];
[self.a didChangeValueForKey:@“name”];
3.KVO的屬性依賴?
就是需要觀察的對象中有一個自定義的對象屬性,如果要觀察這個自定義對象屬性的屬性的時候就需要進行屬性依賴?
@interface Animal : NSObject
///
@property (nonatomic, strong) NSString *name;
///
@property (nonatomic, strong) Cat *cat;
///
@property (nonatomic, strong) NSMutableArray *arr;
@end
@implementation Animal
/// 當觀察cat對象的時候,添加cat對象的所有屬性路徑
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"cat"]) {
NSArray *arrKeyPaths = @[@"_cat.age", @"_cat.name"];
keyPaths = [keyPaths setByAddingObjectsFromArray:arrKeyPaths];
}
return keyPaths;
}
@end
@interface Cat : NSObject
///
@property (nonatomic, assign) int age;
///
@property (nonatomic, strong) NSString *name;
@end
然后在需要觀察的地方添加觀察者即可
[self.a addObserver:self forKeyPath:@"cat" options:NSKeyValueObservingOptionNew context:nil];
接收方法如下
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
// NSLog(@"%@===%@===%@",keyPath,object,change);
if ([keyPath isEqualToString:@"cat"]) {
Cat *cat = change[@"new"];
NSLog(@"%d",cat.age);
}
}
如果我們不做上面的操作,想直接添加觀察者也是可以的,比如下面
[_animal addObserver:self forKeyPath:@"_cat.age" options:NSKeyValueObservingOptionNew context:nil];
但是當有cat對象有很多個屬性的時候,這里需要寫多次觀察者的,這樣寫起來很麻煩。
4.容器類的觀察
由于KVO是觀察的set方法,所以當屬性時容器類的時候,容器類的內容進行改變觀察不到,所以這時需要進行相關的處理操作。
例如:對象Animal中的arr數組,直接進行addObject操作不會被KVO觀察到。此時蘋果為我們提供了一種方法[self.a mutableArrayValueForKey:@“arr”];獲取到此數組,對此數組進行addObject操作就可以觀察到容器類的變化
static int i = 0;
I++;
NSMutableArray *tempArr = [_animal mutableArrayValueForKey:@"arr"];
[tempArr addObject:@(i)];
這時由于使用mutableArrayValueForKey得到的新的數組之后,如果新數組發(fā)生了變化,那么新數組就會把老數組替代了,可以說是執(zhí)行了set方法。
二十、KVO的底層原理?
1. 創(chuàng)建了一個當前類的子類,注冊此子類
2. 給創(chuàng)建的子類添加set方法。
3. 改變isa指針,令self的isa指針指向創(chuàng)建的子類
4. 在步驟2中添加的set方法中調用父類的set方法,在調用observeValueForKeyPath: ofObject: change: context:方法。
二十一、空指針和野指針
空指針:表示指針變量里面沒有保存地址。這就是為什么要把對象置為nil的原因。

野指針:表示指針變量指向的內存地址已經被回收,此時,指針變量保存的內存地址就是垃圾地址。
二十二、runTime如何實現weak變量自動置為nil?
runtime對注冊的類會進行布局,會將weak對象放入一個hash表中,用weak指向的對象的內存地址作為key。當此對象的引用計數為0的時候,會在這個weak hash表中通過key找到weak對象,將其置為nil
二十三、方法和選擇器有何不同?
selector是一個方法的名稱,方法是一個組合體,包含了名字和實現。
二十四、runtime如何通過selector找到對應的IMP地址?
1.每一個類對象中都有一個對象方法列表。(對象方法緩存)
2.類方法列表是存放類對象中isa指針指向的元類對象中。(類方法緩存)
3.方法列表中每個方法結構體中記錄著方法的名稱,方法實現,以及參數類型,其實selector是方法名稱。通過這個名稱
二十五、ViewController生命周期
按照執(zhí)行順序排列:
1. initWithCoder:通過nib文件初始化時觸發(fā)。
2. awakeFromNib:nib文件被加載的時候,會發(fā)生一個awakeFromNib的消息到nib文件中的每個對象。
3. loadView:開始加載視圖控制器自帶的view。
4. viewDidLoad:視圖控制器的view被加載完成。
5. viewWillAppear:視圖控制器的view將要顯示在window上。
6. updateViewConstraints:視圖控制器的view開始更新AutoLayout約束。
7. viewWillLayoutSubviews:視圖控制器的view將要更新內容視圖的位置。
8. viewDidLayoutSubviews:視圖控制器的view已經更新視圖的位置。
9. viewDidAppear:視圖控制器的view已經展示到window上。
10. viewWillDisappear:視圖控制器的view將要從window上消失。
11. viewDidDisappear:視圖控制器的view已經從window上消失。
二十六、你是否接觸過OC中的反射機制?簡單聊一下概念和使用
1. class反射
通過類名的字符串形式實例化對象。
Class class = NSClassFromString(@"Student")
Student *stu = [[class alloc] init];
將類名變?yōu)樽址?Class class = [Student class];
NSString *className = NSStringFromClass(class);
2.SEL的反射
通過方法的字符串形式實例化方法。
SEL selector = NSSelectorFromString(@"setName");
將方法變成字符串
NSStringFromSelector(@selector*(setName:))
二十七、類變量的 @public,@protected,@private,@package 聲明各有什么含義?
@public 任何地方都能訪問。
@protected 該類和子類中訪問,是默認的
@private 只能在本類中訪問
@package 本包內使用,挎包不可用
二十九、isa指針
isa:是一個Class類型的指針。每個實例對象有個isa的指針,他指向對象的類,而Class里也有個isa指針,指向metelClass(元類)。元類中保存了類方法的列表。當類方法被調用時,先會從本身查找類方法的實現,如果沒有,元類會向父類查找該方法。同時注意的是:元類(meteClass)也是類,他也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass)。根元類的isa指針指向本身。這樣形成了一個封閉的內循環(huán)。
三十、下面的代碼輸出什么?
@implementation Son : Father
- (id)init {
if (self = [super init]) {
NSLog(@"%@", NSStringFromClass([self class])); // Son
NSLog(@"%@", NSStringFromClass([super class])); // Son
}
return self;
}
@end
// 解析:
self 是類的隱藏參數,指向當前調用方法的這個類的實例。
super是一個Magic Keyword,它本質是一個編譯器標示符,和self是指向的同一個消息接收者。
不同的是:super會告訴編譯器,調用class這個方法時,要去父類的方法,而不是本類里的。
上面的例子不管調用[self class]還是[super class],接受消息的對象都是當前 Son *obj 這個對象。
三十一、isKindOfClass、isMemberOfClass作用分別是什么
isKindOfClass: 作用是某個對象屬于某個類型或者繼承自某類型。
isMemberOfClass: 某個對象確切屬于哪個類型。
三十二、block的注意點
1). 在block內部使用外部指針且會造成循環(huán)引用情況下,需要用__week修飾外部指針:
__weak typeof(self) weakSelf = self;
2). 在block內部如果調用了延時函數還使用弱指針會取不到該指針,因為已經被銷毀了,需要在block內部再將弱指針重新強引用一下。
__strong typeof(self) strongSelf = weakSelf;
3). 如果需要在block內部改變外部棧區(qū)變量的話,需要在用__block修飾外部變量。
三十三、響應者鏈?
先執(zhí)行事件鏈,找到合適的view,在執(zhí)行響應鏈
1.事件鏈
UIApplication -> window -> view -> view ……..->view
a. 當iOS程序員中發(fā)生觸摸事件后,系統(tǒng)會將事件加入到UIApplication管理的一個任務隊列中
b. UIApplication 將處于任務最前端的事件向下分發(fā),即UIWindow
c. UIWindow將事件向下分發(fā),即UIView
d. UIView首先查看自己是否能處理事件(hidden = NO, userInteractionEnabled = YES, alpha >= 0.01),觸摸點是否在自己身上。如果能,那么繼續(xù)尋找子視圖
e. 便利子控件(從后往前遍歷),重復上面的步驟。
f. 如果沒有找到,那么自己就是事件處理者,如果自己不能處理,那就不做任何事。
事件鏈的過程其實就是 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
函數的執(zhí)行過程。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.判斷當前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
return nil;
}
// 2.判斷點在不在當前控件
if ([self pointInside:point withEvent:event] == NO) {
return nil;
}
// 3.從后往前遍歷自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[I];
// 把當前控件上的坐標系轉換成子控件上的坐標系
CGPoint childPoint = [self convertPoint:point toView:childView];
// 重復上面的工作
UIView *fitView = [childView hitTest:childPoint withEvent:event];
if (fitView != nil) { // 尋找最合適的view
return fitView;
}
}
// 循環(huán)結束,表示沒有比自己更合適的view
return self;
}
2.響應鏈
響應鏈是從最合適的view開始傳遞,處理事件傳遞給下一個響應者,響應鏈的傳遞是事件鏈傳遞相反的。如果所有響應者都不處理事件,則事件被丟棄。通常獲取上級響應者是通過nextResponder方法的。
三十四、UIView與CALayer的區(qū)別?
UIView繼承于UIResponder,可以響應用戶事件,CALayer繼承于NSObject不可以響應用戶事件。
UIView側重于顯示內容的管理,CALayer側重于對內容的繪制。
UIView與CALayer是相互依賴的關系。UIView的顯示依賴于CALayer提供的內容,CALayer依賴UIView的容器來顯示繪制的內容。
三十五、圖像顯示原理?

- CPU輸出位圖
- GPU圖層渲染、紋理合成
- 把結果放到幀緩沖區(qū)中
- 再由視圖控制器根據vsync(垂直同步信號)在指定時間之前去提取幀緩沖區(qū)的屏幕顯示內容
- 5.顯示到屏幕上
三十六、UI卡頓掉幀的原因?

iOS設備會發(fā)出垂直同步信號,然后App的CPU會去計算屏幕要顯示的內容,之后將計算好的內容提交到GPU去渲染。隨后,GPU將渲染的結果提交到幀緩沖區(qū),等到下一個垂直同步信號到來時將緩沖區(qū)的幀顯示到屏幕上。一幀的顯示是CPU和GPU共同決定的。
每隔16.7毫秒會產生一幀畫面。當CPU + GPU處理的時間超過16.7毫秒就會造成掉幀甚至卡頓。
三十七、離屏渲染?
- 當前屏幕渲染:指的是GPU的渲染操作時在當前用于顯示的屏幕緩沖區(qū)中進行。
- 離屏渲染:分為GPU的離屏渲染和CPU的離屏渲染。GPU離屏渲染指的是GPU在當前屏幕緩沖區(qū)外開辟了一個緩沖區(qū)進行渲染操作。
應當盡量避免GPU的離屏渲染。
GPU的離屏渲染何時觸發(fā)?
圓角、圖層蒙版、陰影等設置。
- 為什么要避免GPU的離屏渲染?
因為GPU的離屏渲染需要額外開辟一塊緩沖區(qū)進行渲染,然后繪制到當前屏幕的過程中還需要做onscreen(當前屏幕)和offscreen(非當前屏幕)進行上下文的切換。由于這個切換過程消耗資源,而且每一幀都會進行切換。所以處理不當會對性能產生影響。還會增加GPU的工作量。
三十八、消息傳遞?
其實就是調用 objc_msgSend 函數
流程:緩存中查找—> 當前類查找 —> 父類逐級查找
1. 調用方法之前,先去查找緩存,看看緩存中是否有對應選擇器的方法實現。如果有,就去調用函數,完成消息傳遞(緩存查找:給定值SEL,目標是查找對應的IMP)
2.如果緩存中沒有,會根據當前實例的isa指針查找當前類對象的方法列表,看看是否有同樣名稱的方法,如果有,就去調用函數,完成消息傳遞(當前來中查找:對于已經排序好的方法列表采用二分法查找,對于沒有排序好的列表,采用一般遍歷)
3.如果當前類對象的方法列表中沒有,就會逐級父類方法列表中查找,如果找到,就去調用函數,完成消息傳遞。(父類逐級查找:先判斷父類是否為nil,為nil則結束,否則就繼續(xù)進行緩存中查找 —> 當前類查找 ——> 父類逐級查找流程)
4.如果一直查到根類亦然沒有查到,則進入消息轉發(fā)流程,完成消息傳遞。
三十九、消息轉發(fā)?
1. + (BOOL) resolveInstanceMethod:(SEL)sel; 為對象方法進行決議
+ (BOOL) resolveClassMethod:(SEL)sel; 為類方法進行決議
2. - (id) forwardingTargetForSelector:(SEL)aSelector; 方法轉發(fā)目標
3. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void) forwardInvocation:(NSInvocation *)anInvocation;
最后如果還是沒有對消息進行處理,則會調用 - (void) doesNotRecognizeSelector:(SEL)aSelector方法。
如果前兩步還沒有處理掉,則進入完整的消息轉發(fā)。methodSignatureForSelector方法。
如果methodSignatureForSelector返回nil則程序掛掉。如果返回了一個方法簽名,rutime會創(chuàng)建一個NSInvocation對象并發(fā)送forwardInvocation消息給目標對象。


四十、runTime的實現機制是什么,在實際中的應用
1.使用時需要導入頭文件<objc/message.h> <objc/runtime.h>
2.Runtime運行時機制,它是一套C語言庫。
3.實際上我們編寫所有OC代碼,最終都是轉成了runtime庫的東西。
比如:
類轉換成了Runtime庫里面的結構體等數據類型。
方法轉成了 Runtime庫里面的C語言函數‘
平時調方法都是轉成了objc_msgSend函數(所以說OC有個消息發(fā)送機制)
4.因此,可以說Runtime是OC的底層實現,是OC的膜厚執(zhí)行者。
實際中的應用:
1.動態(tài)給分類添加屬性
2.方法交換
3.字典轉模型
4.獲取所有的私有屬性和方法
5.對私有屬性進行修改
6.動態(tài)添加方法
7.動態(tài)創(chuàng)建類
四十一、什么是 Method Swizzle(黑魔法),什么情況下會使用?
1). 在沒有一個類的實現源碼的情況下,想改變其中一個方法的實現,除了繼承它重寫、和借助類別重名方法暴力搶先之外,還有更加靈活的方法 Method Swizzle。
2). Method Swizzle 指的是改變一個已存在的選擇器對應的實現的過程。OC中方法的調用能夠在運行時通過改變,通過改變類的調度表中選擇器到最終函數間的映射關系。
3). 在OC中調用一個方法,其實是向一個對象發(fā)送消息,查找消息的唯一依據是selector的名字。利用OC的動態(tài)特性,可以實現在運行時偷換selector對應的方法實現。
4). 每個類都有一個方法列表,存放著selector的名字和方法實現的映射關系。IMP有點類似函數指針,指向具體的方法實現。
5). 我們可以利用 method_exchangeImplementations 來交換2個方法中的IMP。
6). 我們可以利用 class_replaceMethod 來修改類。
7). 我們可以利用 method_setImplementation 來直接設置某個方法的IMP。
8). 歸根結底,都是偷換了selector的IMP。
四十二、runLoop?
1.概念:
runloop是通過內部維護的事件循環(huán)(EventLoop)來對事件/消息進行管理的一個對象。
- 沒有消息處理時,休眠已避免資源占用,由用戶狀態(tài)切換到內核狀態(tài)
- 有消息處理時,立即被喚醒,由內核狀態(tài)切換到用戶狀態(tài)。
每條線程都有一個runloop,但是默認都不開啟runloop
2.目的
1.保證runloop所在線程不退出
2.負責監(jiān)聽事件(觸摸、timer、內核)
為什么咱們的main函數不會退出?
由于UIApplicationMain函數內部默認開啟了主線程的RunLoop,保證運行循環(huán)。
UIApplicationMain函數一直沒有返回,而是不斷的接收處理消息以及等待休眠,所以運行程序才會保持持續(xù)運行的狀態(tài)。
3.RunLoop的數據結構?
NSRunLoop(Foundation)是CFRunLoop(CoreFoundation)的封裝,提供了面向對象的API。
主要設計五個類:
- CFRunLoop:RunLoop對象
- CFRunLoopMode:運行模式
- CFRunLoopSource:輸入源/事件源
- CFRunLoopTimer:定時源
- CFRunLoopObserver:觀察者
CFRunLoopMode由name、source0(非基于port的)、source1(基于port的)、observers、timers構成
CFRunLoopSource分為source0(用戶觸發(fā)的事件。需要手動喚醒線程,將當前線程從內核狀態(tài)切換到用戶狀態(tài))和source1(接收系統(tǒng)分發(fā)事件)
CFRunLoopTimer:基于事件的觸發(fā)器,基本上來說就是NSTimer。NSTimer不準確的原因就是當前線程正在處理過多的繁重任務,這時Timer會延遲。
CFRunLoopObserver監(jiān)聽以下的時間點:
- 1.KCFRunLoopEntry: runloop準備啟動
- 2.KCFRunLoopBeforeTimers:runloop將要處理一些Timer相關事件
- 3.KCFRunLoopBeforeSources:runloop將要處理一些source事件
- 4.KCFRunLoopBeforeWaiting:runloop將要進入休眠狀態(tài),即由用戶態(tài)切換為內核態(tài)
- 5.KCFRunLoopAfterWaiting:runloop被喚醒,即將從內核態(tài)切為用戶態(tài)
- 6.KCFRunLoopExit:runloop退出
- 7.KCFRunLoopAllActivities:監(jiān)聽所有狀態(tài)
4.CFRunLoopMode:總共5種,程序員一般用3種
- KCFRunLoopDefaultMode:默認模式,主線程是在這個運行模式下運行的
- UITrackingRunLoopMode:跟蹤用戶交互事件,優(yōu)先級最高,只能被觸摸事件喚醒
- KCFRunLoopCommonModes:偽模式,不是一種真正的運行模式,是同步source/timer/observer到多個mode中的解決方案。
- UIInitialzationRunLoopMode:在啟動app的時進入的第一個mode,啟動完成后不再使用
- EventReceiveRunLoopMode:接收系統(tǒng)內部事件
5.runloop的實現機制?

A. 通知觀察者runLoop即將啟動
B. 通知觀察者runLoop即將處理Timer事件
C. 通知觀察者runLoop即將處理source0事件
D. 處理source0事件
E. 如果有source1事件,則處理喚醒時收到的消息,之后再回到通知觀察者即將處理timer事件
F. 通知觀察者,線程即將休眠
G. 通知觀察者線程即將喚醒
H. 處理喚醒時收到的消息,之后再跳轉到通知觀察者,即將處理timer事件
I. 通知觀察者,即將runloop結束
6.一般應用?
1.NSTimer事件中的應用
2.創(chuàng)建一個常駐線程
3.保證子線程數據回來時不打斷用戶的滑動操作
我們可以將更新UI的事件放到主線程的NSDefaultRunLoopMode上執(zhí)行,這樣當用戶不在滑動的時候才去進行刷新UI。-
4.runLoop處理tableView快速滑動時,掉幀的問題。比如:cell上加載超級大的圖片,它是個消耗性能的過程。這時可能造成卡頓。因此可以使用runLoop進行優(yōu)化。
我們需要在一個RunLoop循環(huán)加載一張圖片,這時使用 NSRunLoop當然是不行的,因為他沒有提供對應的接口。所以需要CFRunLoop了,創(chuàng)建一個CFRunLoop,mode使用commonMode。傳入一個timer維持runloop循環(huán),加載一個圖片是一個任務,在每個runloop循環(huán)時調用block這個任務進入加載圖片。
四十三、Block?
block是將函數及其執(zhí)行上下文封裝起來的對象。
__block_impl 結構體為

block內部有isa指針,所以說其本質是OC對象。
block的幾種形式?
A. 全局block:不使用外部變量的block是全局block
B. 棧block:使用外部變量但未進行copy操作的的block是棧block
C. 堆block:對棧block進行copy就會copy到堆區(qū),對堆區(qū)的block進行copy就會增加引用計數
block變量截?。?br>
A.局部變量截取的是值截獲
B.局部靜態(tài)變量是指針截獲
C.類對象的全局變量是指針截獲
block使用?
A.作為屬性使用
B.作為函數參數使用
C.作為返回值使用(類似Masonry)
四十四、NSOperation和GCD的主要區(qū)別?
1.GCD的核心是C語言寫的系統(tǒng)服務,執(zhí)行和操作簡單高效,因此NSOperation底層也通過GCD實現,換個說法就是NSOperation是對GCD更高層次的抽象,這是他們之間的本質區(qū)別。
2.依賴關系,NSOperation可以設置兩個NSOperation之間的依賴,第二個任務依賴第一個任務執(zhí)行,GCD無法設置依賴關系,但是可以通過dispatch_barrier_async來實現這種效果。
3.優(yōu)先級,NSOperation可以設置自身的優(yōu)先級,但是優(yōu)先級高的不一定先執(zhí)行,GCD只能設置隊列的優(yōu)先級,無法在執(zhí)行的block設置優(yōu)先級。
4.繼承,NSOperation是一個抽象類,實際開發(fā)中常用的兩個類是NSInvocationOperation和NSBlockOperation,同樣我們可以自定義NSOperation,GCD執(zhí)行任務可以自由組裝,沒有繼承那么高的代碼復用度。
5.效率,直接使用GCD效率確實會更高效,NSOperation會多一點開銷,但是NSOperation可以設置依賴,優(yōu)先級,最大并發(fā)數,繼承,線程自主管理的優(yōu)勢。
NSOperation中沒有串行和并行的名詞。它是GCD特有的,NSOperatio通過設置最大并發(fā)數為1,達到串行的目的。
四十五、HTTP協(xié)議?
HTTP協(xié)議是超文本傳輸協(xié)議。它是基于TCP的應用層協(xié)議。
網絡七層模型從上到下分別是:
- 1.應用層
- 2.表示層
- 3.會話層
- 4.傳輸層
- 5.網絡層
- 6.數據鏈路層
- 7.物理層
1.請求報文和響應報文?

請求報文?
請求頭:POST someDir/page.html HTTP/1.1
包含了:請求方法、URL、HTTP版本號
請求頭部:Host:www.baidu.com 域名
Content-Type: application/x-www.form-ulencoded
Connection: Keep-Alive 連接方式(Keep-Alive告訴服務器使用持續(xù)連接)
User-agent: 向服務器發(fā)送請求的瀏覽器類型
Accept-lauguage: 接收的語言,如果沒有就使用默認的
空行:空行分割header和請求內容
請求體:
響應報文?
狀態(tài)行:HTTP/1.1 200 OK
包含了:協(xié)議版本、狀態(tài)碼、短語
響應頭部:Content-Type: text/html 實體對象類型
Connection: close連接方式(close告訴客戶端,發(fā)送完報文后將關閉TCP連接)
Content-Length: 122 發(fā)送對象中的字節(jié)數
Server: Apache/2.2.3 向客戶端發(fā)送服務器類型
Data: Sat, 31 Dec 2005 23:59:59 GMT 服務器從文件系統(tǒng)中檢索到該對象,插入到響應報文,并發(fā)送響應報文的時間
空行:
響應體:
2.HTTP的請求方式?
GET、POST、PUT、Delete、Head、Options
1.Get和Post方式的區(qū)別?
從語法上看:
Get的請求參數一般以“?”分割拼接到URL后面,POST請求參數在Body里面
Get參數長度限制為2048個字符,POST一般是沒有限制的
Get請求參數由于裸露在URL中,是不安全的,Post請求相對安全,如果Post請求被抓包,則Post一樣不安全。
從語義的角度看:
Get:獲取資源是安全的,冪等的(只讀的,同一個請求方法執(zhí)行多次和一次的效果是一樣的)、可緩存的
Post:獲取資源時非安全的,非冪等的,不可緩存的
3.GET和POST本質上都是TCP連接,并無差別。但是由于HTTP的規(guī)定和瀏覽器/服務器的限制,導致他們在應用過程找那個體現出一些不同。
在響應時,Get產生了一個TCP數據包,POST產生了兩個TCP數據包。
對于Get方式的請求,瀏覽器會把Header和實體主體一并發(fā)送出去,服務器響應200返回數據
對于Post,瀏覽器先發(fā)送Header,服務器響應100 Continue,瀏覽器再發(fā)送實體,服務器響應200返回數據。
4.Get相對于Post的優(yōu)勢是什么?
1.最優(yōu)勢就是方便。Get的URL可以直接手輸,從而Get請求中的Url可以被存到書簽中。
2.可以被緩存,大大減輕服務器的負擔。
四十六、三次握手?
- SYN:代表請求創(chuàng)建連接,所以在三次握手中前兩次要SYN=1,表示兩次用于建立連接。
- FIN: 代表請求關閉連接。一次FIN只能關閉一個方向的連接。
- ACK:代表確認接收,三次握手和四次揮手時都會加上ACK=1,表示消息接收到了。
- seq:序列號,當發(fā)送一個數據時,數據被拆分成多個數據包發(fā)送,序列號就是對每個數據包進行編碼,這樣接收方才能對數據包進行再次拼接。初始序列號是隨機生成的。
- ack:代表下一個數據包的編號,所以第二個請求時,ack = seq + 1

過程如下:
第一步:客戶端向服務端發(fā)送一個SYN包,客戶端進入SYN_SENT狀態(tài)。
第二步:服務端收到SYN數據包后,進入SYN_RCVD狀態(tài),同時向客戶端發(fā)送一個SYN_ACK數據包。
第三步:客戶端收到SYN_ACK數據包后,客戶端進入established(建立連接)狀態(tài),同時向服務端發(fā)送一個數據包,服務端收到該數據包也進入established狀態(tài)。這時三次握手完成。
四十七、四次揮手?
參與TCP連接的兩個進程中的任何一個都能終止該連接。

以客戶端發(fā)起終止連接為例:
- 第一步:客戶端應用發(fā)出一個關閉連接的指令。FIN置為1,同時客戶端進入FIN_WAIT_1狀態(tài),等待服務端的帶有確認的TCP報文段。
- 第二步:服務端收到報文段后向客戶端發(fā)送一個確認報文段。服務端進入Close_Wait狀態(tài),對應客戶端的time_wait狀態(tài),表示被動被動關閉。客戶端收到該報文后進入FIN_Wait_2狀態(tài),等待服務端的FIN置為1的報文。
- 第三步:服務端發(fā)送自己的終止報文段,服務端進入Last_ACK狀態(tài),等待客戶端最后的確認報文段。
- 第四部:客戶端收到服務端的終止報文段后,向服務端發(fā)送一個確認報文段。同時客戶端進入time_wait狀態(tài)最后進入close狀態(tài)。服務端收到該報文段后,同樣關閉,重新進入close狀態(tài)。
四十八、為什么建立連接只用三次握手,而斷開連接需要四次揮手?
- 1.首先,當客戶端數據發(fā)送完畢后,且知道服務端也全部接收到了時,就會斷開。
- 2.服務端接收到客戶端的FIN,為了表示接收到了,就會向客戶端發(fā)送了ACK
- 3.但是此時,服務端可能還在發(fā)送數據,并沒有關閉TCP窗口的意思,所以服務端的FIN和ACK并不是同步發(fā)送的,只有當數據發(fā)送完畢了,才會發(fā)送FIN
*答:服務端的FIN和ACK需要分開發(fā)送,并不像三次握手那樣,SYN可以和ACK同步發(fā)送,所以需要四次握手。
四十九、在四次揮手中,客戶端為什么在time_wait后必須等待2ML時間呢?
最后客戶端發(fā)送給服務端的ACK報文段可能丟失,這樣服務端在2MSL時間內收不到ACK報文段就會重復發(fā)送FIN報文段。
客戶端在2MSL(1到4分鐘)時間內收不到重傳的FIN報文段在發(fā)送ACK報文段,直到服務端收到,客戶端和服務端就會進入到closed狀態(tài),關閉TCP連接
答:為了保證客戶端發(fā)送的最后一個ACK報文段能夠達到服務端
五十、TCP在創(chuàng)建連接時,為什么需要三次握手而不是兩次握手?
答:兩次握手會可能導致連接沒有建立起來,但是服務器以為建立起來了,這樣服務端在給客戶端發(fā)送數據時造成了服務端資源的浪費。四次握手浪費資源,沒必要。
五十一、HTTP和HTTPS的區(qū)別?
HTTPS協(xié)議 = HTTP協(xié)議 + SSL/TLS協(xié)議
SSL全稱是Secure Socket Layer,即安全套接層協(xié)議,是為網絡通信提供安全及數據完整性的一種安全協(xié)議。
TLS的全稱是Transport Layer Security,即安全傳輸層協(xié)議。
即HTTPS就是安全的HTTP。
五十二、HTTPS的連接建立流程?
HTTPS為了兼顧安全與效率,同時使用了對稱加密和非對稱加密。在傳輸的過程中涉及到了三個秘鑰:
服務端的公鑰和私鑰,用來進行非對稱加密。
客戶端的隨機秘鑰,用來進行對稱加密。
