原做者:執筆續春秋前端
本面試題爲我的使用版本,如後續流傳出去,請轉發的朋友務必註釋一下,答案正確性有待商榷,本人的答案不表明權威,僅僅是我的理解。 文章內部有寫混亂,將就着看吧。另外大部分圖片加載不出來,,MARKDown格式也不太統一(各平臺不同),因爲博主太懶不想改,不過不影響最終效果,ios
做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交流羣:834688868,無論你是大牛仍是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!程序員
__unsafe_unretained UIView *testObj = [[UIView alloc] init];
NSLog(@"testObj 指針指向的地址:%p 指針自己的地址:%p", testObj, &testObj);
[testObj setNeedsLayout];
// 能夠看到NSlog打印不會閃退,調用[testObj setNeedsLayout];會閃退
複製代碼
這是網友總結的,有興趣的能夠看下:www.jianshu.com/p/9fd4dc046… 本人,也就是看看樂呵,其原理啥的,見仁見智吧。開發行業太j8難了!web
一、找不到方法的實現unrecognized selector sent to instance 二、KVC形成的crash 三、EXC_BAD_ACCESS 四、KVO引發的崩潰 五、集合類相關崩潰 六、多線程中的崩潰 七、Socket長鏈接,進入後臺沒有關閉 八、Watch Dog超時形成的crash 九、後臺返回NSNull致使的崩潰,多見於Java作後臺服務器開發語言面試
大體實現方式以下。數據庫
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler);
return YES;
}
static void my_uncaught_exception_handler (NSException *exception) {
//這裏能夠取到 NSException 信息
NSLog(@"***********************************************");
NSLog(@"%@",exception);
NSLog(@"%@",exception.callStackReturnAddresses);
NSLog(@"%@",exception.callStackSymbols);
NSLog(@"***********************************************");
}
複製代碼
實現方式如: blog.csdn.net/u013896628/…數組
block 捕獲的是當前在block內部執行的外部局部變量的瞬時值, 爲何說瞬時值呢? 看一下C++源碼中得知, 其內部代碼在捕獲的同時瀏覽器
其實block底層生成了一個和外部變量相同名稱的屬性值若是內部修改值,其實修改的是捕獲以前的值,其捕獲的內部的值因代碼只作了一次捕獲,並無作再一次的捕獲,因此block裏面不能夠修改值。緩存
若是當前捕獲的爲對象類型,其block內部能夠認爲從新建立了一個指向當前對象內存地址的指針(堆),操控內部操做的東西均爲同一塊內存地址,因此能夠修改當前內部的對象裏面的屬性,可是不能直接修改當前的指針(沒法直接修改棧中的內容)(即從新生成一個新的內存地址)。其原理和捕獲基本數據類型一致。安全
說白了, block內部能夠修改的是堆中的內容, 但不能直接修改棧中的任何東西。
分類的實現原理是將category中的方法,屬性,協議數據放在category_t結構體中,而後將結構體內的方法列表拷貝到類對象的方法列表中。 Category能夠添加屬性,可是並不會自動生成成員變量及set/get方法。由於category_t結構體中並不存在成員變量。經過以前對對象的分析咱們知道成員變量是存放在實例對象中的,而且編譯的那一刻就已經決定好了。而分類是在運行時纔去加載的。那麼咱們就沒法再程序運行時將分類的成員變量中添加到實例對象的結構體中。所以分類中不能夠添加成員變量。
在往深一點的回答就是 類在內存中的位置是編譯時期決定的, 以後再修改代碼也不會改變內存中的位置,class_ro_t 的屬性在運行期間就不能再改變了, 再添加方法是會修改class_rw_t 的methods 而不是class_ro_t 中的 baseMethods
- (void)foo{
[類 invokeOriginalMethod:self selector:_cmd];
}
+ (void)invokeOriginalMethod:(id)target selector:(SEL)selector {
uint count;
Method *list = class_copyMethodList([target class], &count);
for ( int i = count - 1 ; i >= 0; i--) {
Method method = list[i];
SEL name = method_getName(method);
IMP imp = method_getImplementation(method);
if (name == selector) {
((void (*)(id, SEL))imp)(target, name);
break;
}
}
free(list);
}
複製代碼
答: 舉例說明,12306 同一列火車的車票, 同一時間段多人搶票! 如何解決 互斥鎖使用格式
synchronized(鎖對象) { // 須要鎖定的代碼 }
注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的
Tips: 互斥鎖的優缺點
優勢:能有效防止因多線程搶奪資源形成的數據安全問題
缺點:須要消耗大量的CPU資源
互斥鎖的使用前提:多條線程搶奪同一塊資源
相關專業術語:線程同步,多條線程按順序地執行任務
互斥鎖,就是使用了線程同步技術
Objective-C中的原子和非原子屬性
OC在定義屬性時有nonatomic和atomic兩種選擇
atomic:原子屬性,爲setter/getter方法都加鎖(默認就是atomic)
nonatomic:非原子屬性,不加鎖
atomic加鎖原理:
property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}
- (int)age {
int age1 = 0;
@synchronized(self) {
age1 = _age;
}
}
原子和非原子屬性的選擇
nonatomic和atomic對比
atomic:線程安全,須要消耗大量的資源
nonatomic:非線程安全,適合內存小的移動設備
iOS開發的建議
全部屬性都聲明爲nonatomic
儘可能避免多線程搶奪同一塊資源
儘可能將加鎖、資源搶奪的業務邏輯交給服務器端處理,減少移動客戶端的壓力
atomic就必定能保證線程安全麼?
不能,還須要更深層的鎖定機制才能夠,由於一個線程在連續屢次讀取某條屬性值的時候,與此同時別的線程也在改寫值,這樣仍是會讀取到不一樣的屬性值! 或者 一個線程在獲取當前屬性的值, 另一個線程把這個屬性釋放調了, 有可能形成崩潰
複製代碼
- 另外整理了一份 2021年《大廠最新常問iOS面試題+答案》,有須要,直接加iOS技術交流羣:834688868,,免費獲取;羣內更有內推機會!
![image](//upload-images.jianshu.io/upload_images/18569867-6d3f854fb97ebc5d.png?imageMogr2/auto-orient/strip|imageView2/2/w/720/format/webp)
複製代碼
1. 運用GCD:
import "Manager.h"
implementation Manager
+ (Manager *)sharedManager {
static dispatch_once_t onceToken;
static Manager * sharedManager;
dispatch_once(&onceToken, ^{
sharedManager=[[Manager alloc] init];
});
return sharedManager;
}
end
註明:dispatch_once這個函數,它能夠保證整個應用程序生命週期中某段代碼只被執行一次!
2. 不使用GCD的方式:
static Manager *manager;
implementation Manager
+ (Manager *)defaultManager {
if(!manager)
manager=[[self allocWithZone:NULL] init];
return manager;
}
end
3. 正常的完整版本
+(id)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if(_instance == nil)
_instance = [MyClass alloc] init];
});
return _instance;
}
//重寫allocWithZone,裏面實現跟方法一,方法二一致就行.
+(id)allocWithZone:(struct _NSZone *)zone{
return [self shareInstance];
}
//保證copy時相同
-(id)copyWithZone:(NSZone *)zone{
return _instance;
}
// 方法3建立的目的的是 爲了方式開發者在調用單例的時候並無用shareInstance方法來建立 而是用的alloc 或者copy的形式建立形成單例不一致的狀況
//
複製代碼
//必須把static dispatch_once_t onceToken; 這個拿到函數體外,成爲全局的.
+ (void)attempDealloc {
onceToken = 0; // 只有置成0,GCD纔會認爲它從未執行過.它默認爲0,這樣才能保證下次再次調用shareInstance的時候,再次建立對象.
_sharedInstance = nil;
}
dispatch_once_t 的工做原理是,static修飾會默認將其初始化爲0, 當且僅當其爲0的時候dispatch_once(&onceToken, ^{})這個函數才能被調用, 若是執行了這個函數 這個dispatch_once_t 靜態變成- 1了 就永遠不會被調用
複製代碼
1.第一種方式,重寫+allocWithZone:方法;
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static id instance = nil;
@synchronized (self) { // 互斥鎖
if (instance == nil) {
instance = [super allocWithZone:zone];
}
}
return instance;
}
2.第二種方式,不用重寫+allocWithZone:方法,而是直接用@synchronized 來保證線程安全,其它與上面這個方法同樣;
+ (instancetype)sharedSingleton {
static id instance = nil;
@synchronized (self) {
if (!instance) {
instance = [[self alloc] init];
}
}
return instance;
}
複製代碼
答 :整個程序公用一份資源的時候 例如 :
- 設置單例類訪問應用的配置信息
- 用戶的我的信息登陸後用的NSUserDefaults存儲,對登陸類進一步採用單例封裝方便全局訪問
- 防止一個單例對 應用 多處 對贊成本地數據庫存進行操做
- 第一階段:應用程序的服務器端把要發送的消息、目的iPhone的標識打包,發給APNS。
- 第二階段:APNS在自身的已註冊Push服務的iPhone列表中,查找有相應標識的iPhone,並把消息發送到iPhone。
- 第三階段:iPhone把發來的消息傳遞給相應的應用程序,而且按照設定彈出Push通知。
首先是註冊
- Device(設備)鏈接APNs服務器並攜帶設備序列號(UUID)
- 鏈接成功,APNs通過打包和處理產生devicetoken並返回給註冊的Device(設備)
- Device(設備)攜帶獲取的devicetoken發送到咱們本身的應用服務器
- 完成須要被推送的Device(設備)在APNs服務器和咱們本身的應用服務器的註冊
推送過程
- 一、首先手機裝有當前的app,而且保持有網絡的狀況下,APNs服務器會驗證devicetoken,成功那個以後會處於一個長鏈接。 (這裏會有面試問? 若是app也註冊成功了, 也下載了,也贊成了打開推送功能, 這個時候在把App刪除了, 還能接受推送了麼? )
- 二、當咱們推送消息的時候,咱們的服務器按照指定格式進行打包,結合devicetoken 一塊兒發送給APNs服務器,
- 三、APNs 服務器將新消息推送到iOS 設備上,而後在設備屏幕上顯示出推送的消息。
- 四、iOS設備收到推送消息後, 會通知給咱們的應用程序並給予提示
// 推送過程以下圖
答 : iOS中有五種RunLoop模式
NSDefaultRunLoopMode (默認模式,有事件響應的時候,會阻塞舊事件)
NSRunLoopCommonModes (普通模式,不會影響任何事件)
UITrackingRunLoopMode (只能是有事件的時候纔會響應的模式)
還有兩種系統級別的模式
一個是app剛啓動的時候會執行一次
另一個是系統檢測app各類事件的模式
複製代碼
答 : 本來系統就有一個runloop在檢測App內部的行爲或事件,當輸入源(用戶的直接或者間接的操做)有「執行操做」的時候, 系統的runloop會監聽輸入源的狀態, 進而在系統內部作一些對應的相應操做。 處理完成後,會自動回到睡眠狀態, 等待下一次被喚醒,
RunLoop和線程的關係
RunLoop的做用就是用來管理線程的, 當線程的RunLoop開啓以後,線程就會在執行完成任務後,進入休眠狀態,隨時等待接收新的任務,而不是退出。
爲何只有主線程的runloop
是開啓的
程序開啓以後,要一直運行,不會退出。 說白了就是爲了讓程序不死
// 先建立一個線程用於測試
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(play) object:nil];
[thread start];
// 保證一個線程永遠不死
[[NSRunLoop currentRunLoop] addPort:[NSPort port] -forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
// 在合適的地方處理線程的事件處理
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:NO];
複製代碼
1\. 說說你理解weak屬性?
複製代碼
1.實現weak後,爲何對象釋放後會自動爲nil?
runtime 對註冊的類, 會進行佈局,
對於 weak 對象會放入一個 hash 表中。
用 weak 指向的對象內存地址做爲 key,
Value是weak指針的地址數組。
當釋放的時候,其內部會經過當前的key找到全部的weak指針指向的數組
而後遍歷這個數組把其中的數據設置爲nil。
稍微詳細的說:在內部底層源碼也同時和當前對象相關聯得SideTable, 其內部有三個屬性, 一個是一把自旋鎖,一個是引用計數器相關,一個是維護weak生命得屬性得表
**SideTable**這個結構體同樣的東西,能夠花半個小時看一眼。
複製代碼
objc中向一個nil對象發送消息將會發生什麼? 首先 在尋找對象化的isa指針時就是0地址返回了, 因此不會有任何錯誤, 也不會錯誤
objc在向一個對象發送消息時,發生了什麼?
- 首先是經過obj 的isa指針找到對應的class
- 先去操做對象中的緩存方法列表中objc_cache中去尋找 當前方法,若是找到就直接實現對應IMP
- 若是在緩存中找不到,則在class中找到對用的Method list中對用foo
- 若是class中沒有找到對應的foo, 就會去superClass中去找
- 若是找到了對應的foo, 就會實現foo對應的IMP
緩存方法列表, 就是每次執行這個方法的時候都會作如此繁瑣的操做這樣太過於消耗性能,因此出現了一個objc_cache,這個會把當前調用過的類中的方法作一個緩存, 當前method_name做爲key, method_IMP做爲Value,當再一次接收到消息的時候,直接經過objc_cache去找到對應的foo的IMP便可, 避免每一次都去遍歷objc_method_list
若是一直沒有找到方法, 就會專用消息轉發機制,機制以下
// 動態方法解析和轉發
上面的例子若是foo函數一直沒有被找到,一般狀況下,會出現報錯,可是在報錯以前,OC的運行時給了咱們三次補救的機會
- Method resolution
- Fast forwarding
- Normal forwarding
1. Runtime 會發送 +resolveInstanceMethod: 或者 +resolveClassMethod: 嘗試去 resolve(重啓) 這個消息;
2. 若是 resolve 方法返回 NO,Runtime 就發送 -forwardingTargetForSelector: 容許你把這個消息轉發給另外一個對象;
3. 若是沒有新的目標對象返回, Runtime 就會發送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。你能夠發送 -invokeWithTarget: 消息來手動轉發消息或者發送 -doesNotRecognizeSelector: 拋出異常。
複製代碼
- 二者最明顯的區別是 View能夠接受並處理事件,而 Layer 不能夠;
- 每一個 UIView 內部都有一個 CALayer 在背後提供內容的繪製和顯示,而且 UIView 的尺寸樣式都由內部的 Layer 所提供。二者都有樹狀層級結構,layer 內部有 SubLayers,View 內部有 SubViews.可是 Layer 比 View 多了個AnchorPoint
- 在 View顯示的時候,UIView 作爲 Layer 的 CALayerDelegate,View 的顯示內容由內部的 CALayer 的 display
CALayer 是默認修改屬性支持隱式動畫的,在給 UIView 的 Layer 作動畫的時候,View 做爲 Layer 的代理,Layer 經過 actionForLayer:forKey:向 View請求相應的 action(動畫行爲)
- layer 內部維護着三分 layer tree,分別是 presentLayer Tree(動畫樹),modeLayer Tree(模型樹), Render Tree (渲染樹),在作 iOS動畫的時候,咱們修改動畫的屬性,在動畫的實際上是 Layer 的 presentLayer的屬性值,而最終展現在界面上的實際上是提供 View的modelLayer
複製代碼
- @property有兩個對應的詞,一個是 @synthesize,一個是 @dynamic。若是 @synthesize和 @dynamic都沒寫,那麼默認的就是@syntheszie var = _var;
- @synthesize 的語義是若是你沒有手動實現 setter 方法和 getter 方法,那麼編譯器會自動爲你加上這兩個方法。
- @dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶本身實現,不自動生成。(固然對於 readonly 的屬性只需提供 getter 便可)。假如一個屬性被聲明爲 @dynamic var,而後你沒有提供 @setter方法和 @getter 方法,編譯的時候沒問題,可是當程序運行到 instance.var = someVar,因爲缺 setter 方法會致使程序崩潰;或者當運行到 someVar = var 時,因爲缺 getter 方法一樣會致使崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
複製代碼
static關鍵字能夠修飾函數和變量,做用以下:
**隱藏**
經過static修飾的函數或者變量,在該文件中,全部位於這條語句以後的函數均可以訪問,而其餘文件中的方法和函數則不行
**靜態變量**
類方法不能夠訪問實例變量(函數),經過static修飾的實例變量(函數),能夠被類 方法訪問;
**持久**
static修飾的變量,能且只能被初始化一次;
**默認初始化**
static修飾的變量,默認初始化爲0;
複製代碼
- objc_msgSend(recicver, selecter..)
複製代碼
1\. runloop與線程是一一對應的,一個runloop對應一個核心的線程,爲何說是核心的,是由於runloop是能夠嵌套的,可是核心的只能有一個,他們的關係保存在一個全局的字典裏。
2\. runloop是來管理線程的,當線程的runloop被開啓後,線程會在執行完任務後進入休眠狀態,有了任務就會被喚醒去執行任務。runloop在第一次獲取時被建立,在線程結束時被銷燬。
3\. 對於主線程來講,runloop在程序一啓動就默認建立好了。
4\. 對於子線程來講, runloop是懶加載的,只有當咱們使用的時候纔會建立,因此在子線程用定時器要注意:確保子線程的runloop被開啓,否則定時器不會回調。
複製代碼
鍵值觀察通知依賴於 NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一個被觀察屬性發生改變以前, willChangeValueForKey: 必定會被調用,這就 會記錄舊的值。而當改變發生後, didChangeValueForKey: 會被調用,繼而 observeValueForKey:ofObject:change:context: 也會被調用。若是能夠手動實現這些調用,就能夠實現「手動觸發」了。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"age"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (void)setAge:(NSInteger)age {
if (age >= 18) {
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}else {
_age = age;
}
}
複製代碼
(1)首先會按照順序依次查找setKey:方法和_setKey:方法,只要找到這兩個方法當中的任何一個就直接傳遞參數,調用方法;
(2)若是沒有找到setKey:和_setKey:方法,那麼這個時候會查看accessInstanceVariablesDirectly方法的返回值,若是返回的是NO(也就是不容許直接訪問成員變量),那麼會調用setValue:forUndefineKey:方法,並拋出異常「NSUnknownKeyException」;
(3)若是accessInstanceVariablesDirectly方法返回的是YES,也就是說能夠訪問其成員變量,那麼就會按照順序依次查找 _key、_isKey、key、isKey這四個成員變量,若是查找到了,就直接賦值;若是依然沒有查到,那麼會調用setValue:forUndefineKey:方法,並拋出異常「NSUnknownKeyException」。
(1)首先會按照順序依次查找getKey:、key、isKey、_key:這四個方法,只要找到這四個方法當中的任何一個就直接調用該方法;
(2)若是沒有找到,那麼這個時候會查看accessInstanceVariablesDirectly方法的返回值,若是返回的是NO(也就是不容許直接訪問成員變量),那麼會調用valueforUndefineKey:方法,並拋出異常「NSUnknownKeyException」;
(3)若是accessInstanceVariablesDirectly方法返回的是YES,也就是說能夠訪問其成員變量,那麼就會按照順序依次查找 _key、_isKey、key、isKey這四個成員變量,若是找到了,就直接取值;若是依然沒有找到成員變量,那麼會調用valueforUndefineKey方法,並拋出異常「NSUnknownKeyException」。
按照執行順序排列:
1. initWithCoder:經過nib文件初始化時觸發。
2. awakeFromNib:nib文件被加載的時候,會發生一個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上消失。
複製代碼
三次握手
**1.**客戶端向服務端發起請求連接,首先發送SYN報文,SYN=1,seq=x,而且客戶端進入SYN_SENT狀態 **2.**服務端收到請求連接,服務端向客戶端進行回覆,併發送響應報文,SYN=1,seq=y,ACK=1,ack=x+1,而且服務端進入到SYN_RCVD狀態 **3.**客戶端收到確認報文後,向服務端發送確認報文,ACK=1,ack=y+1,此時客戶端進入到ESTABLISHED,服務端收到用戶端發送過來的確認報文後,也進入到ESTABLISHED狀態,此時連接建立成功
- 哎!
- 嗯
- 給你
複製代碼
爲何須要三次握手: 爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,於是產生錯誤。假設這是一個早已失效的報文段,但server收到此失效的鏈接請求報文段後,就誤認爲是client再次發出的一個新的鏈接請求。因而就向client發出確認報文段,贊成創建鏈接。假設不採用「三次握手」,那麼只要server發出確認,新的鏈接就創建了。因爲如今client並無發出創建鏈接的請求,所以不會理睬server的確認,也不會向server發送數據。但server卻覺得新的運輸鏈接已經創建,並一直等待client發來數據。這樣,server的不少資源就白白浪費掉了。
四次揮手
**1.**客戶端向服務端發起關閉連接,並中止發送數據 **2.**服務端收到關閉連接的請求時,向客戶端發送迴應,我知道了,而後中止接收數據 **3.**當服務端發送數據結束以後,向客戶端發起關閉連接,並中止發送數據 **4.**客戶端收到關閉連接的請求時,向服務端發送迴應,我知道了,而後中止接收數據
- 哎!
- 嗯
- 關了
- 好的
複製代碼
爲何須要四次揮手: 由於TCP是全雙工通訊的,在接收到客戶端的關閉請求時,還可能在向客戶端發送着數據,所以不能再回應關閉連接的請求時,同時發送關閉連接的請求
HTTP和HTTPS有什麼區別?
HTTPS的加密方式?
Https採用對稱加密和非對稱加密結合的方式來進行通訊。
Https不是應用層的新協議,而是Http通訊接口用SSL和TLS來增強加密和認證機制。
HTTP
HTTPS
GET在特定的瀏覽器和服務器對URL的長度是有限制的。 可是理論上是沒有限制的
POST不是經過URL進行傳值,理論上不受限制。
GET會把請求參數拼接到URL後面, 不安全,
POST把參數放到請求體裏面, 會比GET相對安全一點, 可是因爲能夠窺探數據, 因此也不安全, 想更安全用加密。
GET比POST的請求速度快。緣由:Post請求的過程, 會現將請求頭髮送給服務器確認,而後才真正的發送數據, 而Get請求 過程會在連接創建後會將請求頭和數據一塊兒發送給服務器。 中間少了一步。 因此get比post 快
post的請求過程
三次握手以後 第三次會把post請求頭髮送
服務器返回100 continue響應
瀏覽器開始發送數據
服務器返回200 ok響應
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
// 默認person,沒有實現eat方法,能夠經過performSelector調用,可是會報錯。
// 動態添加方法就不會報錯
[p performSelector:@selector(eat)];
}
@end
@implementation Person
// **這裏真是奇葩, 實在想不到何時纔有這種使用場景, 我再外面找不到方法, 我再當前類裏面直接在寫一個方法就好咯,幹嗎要在這裏寫這個玩意, 還要寫一個C語言的東西, 既然面試想問, 那咱就要會!**
// void(*)()
// 默認方法都有兩個隱式參數,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 當一個對象調用未實現的方法,會調用這個方法處理,而且會把對應的方法列表傳過來.
// 恰好能夠用來判斷,未實現的方法是否是咱們想要動態添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 動態添加eat方法
// 第一個參數:給哪一個類添加方法
// 第二個參數:添加方法的方法編號
// 第三個參數:添加方法的函數實現(函數地址)
// 第四個參數:函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
複製代碼
// 延時操做 和GCD的after 一個效果
[p performSelector:@selector(eat) withObject:nil afterDelay:4];
複製代碼
你覺得完了? 錯了,大機率面試官會問你,*** 上面這段代碼放在子線程中 是什麼樣子的?爲何?**
—首先 上面這個方法其實就是內部建立了一個NSTimer定時器,而後這個定時器會添加在當前的RunLoop中因此上面代碼放到子線程中不會有任何定時器相關方法被執行,若是想要執行,開啓當前線程便可 即
[[NSRunLoop currentRunLoop] run];
複製代碼
// 完整調用
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// [[NSRunLoop currentRunLoop] run]; 放在上面執行時不能夠的,由於當前只是開啓了runloop 裏面沒有任何事件(source,timer,observer)也是開啓失敗的
[self performSelector:@selector(test) withObject:nil afterDelay:2];
[[NSRunLoop currentRunLoop] run];
});
// 由此我自行又作了一個測試, 把
[self performSelector:@selector(test)];
在子線程調用,是沒有任何問題的。
// 我又測試了一下,
[self performSelector:@selector(test) withObject:nil afterDelay:2];
這個方法在主線程執行 打印線程是1
在子線程中調用打印線程 非1
複製代碼
// 在子線程中開啓NStimer,或者更改當前Runloop的Mode 爲NSRunLoopCommonModes
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
// 利用CADisplayLink (iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常狀況下會在每次刷新結束都被調用,精確度至關高)
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(logInfo)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// 利用GCD
NSTimeInterval interval = 1.0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"GCD timer test");
});
dispatch_resume(_timer);
複製代碼
在請求完成後咱們須要對數據進行一些序列化處理,或者錯誤處理。若是咱們在主線中處理這些事情很明顯是不合理的。不只會致使UI的卡頓,甚至受到默認的RunLoopModel的影響,咱們在滑動tableview的時候,會致使時間的處理中止。
這裏時候咱們就須要一個子線程來處理事件和網絡請求的回調了。可是,子線程在處理完事件後就會自動結束生命週期,這個時候後面的一些網絡請求得回調咱們就沒法接收了。因此咱們就須要開啓子線程的RunLoop來保存線程的常駐。
固然咱們能夠每次發起一個請求就開啓一條子線程,可是這個想一下就知道開銷有多大了。因此這個時候保活一條線程來對請求得回調處理是比較好的一個方案。
在3.x的AFN版本中使用的是NSURLSession進行封裝。對比於NSURLConnection,NSURLSession不須要在當前的線程等待網絡回調,而是可讓開發者本身設定須要回調的隊列。
因此在3.x版本中AFN使用了NSOperationQueue對網絡回調的管理,而且設置maxConcurrentOperationCount爲1,保證了最大的併發數爲1,也就是說讓網絡請求串行執行。避免了多線程環境下的資源搶奪問題。
//來自Apple文檔,見參考
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:urlencoding:NSUTF8StringEncoding error:&error];
}
// 若是循環次數很是多,並且循環體裏面的對象都是臨時建立使用的,就能夠用@autoreleasepool 包起來,讓每次循環結束時,能夠及時釋放臨時對象的內存
// for 和 for in 裏面是沒有自動包裝@autoreleasepool着的,而下面的方法是由@autoreleasepool自動包圍的
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 這裏被一個局部@autoreleasepool包圍着
}];
複製代碼
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString* str = [[[NSString alloc] initWithString:@"666"] autorelease];
[pool drain];
// 其做用於爲drain 和 init 之間
複製代碼
首先iOS渲染視圖的核心是Core Animation,其渲染層次依次爲:圖層樹->呈現樹->渲染樹
一共三個階段
CPU階段(進行Frame佈局,準備視圖和圖層之間的層級關係)
OpenGL ES階段(iOS8之後改爲Metal), (渲染服務把上面提供的圖層上色,生成各類幀)
GPU階段 (把上面操做的東西進行一些列的操做,最終展現到屏幕上面)
稍微詳細說明
首先一個視圖由CPU進行Frame佈局,準備視圖和圖層的層及關係。
CUP會將處理視圖和圖層的層級關係打包,經過IPC(進程間的通訊)通道提交給渲染服務(OpenGL和GPU)
渲染服務首先將圖層交給OpenGL進行紋理生成和着色,生成先後幀緩存,再根據硬件的刷新幀率,通常以設備的VSync信號和CADisplayLink(相似一個刷新UI專用的定時器)爲標準,進行先後幀緩存的切換
最後,將最終 要顯示在畫面上的後幀緩存交給GPU,進行採集圖片和形狀,運行變換, 應用紋理混合,最終顯示在屏幕上。
這裏會出現一個面試題!!! 題目以下:
在科普一下 1.Core Animation Core Animation 在 RunLoop 中註冊了一個 Observer,監聽了 BeforeWaiting 和 Exit 事件。這個 Observer 的優先級是 2000000,低於常見的其餘 Observer。當一個觸摸事件到來時,RunLoop 被喚醒,App 中的代碼會執行一些操做,好比建立和調整視圖層級、設置 UIView 的 frame、修改 CALayer 的透明度、爲視圖添加一個動畫;這些操做最終都會被 CALayer 捕獲,並經過 CATransaction 提交到一箇中間狀態去(CATransaction 的文檔略有提到這些內容,但並不完整)。當上面全部操做結束後,RunLoop 即將進入休眠(或者退出)時,關注該事件的 Observer 都會獲得通知。這時 CA 註冊的那個 Observer 就會在回調中,把全部的中間狀態合併提交到 GPU 去顯示;若是此處有動畫,CA 會經過 DisplayLink 等機制屢次觸發相關流程。
2.CPU渲染職能
3.GPU渲染職能 GPU會根據生成的先後幀緩存數據,根據實際狀況進行合成,其中形成GPU渲染負擔的通常是:離屏渲染,圖層混合,延遲加載。
這裏又會出現一個面試題!!! 一個UIImageView添加到視圖上之後,內部如何渲染到手機上的?
圖片顯示分爲三個步驟: 加載、解碼、渲染、 一般,咱們程序員的操做只是加載,至於解碼和渲染是由UIKit內部進行的。 例如:UIImageView顯示在屏幕上的時候須要UIImage對象進行數據源的賦值。而UIImage持有的數據是未解碼的壓縮數據,當賦值的時候,圖像數據會被解碼變成RGB顏色數據,最終渲染到屏幕上。
看完上面的又來問題了! 關於UITableView優化的問題?(真他媽子子孫孫無窮盡也~) 先說形成UITableView滾動時候卡頓的的緣由有哪些?
在說關於UITableView的優化問題!
基礎的
**** 解釋一下爲何減小離屏渲染操做?****
須要建立新的緩衝區
整個過程須要屢次切換上下文環境, 顯示從當前的屏幕切換到離屏,等待離屏渲染結束後,將離屏緩衝區的渲染結果 顯示到屏幕有上, 又要將上下文環境從離屏切換到當前屏幕,
那些操做會觸發離屏渲染?
光柵化 layer.shouldRasterize = YES
遮罩layer.mask
圓角layer.maskToBounds = Yes,Layer.cornerRadis 大於0
陰影layer.shadowXXX
進階的
高階的
至於上面的那些基礎的,涉及到渲染級別的本身說的時候悠着點,面試官若是想搞你的話,考一考你最上面的那些,CUP和GUP,以及openGL相關, 在考一下你進程通訊IPC,以及VSync信號啥的, 這些東西太雞兒高深了,沒點匠心 這東西還真搞不了,要想研究能夠看看YYKit的做者寫的一篇關於頁面流暢的文章:blog.ibireme.com/2015/11/12/…
既然都是圖形繪製了,那就再研究一下事件響應鏈&原理
傳統的問法來了:UIView和CALayer的區別? 一般咱們這樣回答:UIView能夠響應用戶事件,而CALayer不能處理事件
回答這個以前, 先回顧一下另一個經典面試題:事件響應鏈和事件傳遞?
基本概念:
響應鏈: 是由連接在一塊兒的響應者(UIResponse子類)組成的,通常爲第一響應着到application對象以及中間全部響應者一塊兒組成的。
事件傳遞: 獲取響應鏈以後, 將事件由第一響應者網application的傳遞過程
事件的分發和傳遞
當程序中發生觸摸事件以後,系統會將事件添加到UIApplication管理的一個隊列當中
UIApplication將處於任務隊列最前端的事件向下分發 即UIWindow
UIWindow將事件向下分發,即UIView或者UIViewController
UIView首先看本身可否處理這個事件,觸摸點是否在本身身上,本身的透明度是否大於0,01,userInteractionEnabled 是不是YES, Hidden實際是NO,若是這些都知足,那麼繼續尋找其子視圖
遍歷子控件,重複上面步驟
若是沒有找到,那麼本身就是改事件的處理者
若是本身不能處理,那麼就不作任何處理 即視爲沒有合適的View能接收處理當前事件,則改事件會被廢棄。
*** 怎麼尋找當前觸摸的是哪個View?*** 下面中兩個方法
// 此方法返回的View是本次點擊事件須要的最佳View
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
// 判斷一個點是否落在範圍內
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
複製代碼
事件傳遞給控件以後, 就會調用hitTest:withEvent方法去尋找更合適的View,若是當前View存在子控件,則在子控件繼續調用hitTest:withEvent方法判斷是不是合適的View, 若是還不是就一直遍歷尋找, 找不到的話直接廢棄掉。
// 由於全部的視圖類都是繼承BaseView
- (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 childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 尋找到最合適的view
return fitView;
}
}
// 循環結束,表示沒有比本身更合適的view
return self;
}
複製代碼
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
複製代碼
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if([touch.view isKindOfClass:[XXXXcell class]])
{
return NO;
}
return YES;
}
複製代碼
最後推薦個個人iOS交流羣:834688868,無論你是大牛仍是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!
'有一個共同的圈子很重要,結識人脈!裏面都是iOS開發,全棧發展,歡迎入駐,共同進步!(羣內會免費提供一些羣主收藏的免費學習書籍資料以及整理好的幾百道面試題和答案文檔!)
做者:iOS開發面試總結 連接:www.jianshu.com/p/65dd685e3… 來源:簡書