目錄
1.用什么方式判斷gif/png圖片的?
2.什么是鏈表,鏈表逆序怎么實現(xiàn)?
3.Swift與OC的區(qū)別?
4.Swift 中的Any 與 AnyObject的區(qū)別?
5.如何使用Swift 中的weak與unowned?
6.instancetype和id的異同
7.iOS內(nèi)存泄露及檢測方法
8.NSString使用copy或strong
9.AFN為什么添加一條常駐線程?
10. 控件主要響應(yīng)3種事件
11. xib文件的構(gòu)成分為哪3個圖標(biāo)?都具有什么功能。
12.UIView與CLayer有什么區(qū)別?
13. Quatrz 2D的繪圖功能的三個核心概念是什么并簡述其作用。
14.有哪幾種手勢通知方法、寫清楚方法名?
15.CFSocket使用有哪幾個步驟。
16. ios 平臺怎么做數(shù)據(jù)的持久化?coredata 和sqlite有無必然聯(lián)系?coredata是一個關(guān)系型數(shù)據(jù)庫嗎?
17.OC的理解與特性
18. Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時執(zhí)行代碼、方法又是什么?
19.Objective-C 中是否支持垃圾回收機(jī)制?
20.循環(huán)引用的產(chǎn)生原因,以及解決方法
21.switch 語句 if 語句區(qū)別與聯(lián)系
22.如何查找某一導(dǎo)航棧中是否有某一個特定VC,isMemberOfClass 和 isKindOfClass 聯(lián)系與區(qū)別
23.在多人合作開發(fā)環(huán)境下,可能會有重復(fù)添加KVO的時候,或者重復(fù)移除KVO的時候,重復(fù)添加或刪除KVO會怎么樣?采取什么方案去解決?
24.如果讓你去設(shè)計SDWebImage的緩存機(jī)制,怎么去設(shè)計?
25.如果一個自定義的Cell可能被用在多處,每處后臺返回的數(shù)據(jù)源模型(json原始數(shù)據(jù))字段都是不同的,如果讓你去設(shè)計一種Model去兼容所有的業(yè)務(wù)需求,即不管原始數(shù)據(jù)什么樣都能一樣進(jìn)行解析賦給Cell,你怎么去設(shè)計?
26.__block 能修改外部變量根本原因是什么?
27.自動釋放池中的變量什么時候釋放
28.block根據(jù)內(nèi)存區(qū)間分為幾種類型?
29.block在什么時候會造成循環(huán)引用,如何避免循環(huán)引用以及weakSelf、strongSelf的區(qū)別。
30.通知中心添加事件監(jiān)聽需要注意什么?如果添加監(jiān)聽后忘記移除監(jiān)聽會有什么樣的坑?通知中心是什么設(shè)計模式的實現(xiàn),如何實現(xiàn)這種模式?postNotification方法是同步的還是異步的?
31. ios crash的原因與抓取crash日志的方法
32.OC的消息轉(zhuǎn)發(fā)機(jī)制
1.用什么方式判斷gif/png圖片的?
取出圖片數(shù)據(jù)的第一個字節(jié), 就可以判斷出圖片的真實類型
//通過圖片Data數(shù)據(jù)第一個字節(jié) 來獲取圖片擴(kuò)展名
- (NSString *)contentTypeForImageData:(NSData *)data {
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return @"jpeg";
case 0x89:
return @"png";
case 0x47:
return @"gif";
case 0x49:
case 0x4D:
return @"tiff";
case 0x52:
if ([data length] < 12) {
return nil;
}
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return @"webp";
}
return nil;
}
return nil;
}
使用方法
//假設(shè)這是一個網(wǎng)絡(luò)獲取的URL
NSString *path = @"http://www.yxzoo.com/uploads/allimg/160803/1-160P3113351.jpg";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:path]];
//調(diào)用獲取圖片擴(kuò)展名
NSString *string = [self contentTypeForImageData:data];
//輸出結(jié)果為 jpeg
NSLog(@"%@",string);
2.什么是鏈表,鏈表逆序怎么實現(xiàn)?
鏈表(Linkedlist)是一種常見的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),是一種線性表,是一種物理存儲單元上非連續(xù)、非順序的存儲結(jié)構(gòu)。數(shù)據(jù)元素的邏輯順序是通過鏈表中的指針鏈接次序?qū)崿F(xiàn)的,但是并不會按線性的順序存儲數(shù)據(jù),鏈表通常由一連串節(jié)點組成,每個節(jié)點包含任意的實例數(shù)據(jù)(datafields)和一或兩個用來指向上一個/或下一個節(jié)點的位置的鏈接("links")。
鏈表的結(jié)構(gòu)
鏈表最基本的結(jié)構(gòu)是在每個節(jié)點保存數(shù)據(jù)和到下一個節(jié)點的地址,在最后一個節(jié)點保存一個特殊的結(jié)束標(biāo)記。另外在一個固定的位置保存指向第一個節(jié)點的指針,有的時候也會同時儲存指向最后一個節(jié)點的指針。但是也可以提前把一個節(jié)點的位置另外保存起來,然后直接訪問。當(dāng)然如果只是訪問數(shù)據(jù)就沒必要了,不如在鏈表上儲存指向?qū)嶋H數(shù)據(jù)的指針。這樣一般是為了訪問鏈表中的下一個或者前一個節(jié)點。
優(yōu)勢:可以克服數(shù)組鏈表需要預(yù)先知道數(shù)據(jù)大小的缺點,鏈表結(jié)構(gòu)可以充分利用計算機(jī)內(nèi)存空間,實現(xiàn)靈活的內(nèi)存動態(tài)管理。鏈表最明顯的好處就是,常規(guī)數(shù)組排列關(guān)聯(lián)項目的方式可能不同于這些數(shù)據(jù)項目在記憶體或磁盤上順序,數(shù)據(jù)的存取往往要在不同的排列順序中轉(zhuǎn)換。而鏈表是一種自我指示數(shù)據(jù)類型,因為它包含指向另一個相同類型的數(shù)據(jù)的指針(鏈接),同時,鏈表允許插入和移除表上任意位置上的節(jié)點。
劣勢:鏈表由于增加了結(jié)點的指針域,空間開銷比較大;另外,鏈表失去了數(shù)組隨機(jī)讀取的優(yōu)點,一般查找一個節(jié)點的時候需要從第一個節(jié)點開始每次訪問下一個節(jié)點,一直訪問到需要的位置。
單向鏈表
鏈表中最簡單的一種是單向鏈表,
一個單向鏈表的節(jié)點被分成兩個部分。它包含兩個域,一個信息域和一個指針域。第一個部分保存或者顯示關(guān)于節(jié)點的信息,第二個部分存儲下一個節(jié)點的地址,而最后一個節(jié)點則指向一個空值。單向鏈表只可向一個方向遍歷。
雙向鏈表
雙向鏈表其實是單鏈表的改進(jìn),當(dāng)我們對單鏈表進(jìn)行操作時,有時你要對某個結(jié)點的直接前驅(qū)進(jìn)行操作時,又必須從表頭開始查找。這是由單鏈表結(jié)點的結(jié)構(gòu)所限制的。因為單鏈表每個結(jié)點只有一個存儲直接后繼結(jié)點地址的鏈域,那么能不能定義一個既有存儲直接后繼結(jié)點地址的鏈域,又有存儲直接前驅(qū)結(jié)點地址的鏈域的這樣一個雙鏈域結(jié)點結(jié)構(gòu)呢?這就是雙向鏈表。
在雙向鏈表中,結(jié)點除含有數(shù)據(jù)域外,還有兩個鏈域,一個存儲直接后繼結(jié)點地址,一般稱之為右鏈域(當(dāng)此“連接”為最后一個“連接”時,指向空值或者空列表);一個存儲直接前驅(qū)結(jié)點地址,一般稱之為左鏈域(當(dāng)此“連接”為第一個“連接”時,指向空值或者空列表)。
循環(huán)鏈表
循環(huán)鏈表是與單向鏈表一樣,是一種鏈?zhǔn)降拇鎯Y(jié)構(gòu),所不同的是,循環(huán)鏈表的最后一個結(jié)點的指針是指向該循環(huán)鏈表的第一個結(jié)點或者表頭結(jié)點,從而構(gòu)成一個環(huán)形的鏈。
循環(huán)鏈表的運算與單鏈表的運算基本一致。所不同的有以下幾點:
1、在建立一個循環(huán)鏈表時,必須使其最后一個結(jié)點的指針指向表頭結(jié)點,而不是象單鏈表那樣置為NULL。此種情況還使用于在最后一個結(jié)點后插入一個新的結(jié)點。
2、在判斷是否到表尾時,是判斷該結(jié)點鏈域的值是否是表頭結(jié)點,當(dāng)鏈域值等于表頭指針時,說明已到表尾。而非象單鏈表那樣判斷鏈域值是否為NULL。
塊狀鏈表
塊狀鏈表本身是一個鏈表,但是鏈表儲存的并不是一般的數(shù)據(jù),而是由這些數(shù)據(jù)組成的順序表。每一個塊狀鏈表的節(jié)點,也就是順序表,可以被叫做一個塊。
塊狀鏈表另一個特點是相對于普通鏈表來說節(jié)省內(nèi)存,因為不用保存指向每一個數(shù)據(jù)節(jié)點的指針。
常規(guī)寫法(迭代)
迭代算法效率較高,但是代碼比遞歸算法略長。遞歸算法雖然代碼量更少,但是難度也稍大,不細(xì)心容易寫錯。迭代算法的思想就是遍歷鏈表,改變鏈表節(jié)點next指向,遍歷完成,鏈表逆序也就完成了。代碼如下:
struct node {
int data;
struct node* next;
};
typedef struct node* pNode;
pNode reverse(pNode head)
{
pNode current = head;
pNode next = NULL, result = NULL;
while (current != NULL) {
next = current->next;
current->next = result;
result = current;
current = next;
}
return result;
}
如果不返回值,可以傳遞參數(shù)改為指針的指針,直接修改鏈表的頭結(jié)點值(如果在C++中直接傳遞引用更方便),可以寫出下面的代碼:
void reverse(pNode* headRef)
{
pNode current = *headRef;
pNode next = NULL, result = NULL;
while (current != NULL) {
next = current->next;
current->next = result;
result = current;
current = next;
}
*headRef = result;
}
進(jìn)階寫法(遞歸)
遞歸寫法的實現(xiàn)原理:假定原鏈表為1,2,3,4,則先逆序后面的2,3,4變?yōu)?,3,2,然后將節(jié)點1鏈接到已經(jīng)逆序的4,3,2后面,形成4,3,2,1,完成整個鏈表的逆序。代碼如下:
void reverseRecur(pNode* headRef)
{
if (*headRef == NULL) return;
pNode first, rest;
first = *headRef;
rest = first->next;
if (rest == NULL) return;
reverseRecur(&rest);
first->next->next = first; //注意這里不要錯寫成rest->next = first噢,請想想指針的指向。
first->next = NULL;
*headRef = rest; //更新頭結(jié)點
}
如果使用C++的引用類型,代碼會稍顯簡單點,代碼如下:
void reverseRecur(pNode& p)
{
if (!p) return;
pNode rest = p->next;
if (!rest) return;
reverseRecur(rest);
p->next->next = p;
p->next = NULL;
p = rest;
}
3.Swift與OC的區(qū)別?
不用#import 頭文件,取而代之的是在前面加共有或者私有的前綴。
少了隱式轉(zhuǎn)換(據(jù)說是為了安全),比如float 跟int。
加入可選值類型,并且因此出現(xiàn)強(qiáng)制解析,當(dāng)然也可以安全解析。
寫法上更加的偏向extension (擴(kuò)展)
-
常量和變量
- Swift常量使用let聲明;變量使用var聲明
- Swift對常量和變量有類型推斷的機(jī)制
- Swift對變量新增了可選類型,可選即表示這個變量要么有值,要么為nil
-
函數(shù)
- Swift一行代碼不用寫分號
- Swift的返回值可以使用元組返回多個值
- Swift的函數(shù)參數(shù)可以設(shè)置缺省值
- Swift的函數(shù)參數(shù)有內(nèi)外標(biāo)簽
- Swift的函數(shù)可以嵌套函數(shù)
- Swift子類覆蓋父類的方法必須使用關(guān)鍵字override
-
關(guān)鍵字、保留字、數(shù)據(jù)類型
- nil:OC中nil只能修飾NSObject及其子類對象,表示OC對象指針為空;Swift中nil可以修飾所有類型,包括基礎(chǔ)數(shù)據(jù)類型,表示值缺失
- switch:1. Swift中switch語句的值可以是字符串等值 2.Swift中switch不用break,如果想實現(xiàn)幾個值貫穿可以使用關(guān)鍵字fall through
- 數(shù)組:Swift的數(shù)據(jù)可以存儲基礎(chǔ)類型數(shù)據(jù);NSArray只能使用NSNumber存儲基礎(chǔ)數(shù)據(jù)類型數(shù)據(jù)
- 布爾類型:Swift的Bool類型true才為真;OC里BOOL類型非0即為真
- 取余:Swift可以對浮點型數(shù)據(jù)取余(Swift3.0后使用函數(shù)對浮點型數(shù)據(jù)取余;%與OC中保持一致)
- Swift中對變量取別名使用typealias;OC中使用typedef
-
Swift新增關(guān)鍵字、運算符
- 范圍運算符:
a...b表示[a, b],及a<= value <=b
a..<b表示[a, b),及a<= value <b - 元組
- 元組(tuples)把多個值組合成一個復(fù)合值。元組內(nèi)的值可以使任意類型,并不要求是相同類型。
- 范圍運算符:
4.Swift 中的Any 與 AnyObject的區(qū)別?
AnyObject:可以代表任何class類型的實例;
Any:可以代表任何類型,甚至包括方法(func)類型。
Any和AnyObject都是協(xié)議而且,并且從Apple提供的注釋中可以看出所有的type(類型)都隱式實現(xiàn)了Any協(xié)議,所有的class都隱式實現(xiàn)了AnyObject協(xié)議。
可以總結(jié)為:
- AnyObject是Any的子集
- 所有用class關(guān)鍵字定義的對象就是AnyObject
- 所有不是用class關(guān)鍵字定義的對象就不是AnyObject,而是Any
5.如何使用Swift 中的weak與unowned?
weak
含義:weak 即弱引用,當(dāng)把一個實例聲明為弱引用時,此實例不會持有這個對象,即不會使對象的引用計數(shù)加1。當(dāng)對象被廢棄,其所有的弱引用會被置為 nil。
適用場景:
- 類實例之間的循環(huán)強(qiáng)引用:實例之間形成引用循環(huán),且無法確定對象之間生命周期的依賴關(guān)系,即無法確定弱引用在某一時刻是否為空時,將可能為空的實例聲明為弱引用。由于弱引用可能為 nil,應(yīng)當(dāng)聲明為可選值。如;
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? //公寓的房客可能為空所以聲明為weak
deinit { print("Apartment \(unit) is being deinitialized") }
}
由于 tenant 是弱引用,當(dāng) tenant 引用的對象被銷毀(如賦值 nil),tenant 被置為空,并且釋放對 apartment的強(qiáng)引用,此時 apartment 所指對象就可以正常釋放了。
- 閉包引起的循環(huán)強(qiáng)引用:在被捕獲的引用可能會變?yōu)閚il時,在捕獲列表中,將閉包內(nèi)的捕獲定義為弱引用。
如:
// someClosure 是類成員變量
lazy var someClosure: (Int, String) -> String = {
//self.delegate 可能在被捕獲后變?yōu)?nil,所以定義為弱引用,unowned 解釋見下文
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 這里是閉包的函數(shù)體
}
由于 self.delegate 指向的是外部對象,生命周期與self無關(guān),所以可能在被捕獲后變?yōu)閚il。(delegate 一般都聲明為weak以避免循環(huán)引用)
unowned
含義:無主引用,與弱引用一樣,當(dāng)把一個實例聲明為無主引用時,此實例不會持有這個對象,即不會使對象的引用計數(shù)加1。但與弱引用不同的是,當(dāng)對象被廢棄,其無主引用并不會被置為 nil。
適用場景:
- 類實例之間的循環(huán)強(qiáng)引用:實例之間形成引用循環(huán),且可以確定某一實例之外的其他實例有相同或者更長的生命周期時,將此實例聲明為無主引用。無主引用總被期望擁有值,當(dāng)你訪問對象被銷毀的無主引用時,會觸發(fā)運行時錯誤。
class Country {
let name: String
var capitalCity: City! //由于 capitalCity 的生命周期等于 country 的生命周期,可以隱式解析可選屬性
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
雖然在這個例子中,capitalCity 與 country 的生命周期相同,理論上講將其中任何一個聲明為無主引用都可以打破引用循環(huán),但 capitalCity 與 country 之間有從屬關(guān)系,所以傾向于將“大”的一方,即 country 聲明為無主引用。
- 閉包引起的循環(huán)強(qiáng)引用:在閉包和捕獲的實例總是互相引用并且總是同時銷毀時,將閉包內(nèi)的捕獲定義為無主引用。
// someClosure 是類成員變量
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 這里是閉包的函數(shù)體
}
self 與屬于 self 的成員變量 someClosure 生命周期相同,同時銷毀,所以聲明為無主引用。
6.instancetype和id的異同
1、相同點
都可以作為方法的返回類型
2、不同點
①instancetype可以返回和方法所在類相同類型的對象,id只能返回未知類型的對象;
②instancetype只能作為返回值,不能像id那樣作為參數(shù)
7.iOS內(nèi)存泄露及檢測方法
內(nèi)存泄漏出現(xiàn):
- AFNet, //請求隊列管理者 單例創(chuàng)建形式 防止內(nèi)存泄漏
- Block循環(huán)引用
防止Block循環(huán)引用就是要防止對象之間引用的閉環(huán)出現(xiàn)。 - delegate循環(huán)引用問題
delegate循環(huán)引用問題比較基礎(chǔ),只需注意將代理屬性修飾為weak即可 - NSTimer循環(huán)引用
若將cleanTimer方法調(diào)用在dealloc方法中會產(chǎn)生如下問題,當(dāng)前類銷毀執(zhí)行dealloc的前提是定時器需要停止并滯空,而定時器停止并滯空的時機(jī)在當(dāng)前類調(diào)用dealloc方法時,這樣就造成了互相等待的場景,從而內(nèi)存一直無法釋放。因此需要注意cleanTimer的調(diào)用時機(jī)從而避免內(nèi)存無法釋放,如上的解決方案為將cleanTimer方法外漏,在外部調(diào)用即可。 - 非OC對象內(nèi)存處理
- 地圖類處理
若項目中使用地圖相關(guān)類,一定要檢測內(nèi)存情況,因為地圖是比較耗費App內(nèi)存的,因此在根據(jù)文檔實現(xiàn)某地圖相關(guān)功能的同時,我們需要注意內(nèi)存的正確釋放,大體需要注意的有需在使用完畢時將地圖、代理等滯空為nil,注意地圖中標(biāo)注(大頭針)的復(fù)用,并且在使用完畢時清空標(biāo)注數(shù)組等。
內(nèi)存泄漏檢測:
- 通過Xcode中Product->Analyze靜態(tài)分析代碼,找出潛在的內(nèi)存泄露。
方案1的優(yōu)點是不需要寫代碼,只需要運行一次,就能檢測出潛在的內(nèi)存泄露;缺點是由于是靜態(tài)代碼檢查,無法覆蓋全部場景(比如動態(tài)運行場景),有可能誤報。 - 使用Xcode自帶工具Instruments來檢測內(nèi)存泄露。
方案2的優(yōu)點是不需要寫代碼,直接運行Instruments工具進(jìn)行檢測,適用于開發(fā)、測試來使用;缺點是需要一個一個頁面去點擊。 - 微信讀書開源的內(nèi)存泄露檢測庫MLeaksFinder,MLeaksFinder具有如下功能:
- 1、能檢測出內(nèi)存泄露和循環(huán)引用,并且彈框提醒。
- 2、只在Debug模式下起作用,Release不起作用。
- 3、支持檢查VC和View里面任意對象的內(nèi)存泄露。
8.NSString使用copy或strong
1.__NSCFConstantString
這些對象地址相同,是因為他們都是__NSCFConstantString對象,也就是字符串常量對象,可以看到其isa都是__NSCFConstantString,該對象存儲在棧上,創(chuàng)建之后由系統(tǒng)來管理內(nèi)存釋放,相同內(nèi)容的NSCFConstantString對象地址相同。該對象引用計數(shù)很大,為固定值不會變化,表示無限運行的retainCount,對其進(jìn)行retain或release也不會影響其引用計數(shù)。
當(dāng)創(chuàng)建一個NSCFConstantString對象時,會檢測這個字符串內(nèi)容是否已經(jīng)存在,如果存在,則直接將地址賦值給變量;不存在的話,則創(chuàng)建新地址,再賦值。
對于NSCFConstantString對象,只要字符串內(nèi)容不變,就不會分配新的內(nèi)存地址,無論你是賦值、retain、copy。這種優(yōu)化在大量使用NSString的情況下可以節(jié)省內(nèi)存,提高性能。
2.__NSCFString
__NSCFString對象是一種NSString子類,存儲在堆上,不屬于字符串常量對象。該對象創(chuàng)建之后和其他的Obj對象一樣引用計數(shù)為1,對其執(zhí)行retain和release將改變其retainCount。
3.聲明NSString屬性一般來說用copy,因為父類指針可以指向子類對象,而NSMutableNSString是NSString的子類,使用strong的話該NSString屬性可能指向一個NSMutableNSString可變對象,如果這個可變對象的內(nèi)容在外部被修改了,那該屬性所屬的對象可能對此毫不知情。
聲明NSString為屬性時,如果希望保護(hù)屬性封裝性不受外界影響,則應(yīng)該使用copy關(guān)鍵字,讓所屬對象持有的是一份“不可變”(immutable)副本,不用擔(dān)心字符串內(nèi)容無意間變動。
9.AFN為什么添加一條常駐線程?
主線程是UI線程 你要在這做超過16ms的事情就會卡頓,時間再長就會造成界面卡死。 一般rom對主線程卡住10s以上的應(yīng)用都會ANR,讓用戶選擇強(qiáng)制殺死應(yīng)用。
10. 控件主要響應(yīng)3種事件
答:1). 基于觸摸的事件 ; 2). 基于值的事件 ; 3).基于編輯的事件。
11. xib文件的構(gòu)成分為哪3個圖標(biāo)?都具有什么功能。
答: File’s Owner 是所有 nib 文件中的每個圖標(biāo),它表示從磁盤加載 nib 文件的對象;
First Responder 就是用戶當(dāng)前正在與之交互的對象;
View 顯示用戶界面;完成用戶交互;是 UIView 類或其子類。
12.UIView與CLayer有什么區(qū)別?
答:
1).UIView 是 iOS 系統(tǒng)中界面元素的基礎(chǔ),所有的界面元素都是繼承自它。它本身完全是由 CoreAnimation 來實現(xiàn)的。它真正的繪圖部分,是由一個 CALayer 類來管理。 UIView 本身更像是一個 CALayer 的管理器,訪問它的跟繪圖和跟坐標(biāo)有關(guān)的屬性。
2).UIView 有個重要屬性 layer ,可以返回它的主 CALayer 實例。
3).UIView 的 CALayer 類似 UIView 的子 View 樹形結(jié)構(gòu),也可以向它的 layer 上添加子layer ,來完成某些特殊的表示。即 CALayer 層是可以嵌套的。
4).UIView 的 layer 樹形在系統(tǒng)內(nèi)部,被維護(hù)著三份 copy 。分別是邏輯樹,這里是代碼可以操縱的;動畫樹,是一個中間層,系統(tǒng)就在這一層上更改屬性,進(jìn)行各種渲染操作;顯示樹,其內(nèi)容就是當(dāng)前正被顯示在屏幕上得內(nèi)容。
5).動畫的運作:對 UIView 的 subLayer (非主 Layer )屬性進(jìn)行更改,系統(tǒng)將自動進(jìn)行動畫生成,動畫持續(xù)時間的缺省值似乎是 0.5 秒。
6).坐標(biāo)系統(tǒng): CALayer 的坐標(biāo)系統(tǒng)比 UIView 多了一個 anchorPoint 屬性,使用CGPoint 結(jié)構(gòu)表示,值域是 0~1 ,是個比例值。這個點是各種圖形變換的坐標(biāo)原點,同時會更改 layer 的 position 的位置,它的缺省值是 {0.5,0.5} ,即在 layer 的中央。
7).渲染:當(dāng)更新層,改變不能立即顯示在屏幕上。當(dāng)所有的層都準(zhǔn)備好時,可以調(diào)用setNeedsDisplay 方法來重繪顯示。
8).變換:要在一個層中添加一個 3D 或仿射變換,可以分別設(shè)置層的 transform 或affineTransform 屬性。
9).變形: Quartz Core 的渲染能力,使二維圖像可以被自由操縱,就好像是三維的。圖像可以在一個三維坐標(biāo)系中以任意角度被旋轉(zhuǎn),縮放和傾斜。 CATransform3D 的一套方法提供了一些魔術(shù)般的變換效果。
13. Quatrz 2D的繪圖功能的三個核心概念是什么并簡述其作用。
答:上下文:主要用于描述圖形寫入哪里;
路徑:是在圖層上繪制的內(nèi)容;
狀態(tài):用于保存配置變換的值、填充和輪廓, alpha 值等。
14.有哪幾種手勢通知方法、寫清楚方法名?
答:
-(void)touchesBegan:(NSSet)touchedwithEvent:(UIEvent)event;
-(void)touchesMoved:(NSSet)touched withEvent:(UIEvent)event;
-(void)touchesEnded:(NSSet)touchedwithEvent:(UIEvent)event;
-(void)touchesCanceled:(NSSet)touchedwithEvent:(UIEvent)event;
15.CFSocket使用有哪幾個步驟。
答:創(chuàng)建 Socket 的上下文;創(chuàng)建 Socket ;配置要訪問的服務(wù)器信息;封裝服務(wù)器信息;連接服務(wù)器;
16. ios 平臺怎么做數(shù)據(jù)的持久化?coredata 和sqlite有無必然聯(lián)系?coredata是一個關(guān)系型數(shù)據(jù)庫嗎?
答:iOS 中可以有四種持久化數(shù)據(jù)的方式:屬性列表(plist)、對象歸檔、 SQLite3 和 Core Data;
1>屬性列表:只有NSString、NSArray、NSDictionary、NSData可writeToFile;存儲依舊是plist文件。plist文件可以存儲的7中數(shù)據(jù)類型:array、dictionary、string、bool、data、date、number。
2>對象序列化(對象歸檔):對象序列化通過序列化的形式,鍵值關(guān)系存儲到本地,轉(zhuǎn)化成二進(jìn)制流。通過runtime實現(xiàn)自動化歸檔/解檔,實現(xiàn)NSCoding協(xié)議必須實現(xiàn)的兩個方法:
1.編碼(對象序列化):把不能直接存儲到plist文件中得到數(shù)據(jù),轉(zhuǎn)化為二進(jìn)制數(shù)據(jù),NSData,可以存儲到本地;
2.解碼(對象反序列化):把二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為本來的類型。
3>SQLite 數(shù)據(jù)庫:大量有規(guī)律的數(shù)據(jù)使用數(shù)據(jù)庫。
4>CoreData :通過管理對象進(jìn)行增、刪、查、改操作的。它不是一個數(shù)據(jù)庫,不僅可以使用SQLite數(shù)據(jù)庫來保持?jǐn)?shù)據(jù),也可以使用其他的方式來存儲數(shù)據(jù)。如:XML。
core data 可以使你以圖形界面的方式快速的定義 app 的數(shù)據(jù)模型,同時在你的代碼中容易獲取到它。 coredata 提供了基礎(chǔ)結(jié)構(gòu)去處理常用的功能,例如保存,恢復(fù),撤銷和重做,允許你在 app 中繼續(xù)創(chuàng)建新的任務(wù)。在使用 core data 的時候,你不用安裝額外的數(shù)據(jù)庫系統(tǒng),因為 core data 使用內(nèi)置的 sqlite 數(shù)據(jù)庫。 core data 將你 app 的模型層放入到一組定義在內(nèi)存中的數(shù)據(jù)對象。 coredata 會追蹤這些對象的改變,同時可以根據(jù)需要做相反的改變,例如用戶執(zhí)行撤銷命令。當(dāng) core data 在對你 app 數(shù)據(jù)的改變進(jìn)行保存的時候, core data 會把這些數(shù)據(jù)歸檔,并永久性保存。 mac os x 中sqlite 庫,它是一個輕量級功能強(qiáng)大的關(guān)系數(shù)據(jù)引擎,也很容易嵌入到應(yīng)用程序。可以在多個平臺使用, sqlite 是一個輕量級的嵌入式 sql 數(shù)據(jù)庫編程。與 core data 框架不同的是, sqlite 是使用程序式的, sql 的主要的 API 來直接操作數(shù)據(jù)表。 Core Data 不是一個關(guān)系型數(shù)據(jù)庫,也不是關(guān)系型數(shù)據(jù)庫管理系統(tǒng) (RDBMS) 。雖然 Core Dta 支持SQLite 作為一種存儲類型,但它不能使用任意的 SQLite 數(shù)據(jù)庫。 Core Data 在使用的過程種自己創(chuàng)建這個數(shù)據(jù)庫。 Core Data 支持對一、對多的關(guān)系。
17.OC的理解與特性
OC作為一門面向?qū)ο蟮恼Z言,自然具有面向?qū)ο蟮恼Z言特性:封裝、繼承、多態(tài)。它既具有靜態(tài)語言的特性(如C++),又有動態(tài)語言的效率(動態(tài)綁定、動態(tài)加載等)。總體來講,OC確實是一門不錯的編程語言,
Objective-C具有相當(dāng)多的動態(tài)特性,表現(xiàn)為三方面:動態(tài)類型(Dynamic typing)、動態(tài)綁定(Dynamic binding)和動態(tài)加載(Dynamic loading)。動態(tài)——必須到運行時(run time)才會做的一些事情。
- 動態(tài)類型:即運行時再決定對象的類型,這種動態(tài)特性在日常的應(yīng)用中非常常見,簡單來說就是id類型。事實上,由于靜態(tài)類型的固定性和可預(yù)知性,從而使用的更加廣泛。靜態(tài)類型是強(qiáng)類型,而動態(tài)類型屬于弱類型,運行時決定接受者。
- 動態(tài)綁定:基于動態(tài)類型,在某個實例對象被確定后,其類型便被確定了,該對象對應(yīng)的屬性和響應(yīng)消息也被完全確定。
- 動態(tài)加載:根據(jù)需求加載所需要的資源,最基本就是不同機(jī)型的適配,例如,在Retina設(shè)備上加載@2x的圖片,而在老一些的普通蘋設(shè)備上加載原圖,讓程序在運行時添加代碼模塊以及其他資源,用戶可根據(jù)需要加載一些可執(zhí)行代碼和資源,而不是在啟動時就加載所有組件,可執(zhí)行代碼可以含有和程序運行時整合的新類。
18. Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時執(zhí)行代碼、方法又是什么?
答:線程創(chuàng)建有三種方法:使用NSThread創(chuàng)建、使用GCD的dispatch、使用子類化的NSOperation,然后將其加入NSOperationQueue;在主線程執(zhí)行代碼,方法是performSelectorOnMainThread,如果想延時執(zhí)行代碼可以用performSelector:onThread:withObject:waitUntilDone:
19.Objective-C 中是否支持垃圾回收機(jī)制?
OC是支持垃圾回收機(jī)制的(Garbage collection簡稱GC),但是apple的移動終端中,是不支持GC的,Mac桌面系統(tǒng)開發(fā)中是支持的.
移動終端開發(fā)是支持ARC(Automatic Reference Counting的簡稱),ARC是在IOS5之后推出的新技術(shù),它與GC的機(jī)制是不同的。我們在編寫代碼時, 不需要向?qū)ο蟀l(fā)送release或者autorelease方法,也不可以調(diào)用delloc方法,編譯器會在合適的位置自動給用戶生成release消息(autorelease),ARC 的特點是自動引用技術(shù)簡化了內(nèi)存管理的難度.
20.循環(huán)引用的產(chǎn)生原因,以及解決方法
產(chǎn)生原因:對象A和對象B相互引用了對方作為自己的成員變量,只有自己銷毀的時候才能將成員變量的引用計數(shù)減1。對象A的銷毀依賴于對象B的銷毀,同時對象B銷毀也依賴與對象A的銷毀,從而形成循環(huán)引用,此時,即使外界沒有任何指針訪問它,它也無法釋放。
多個對象間依然會存在循環(huán)引用問題,形成一個環(huán),在編程中,形成的環(huán)越大越不容易察覺.
解決方法:
事先知道存在循環(huán)引用的地方,在合理的位置主動斷開一個引用,是對象回收;
使用弱引用的方法。
21.switch 語句 if 語句區(qū)別與聯(lián)系
均表示條件的判斷,switch語句表達(dá)式只能處理的是整型、字符型和枚舉類型,而選擇流程語句則沒有這樣的限制。但switch語句比選擇流程控制語句效率更高。
22.如何查找某一導(dǎo)航棧中是否有某一個特定VC,isMemberOfClass 和 isKindOfClass 聯(lián)系與區(qū)別
聯(lián)系:兩者都能檢測一個對象是否是某個類的成員
區(qū)別:isKindOfClass 不僅用來確定一個對象是否是一個類的成員,也可以用來確定一個對象是否派生自該類的類的成員 ,而isMemberOfClass 只能做到第一點。
舉例:如 ClassA派 生 自NSObject 類 , ClassA *a = [ClassA alloc] init];,[a isKindOfClass:[NSObject class]] 可以檢查出 a 是否是 NSObject派生類 的成員,但 isMemberOfClass 做不到。
23.在多人合作開發(fā)環(huán)境下,可能會有重復(fù)添加KVO的時候,或者重復(fù)移除KVO的時候,重復(fù)添加或刪除KVO會怎么樣?采取什么方案去解決?
這個問題其實就是看你解決問題的能力及思路,我覺得有兩種方法都可以實現(xiàn),一種就是手動實現(xiàn)一個KVO,KVO本質(zhì)是蘋果偷偷添加了一個派生類,將指針指向派生的子類,去監(jiān)聽子類的set方法從而實現(xiàn)監(jiān)聽的效果,手動實現(xiàn)一個KVO,并修改其添加和移除的方法,使用BOOL值作為屬性在方法內(nèi)部進(jìn)行判斷當(dāng)前這個對象是否添加過或刪除過即可;第二種方法,就是寫一個類別,利用runtime去交換kvo的添加或移除方法到自定義的添加或移除方法,修改內(nèi)部方法實現(xiàn)以達(dá)到該效果。
24.如果讓你去設(shè)計SDWebImage的緩存機(jī)制,怎么去設(shè)計?
SDWebImage的緩存分為兩種,一種是內(nèi)存緩存(MemoryCache),另一種是硬盤緩存(DiskCache),我會自定義一個單例緩存類,其中會有對應(yīng)的兩種緩存作為屬性,另外還會暴露一些自定義屬性讓用戶可以去修改緩存的策略,例如最大緩存字節(jié)數(shù),自動進(jìn)行清理緩存還是不自動去清理等,還會暴露一些方法比如通過key去查找該緩存文件,當(dāng)前key是否存在于沙盒或者內(nèi)存中等。
25.如果一個自定義的Cell可能被用在多處,每處后臺返回的數(shù)據(jù)源模型(json原始數(shù)據(jù))字段都是不同的,如果讓你去設(shè)計一種Model去兼容所有的業(yè)務(wù)需求,即不管原始數(shù)據(jù)什么樣都能一樣進(jìn)行解析賦給Cell,你怎么去設(shè)計?
這個問題我覺得有很多種解決方法,我當(dāng)時列舉了兩種,
第一種是采用一個萬能Model,即設(shè)計一個只針對于View控件的Model,不去管數(shù)據(jù)源的模型??梢赃@么理解,這個Model中的所有字段都是針對于View的,而不是原始數(shù)據(jù)源的,這樣當(dāng)你從AFN請求下來數(shù)據(jù)后需要再進(jìn)行二次轉(zhuǎn)換,即將原始數(shù)據(jù)轉(zhuǎn)換成Model里的字段,這樣就不管你是什么原始數(shù)據(jù)最終都將轉(zhuǎn)換成只針對于View的Model里的字段,即萬能字段(這個想法說出之后,不知道為什么那個面試官楞了一下,可能沒理解還是我表達(dá)的不夠清楚,他最后想了想說這個也可以還有沒有更好的方法?我心中默念:mmp,難道只有你心里那個答案才是正確的么?)
第二種解決方法是采用 協(xié)議/接口 模式,即在Cell中去創(chuàng)建一個代理人,代理人遵守Cell數(shù)據(jù)的接口,這個協(xié)議的所有方法即是賦給Cell數(shù)據(jù)的方法,當(dāng)然也只考慮當(dāng)前這個Cell,不考慮原始數(shù)據(jù);
26.__block 能修改外部變量根本原因是什么?
Block不允許修改外部變量的值,這里所說的外部變量的值,指的是棧中指針的內(nèi)存地址。__block 所起到的作用就是只要觀察到該變量被 block 所持有,就將“外部變量”在棧中的內(nèi)存地址放到了堆中。進(jìn)而在block內(nèi)部也可以修改外部變量的值。
27.自動釋放池中的變量什么時候釋放
自動釋放池其實會在其作用域結(jié)束時釋放池子,所有在池中的變量都會執(zhí)行一次release操作,所以如果在池子中的變量執(zhí)行過release操作后引用計數(shù)仍然大于0,那么就不會被釋放,反之為0就會被釋放,所以自動釋放池中的變量也不一定出了池作用域后就會被釋放的
28.block根據(jù)內(nèi)存區(qū)間分為幾種類型?
__NSMallocBlock /__NSStackBlock/ __ NSGlobalBlock
在ARC下,默認(rèn)的block創(chuàng)建后都是GlobalBlock,當(dāng)block內(nèi)部引用到外部變量的值時,就會從GlobalBlock變到NSMallocBloc,注意即使你引用的是在棧區(qū)的局部變量也是NSMallocBloc,因為ARC下會將棧區(qū)的block copy到堆區(qū),如果在MRC下需要手動去copy才會到堆區(qū),所以關(guān)鍵在于block是否引用外部的變量,外部變量內(nèi)存地址決定了block的內(nèi)存地址,即NSStackBlock還是NSMallocBlock。
29.block在什么時候會造成循環(huán)引用,如何避免循環(huán)引用以及weakSelf、strongSelf的區(qū)別。
循環(huán)引用指兩個對象相互強(qiáng)引用了對方,即retain了對方,從而導(dǎo)致誰也釋放不了誰的內(nèi)存泄露問題。
block的循環(huán)引用問題,因為block在拷貝到堆上的時候,會retain其引用的外部變量,那么如果block中如果引用了他的宿主對象,那很有可能引起循環(huán)引用
因此防止循環(huán)引用的方法如下:__weak TestCycleRetain *weakSelf = self;
在 block 之前定義對 self 的一個弱引用 wself,因為是弱引用,所以當(dāng) self 被釋放時 wself 會變?yōu)?nil;然后在 block 中引用該弱應(yīng)用,考慮到多線程情況,通過使用強(qiáng)引用 sself 來引用該弱引用,這時如果 self 不為 nil 就會 retain self,以防止在后面的使用過程中 self 被釋放;然后在之后的 block 塊中使用該強(qiáng)引用 sself,注意在使用前要對 sself 進(jìn)行了 nil 檢測,因為多線程環(huán)境下在用弱引用 wself 對強(qiáng)引用 sself 賦值時,弱引用 wself 可能已經(jīng)為 nil 了。通過這種手法,block 就不會持有 self 的引用,從而打破了循環(huán)引用。
30.通知中心添加事件監(jiān)聽需要注意什么?如果添加監(jiān)聽后忘記移除監(jiān)聽會有什么樣的坑?通知中心是什么設(shè)計模式的實現(xiàn),如何實現(xiàn)這種模式?postNotification方法是同步的還是異步的?
優(yōu)勢:
1.不需要編寫多少代碼,實現(xiàn)比較簡單;
2.對于一個發(fā)出的通知,多個對象能夠做出反應(yīng),即1對多的方式實現(xiàn)簡單
3.controller能夠傳遞context對象(dictionary),context對象攜帶了關(guān)于發(fā)送通知的自定義的信息
缺點:
1.在編譯期不會檢查通知是否能夠被觀察者正確的處理;
2.在釋放注冊的對象時,需要在通知中心取消注冊;
3.在調(diào)試的時候應(yīng)用的工作以及控制過程難跟蹤;
4.需要第三方對喜愛那個來管理controller與觀察者對象之間的聯(lián)系;
5.controller和觀察者需要提前知道通知名稱、UserInfo dictionary keys。如果這些沒有在工作區(qū)間定義,那么會出現(xiàn)不同步的情況;
6.通知發(fā)出后,controller不能從觀察者獲得任何的反饋信息。
觀察者模式
postNotification方法是同步的,用時間差檢測;
31. ios crash的原因與抓取crash日志的方法
IOS策略
1)低內(nèi)存閃退
前面提到大多數(shù)crash日志都包含著執(zhí)行線程的棧調(diào)用信息,但是低內(nèi)存閃退日志除外,這里就先看看低內(nèi)存閃退日志是什么樣的。
我們使用Xcode 5和iOS 7的設(shè)備模擬一次低內(nèi)存閃退,然后通過Organizer查看產(chǎn)生的crash日志,可以發(fā)現(xiàn)Process和Type都為Unknown:
2) Watchdog超時
Apple的iOS Developer Library網(wǎng)站上,QA1693文檔中描述了Watchdog機(jī)制,包括生效場景和表現(xiàn)。如果我們的應(yīng)用程序?qū)σ恍┨囟ǖ腢I事件(比如啟動、掛起、恢復(fù)、結(jié)束)響應(yīng)不及時,Watchdog會把我們的應(yīng)用程序干掉,并生成一份響應(yīng)的crash報告。
3) 用戶強(qiáng)制退出
一看到“用戶強(qiáng)制退出”,首先可能想到的雙擊Home鍵,然后關(guān)閉應(yīng)用程序。不過這種場景是不會產(chǎn)生crash日志的,因為雙擊Home鍵后,所有的應(yīng)用程序都處于后臺狀態(tài),而iOS隨時都有可能關(guān)閉后臺進(jìn)程,所以這種場景沒有crash日志。
另一種場景是用戶同時按住電源鍵和Home鍵,讓iPhone重啟。這種場景會產(chǎn)生日志(僅驗證過一次),但并不針對特定應(yīng)用程序。
這里指的“用戶強(qiáng)制退出”場景,是稍微比較復(fù)雜點的操作:先按住電源鍵,直到出現(xiàn)“滑動關(guān)機(jī)”的界面時,再按住Home鍵,這時候當(dāng)前應(yīng)用程序會被終止掉,并且產(chǎn)生一份相應(yīng)事件的crash日志。
通常,用戶應(yīng)該是遇到應(yīng)用程序卡死,并且影響到了iOS響應(yīng),才會進(jìn)行這樣的操作——不過感覺這操作好高級,所以這樣的crash日志應(yīng)該比較少見。
代碼bug
此外,比較常見的崩潰基本都源于代碼bug,比如數(shù)組越界、插空、空引用、引用未定義方法、多線程安全性、訪問野指針、發(fā)送未實現(xiàn)的selector等。
再來談?wù)劔@取iOS設(shè)備上崩潰日志(Crash Log)的方法
第一個方法:XCode 的菜單Window->Organizer 選擇Devices -> 選中的手機(jī) -> 點擊手機(jī)名稱左邊的箭頭 會等到如下圖
在右邊豎藍(lán)色矩形框中 Type里面出現(xiàn)兩種類型:Unknown和Crash 這兩種類型分別是 內(nèi)存不夠回收內(nèi)存kill應(yīng)用程序?qū)е翪rash和程序異常Crash的日志。
第二種方法 打開手機(jī) - > 設(shè)置 -> 通用 - > 關(guān)于本機(jī) - > 診斷與用量 - > 診斷與用量數(shù)據(jù) 這里面就是所有應(yīng)用的Crash日志。(本人沒找到這個)
第三種方法 使用第三方軟件:itools等
如果你平時不用iTunes,而是使用itools這類第三方的軟件對iPhone設(shè)備進(jìn)行管理,也是沒問題的。
打開itools,在你的設(shè)備下,找到“高級功能”,點擊“崩潰日志”,然后將需要的日志導(dǎo)出到電腦里面就可以了!
第四種方法 通過iTunes Connect(Manage Your Applications - View Details - Crash Reports)獲取用戶的crash日志
大部分用戶可能都會使用iTunes軟件來管理iPhone或者iPad設(shè)備,這時候同步的Crash日志就會同步到電腦上,我們只需要在特定的路徑里面尋找即可。
Mac OS X:~/Library/Logs/CrashReporter/MobileDevice
Windows XP:C:\Documents and Settings\Application Data\Apple computer\Logs\CrashReporter
Windows 7/Vista: C:\Users\計算機(jī)登錄名\AppData\Roaming\Apple Computer\Logs\CrashReporter\MobileDevice
32.OC的消息轉(zhuǎn)發(fā)機(jī)制
OC中的方法調(diào)用是利用消息轉(zhuǎn)發(fā)實現(xiàn)的。
objc在向一個對象發(fā)送消息時,runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運行。如果在層層的尋找中,均未找到方法的實現(xiàn),就是會拋出unrecognized selector sent to XXX的異常,導(dǎo)致程序崩潰。
OC的方法調(diào)用,稱為消息發(fā)送,就是給被調(diào)用的對象發(fā)送了一條消息 ,像這樣【receiver msg】其實就是告訴對象要調(diào)用某個方法(其實就是函數(shù)),每一個方法都對應(yīng) SEL,這個 SEL叫做選擇器,表示一個方法的 selector指針,兩個類之間不管之間有什么關(guān)系,只要方法名字相同,那么就對應(yīng)了同一個 SEL,所以在 OC 中同一個類或者同一個繼承體系中,不能存在兩個相同的方法名,即使參數(shù)類型不同也不行。不同類的實例對象執(zhí)行相同的 selector 時,會在各自的 methodList 中尋找各自對應(yīng)的 IMP ,那么這個 IMP 是什么鬼,那就說說吧,IMP 其實就相當(dāng)于函數(shù)指針,指向方法實現(xiàn)的首地址,IMP 就是為 SEL 而生的,SEL 是一個方法的唯一標(biāo)識,通過取得 IMP,我們可以跳過 OC 的 Runtime 機(jī)制,直接指向 IMP 的方法實現(xiàn),這樣就省去了 runtime 過程中的一系列的消息查找工作,會比直接向?qū)ο蟀l(fā)送消息更加高效一些。
那么下面就是消息轉(zhuǎn)發(fā)了,當(dāng) OC 向某個對象發(fā)送了一條未知的消息時,他并不會馬上報錯,而是會經(jīng)歷幾個步驟:
1 動態(tài)方法解析
2 備用接收者
3 完整轉(zhuǎn)發(fā)
(1)動態(tài)方法解析:首先調(diào)用所屬類的的類方法+resolveInstanceMrthod 可以有機(jī)會為該未知消息新增一個處理方法,不過前提是我們已經(jīng)實現(xiàn)了這個處理方法,我們可以通過 class_addMethod 函數(shù)動態(tài)的添加未知消息到類里,讓原來沒有處理這個消息的類具有處理這個消息的能力
(2)備用接收者:-(id)forwardingTargetForSelector:(SEL)aselector
如果一個對象實現(xiàn)了方法并返回一個非 nil 的對象,則返回的對象會作為消息的接收者,且消息會被分發(fā)到這個對象,當(dāng)然如果沒有指定的對象來處理這個 aselector,則應(yīng)該調(diào)用父類的實現(xiàn)來返回結(jié)果,但是有一點這個方法通常在對象的內(nèi)部比如.m 文件里,還有一系列對象能處理該消息,我們可以借這些對象來處理該消息并且返回,這樣好像看起來就是該類處理了消息,做好事不留名,像我一樣
(3)完整消息轉(zhuǎn)發(fā):
—(void)forwardInvocation(NSInvocation *)aInvocation
NSInvocation 初始化需要有一個方法簽名 NSMethodSignature
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
—(void)forwardInvocation(NSInvocation *)aInvocation{
if([object instanceRespondToSelector:aInvocaion.selector]){
[aInvocation invokeWithTarget:[object alloc]init]]
}
}
這樣相應(yīng)的消息就會轉(zhuǎn)發(fā)到這個 object 對象這里來實現(xiàn)了
33.api hook原理及應(yīng)用,如何防止按鈕重復(fù)點擊。
iOS中的按鈕事件機(jī)制 >>> Target-Action機(jī)制
用戶點擊時,產(chǎn)生一個按鈕點擊事件消息
這個消息發(fā)送給注冊的Target處理
Target接收到消息,然后查找自己的SEL對應(yīng)的具體實現(xiàn)IMP正兒八經(jīng)的去處理點擊事件
實際上該點擊消息包含三個東西:
Target處理者
SEL方法Id
按鈕事件當(dāng)時觸發(fā)時的狀態(tài)
所有的按鈕事件狀態(tài)
typedef NS_OPTIONS(NSUInteger, UIControlState) {
UIControlStateNormal = 0,
UIControlStateHighlighted = 1 << 0, // used when UIControl isHighlighted is set
UIControlStateDisabled = 1 << 1,
UIControlStateSelected = 1 << 2, // flag usable by app (see below)
UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 3, // Applicable only when the screen supports focus
UIControlStateApplication = 0x00FF0000, // additional flags available for application use
UIControlStateReserved = 0xFF000000 // flags reserved for internal framework use
};
點擊按鈕時候,會產(chǎn)生一個包裝了Target、SEL、按鈕事件狀態(tài)三個東西的消息發(fā)送給Target處理
問題: 是誰來包裝UIButton的點擊事件消息,并且完成發(fā)送消息了?
這個是解決連續(xù)點擊按鈕的關(guān)鍵問題所在,必須搞清楚。因為如果搞清楚具體包裝和發(fā)送按鈕點擊時間消息的地方和時機(jī),那么可以攔截這個地方執(zhí)行,然后加入是否在指定的間隔時間內(nèi)決定是否讓其繼續(xù)執(zhí)行發(fā)送消息的操作。
那么問題不就解決了嗎,我都不讓他發(fā)送消息了,他還能執(zhí)行?
首先從UIButton.h頭文件中查找,是否有send message 、send Action …等等包含send的方法無法找到.
UIButton繼承自UIControl,而UIControl又負(fù)責(zé)很多的UI事件處理,那么可以繼續(xù)從UIControl.h中查找找到兩個send相關(guān)的函數(shù):
// send the action. the first method is called for the event and is a point at which you can observe or override behavior. it is called repeately by the second.
- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
- (void)sendActionsForControlEvents:(UIControlEvents)controlEvents; // send all actions associated with events
突破口 >>> UIControl完成按鈕點擊事件消息的包裝與發(fā)送的階段,可以做一些間隔時間處理點擊消息發(fā)送我們可以在UIControl的sendAction:to:forEvent:做防止按鈕連續(xù)處理.那么大概有如下幾種做法:
第一種、自定義我們的UIButton類,以后程序中都使用我們UIButton類(只適合新項目,不太適合老項目,用的地方太多了)
第二種、使用UIButton Category封裝防止按鈕連續(xù)點擊處理的邏輯(這種挺好,對原來的UIButton使用代碼綠色無公害)
第三種、直接在main.m中執(zhí)行main()之前,就替換掉UIControl的sendAction:to:forEvent:
具體實現(xiàn):
使用UIButton子類實現(xiàn)
#import@interface MyButton : UIButton
/**
* 按鈕點擊的間隔時間
*/
@property (nonatomic, assign) NSTimeInterval time;
@end
#import "MyButton.h"
// 默認(rèn)的按鈕點擊時間
static const NSTimeInterval defaultDuration = 3.0f;
// 記錄是否忽略按鈕點擊事件,默認(rèn)第一次執(zhí)行事件
static BOOL _isIgnoreEvent = NO;
// 設(shè)置執(zhí)行按鈕事件狀態(tài)
static void resetState() {
_isIgnoreEvent = NO;
}
@implementation MyButton
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
//1. 按鈕點擊間隔事件
_time = _time == 0 ? defaultDuration : _time;
//2. 是否忽略按鈕點擊事件
if (_isIgnoreEvent) {
//2.1 忽略按鈕事件
// 直接攔截掉super函數(shù)進(jìn)行發(fā)送消息
return;
} else if(_time > 0) {
//2.2 不忽略按鈕事件
// 后續(xù)在間隔時間內(nèi)直接忽略按鈕事件
_isIgnoreEvent = YES;
// 間隔事件后,執(zhí)行按鈕事件
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
resetState();
});
// 發(fā)送按鈕點擊消息
[super sendAction:action to:target forEvent:event];
}
}
@end
ViewController中測試
@implementation ViewController
- (void)btnDidClick:(id)sender {
NSLog(@"我被點擊了 >>> %@", NSStringFromSelector(_cmd));
}