趁着開年的空閒時間,找了一些面試題寫寫,算是回顧總結一下iOS開發方面的知識, 水平渣,答案僅做參考! 歡迎指導討論,寫的不對或不妥的地方望及時提出! 原題在這裏html
iOS常見面試題基礎篇(附參考答案)看這裏ios
Blockgit
block的實質是什麼?一共有幾種block?都是什麼狀況下生成的?github
簡單的來說,block在OC中的表現能夠看做爲帶有自動變量(局部變量)的匿名函數。面試
C語言函數定義的時候,能夠將函數的地址賦值給函數指針類型的變量,以下:數據結構
int func(int count){
return count + 1;
}
// 賦值
int (*funcprt)(int) = &func;
int result = (*funcprt)(10);複製代碼
同理,咱們也能夠將block語法賦值給聲明爲block類型的變量中。以下:函數
// 聲明一個block類型的變量,其與函數指針類型的變量不一樣之處只有'*'改成'^'
int (^blk)(int);
// 賦值
int (^blk)(int) = ^(int count){return count + 1;};
int result = blk(10);複製代碼
還能夠經過typedef來聲明blk_t
類型變量,以下:oop
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1;};
int result = blk(10)複製代碼
以上解釋了匿名函數,如今來解釋一下帶有自動變量post
int value = 10;
void(^blk)() = ^{
NSLog(@"value === %d", value);
};
blk();
value = 2;
NSLog(@"%@", value);複製代碼
value結果爲 10 。在block中,block表達式截獲所使用的自動變量的值,即保存該變量的瞬間值,因此在執行了block後,改變block外自動變量的值,並不會影響block執行時自動變量的值。ui
block的本質
關於block的本質, ibireme大神objc 中的 block這篇博客裏,有詳細的分析。
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};複製代碼
根據block的數據結構能夠發現,它是含有*isa
指針的,在OC中根據對象的定義,凡是首地址是*isa的結構體指針,均可以認爲是對象(id)。這樣在objc中,block實際上就算是對象。
既然OC處理Block是按照對象來處理的。在iOS中,常見的就是_NSConcreteStackBlock
,_NSConcreteMallocBlock
,_NSConcreteGlobalBlock
這3種,還有另外幾種,暫不作討論。
ARC下:
void(^blockA)() = ^{
NSLog(@"just a block");
};
NSLog(@"%@", blockA);
int value = 10;
void(^blockB)() = ^{
NSLog(@"just a block === %d", value);
};
NSLog(@"%@", blockB);複製代碼
ARC下打印結果以下:
<__NSGlobalBlock__: 0x10c81c308> <__NSMallocBlock__: 0x60400025d160>
MRC下打印結果以下: <__NSGlobalBlock__: 0x1056bf308> <__NSStackBlock__: 0x7ffeea540a48>
咱們對棧上的block作copy操做:
NSLog(@"%@", [blockB copy])
結果爲: <__NSMallocBlock__: 0x600000444b60>
由此咱們能夠得出如下結果:
當block沒有引用外部局部變量的時候,block爲全局block
當block引用外部局部變量的時候,ARC下爲堆block
,MRC下爲棧block
,此時對MRC下的棧block進行copy,棧block
就變爲堆block
。在ARC下,編譯器會把block從棧拷貝到堆。
經驗證,當block只引用靜態變量,全局變量的時候,block均爲全局block
爲何在默認狀況下沒法修改被block捕獲的變量? __block都作了什麼?
__block
的自動變量,通過編譯後會變成一個結構體,經過結構體中的__forwarding
指針能夠訪問到變量,天然就能夠修改變量了。模擬一下循環引用的一個狀況?block實現界面反向傳值如何實現?
循環引用場景
// 當block做爲屬性時:
@property(nonatomic, copy) void(^block)();
self.block = ^{
NSLog(@"%@",self);
}
複製代碼
此時會出現警告:
界面反向傳值
//SecondViewController.h
#import < UIKit/UIKit.h>
typedef void(^CallBackBlock) (NSString *string);
@interface SecondViewController : UIViewController
@property (nonatomic,strong)UItextField *textField;
@property (nonatomic,copy)CallBackBlcok callBackBlock;
@end
// 在implementation中添加一個點擊事件:
- (IBAction)click:(id)sender {
self.callBackBlock(_textField.text);
[self.navigationController popToRootViewControllerAnimated:YES];
}
複製代碼
在FirstViewController中:
// FirstViewController.m
- (IBAction)push:(id)sender {
SecondViewController *secondVC = [[SecondViewController alloc]init]
secondVC.callBackBlock = ^(NSString *string){
NSLog(@"string is %@",string);
self.label.text = string;
};
[self.navigationController pushViewController:secondVC animated:YES];
}複製代碼
思考一下這個問題: ARC下會發生什麼? MRC呢?若blockA
並無引用自動變量val
的話狀況又是什麼樣?
@property(nonatomic, weak) void(^block)();
- (void)viewDidLoad {
[superviewDidLoad];
int val = 10;
void(^blockA)() = ^{
NSLog(@"val:%d", val);
};
NSLog(@"%@", blockA);
_block = blockA;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"%@", _block);
}複製代碼
Runtime
objc在向一個對象發送消息時,發生了什麼?
衆所周知,在objc中方法調用的本質是發消息,例如:
[obj message];
// 運行時會轉化爲:
objc_msgSend(obj, selector)
// 當有參數時:
[obj message:(id)arg...];
// 運行時會轉化爲:
objc_msgSend(obj, selector, arg1, arg2, ...)複製代碼
消息在運行時纔會與方法綁定
當向一個對象發送消息時:
1.首先檢測這個 selector 是否是要忽略。好比 Mac OS X 開發,有了垃圾回收就不理會 retain,release 這些函數。
2.檢測這個 selector 的 target 是否是 nil,Objc 容許咱們對一個 nil 對象執行任何方法不會 Crash,由於運行時會被忽略掉。
3.若是上面兩步都經過了,那麼就開始查找這個類的實現 IMP,先從 cache 裏查找,若是找到了就運行對應的函數去執行相應的代碼。
4.若是 cache 找不到就找類的方法列表中是否有對應的方法。 若是類的方法列表中找不到就到父類的方法列表中查找,一直找到 NSObject 類爲止。
5.若是還找不到,就要開始進入消息轉發流程
以下圖所示:
何時會報unrecognized selector錯誤?iOS有哪些機制來避免走到這一步?
當調用某對象上某個方法,而該對象並無實現這個方法的時候, 能夠經過消息轉發進行解決。
消息轉發步驟以下:
objc運行時會調用 +resolveInstanceMethod:
或者 +resolveClassMethod:
,讓咱們有機會提供一個函數實現。若是你添加了函數,那運行時系統就會從新啓動一次消息發送的過程,不然,運行時就會移到下一步,消息轉發(Message Forwarding)。
調用forwardingTargetForSelector:
方法,嘗試找到一個能響應該消息的對象。若是獲取到,則直接轉發給它。若是返回了nil,繼續下面的動做。
調用methodSignatureForSelector:
方法,嘗試得到一個方法簽名。若是獲取不到,則直接調用doesNotRecognizeSelector拋出異常。若是返回了一個方法簽名,Runtime就會建立一個NSInvocation對象,而後繼續進行第四步
調用forwardInvocation:
方法,將地3步獲取到的方法簽名包裝成Invocation
傳入,如何處理就在這裏面了。
如圖所示:
可否向編譯後獲得的類中增長實例變量?可否向運行時建立的類中添加實例變量?爲何?
runtime如何實現weak變量的自動置nil?
給類添加一個屬性後,在類結構體裏哪些元素會發生變化?
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */複製代碼
instance_size
和屬性列表:objc_ivar_list *ivars
會發生改變。RunLoop
runloop是來作什麼的?runloop和線程有什麼關係?主線程默認開啓了runloop麼?子線程呢?
runloop字面的意思就是跑圈,實際上App能一直不停的運行下去,runloop功不可沒!咱們來分析一下main函數:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}複製代碼
若是沒有runloop,線程執行完以後就會退出,就不能再執行任務了。這時咱們就須要採用一種方式來讓線程可以處理任務,並不退出。因此,咱們就有了RunLoop,其中UIApplicationMain
函數內部幫咱們開啓了主線程的RunLoop,UIApplicationMain
內部擁有一個無線循環的代碼。
runloop與線程對應關係以下:
runloop的mode是用來作什麼的?有幾種mode?
在ibireme大神的深刻理解 Runloop一文中,詳細的介紹了一個Runloop包含了哪些東西,以下圖所示:
常見的Mode以下:
1.kCFRunLoopDefaultMode
:App的默認運行模式,一般主線程是在這個運行模式下運行 2.UITrackingRunLoopMode
:跟蹤用戶交互事件(用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘Mode影響) 3.UIInitializationRunLoopMode
:在剛啓動App時第進入的第一個 Mode,啓動完成後就再也不使用 4.GSEventReceiveRunLoopMode
:接受系統內部事件,一般用不到 5.kCFRunLoopCommonModes
:僞模式,不是一種真正的運行模式
爲何把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運行循環之後,滑動scrollview的時候NSTimer卻不動了?
蘋果是如何實現Autorelease Pool的?
autorelease
一個被autorelease修飾的對象會被加到最近的autoreleasePool
中,當這個autoreleasePool
自身drain的時候,其中的autoreleased對象會被release
autoreleasePool是怎麼實現的?
1.AutoreleasePool
並無單獨的結構,而是由若干個AutoreleasePoolPage
以雙向鏈表的形式組合而成
2.AutoreleasePoolPage
對象會記錄autorelease
對象地址
3.AutoreleasePool的操做時經過如下這幾個函數實現的:objc_autoreleasepoolPush
,objc_autoreleasepoolPop
,objc_autorelease
4.Autorelease
對象是在當前的runloop
迭代結束時釋放的,而它可以釋放的緣由是系統在每一個runloop
迭代中都加入了autorelease
的Push和Pop
推薦你們閱讀這篇黑幕背後的Autorelease
類結構
isa指針?(對象的isa,類對象的isa,元類的isa都要說)
isa
指針,在OC中類也是一種對象,它屬於元類metaClasss
,對象的isa
指針指向類,類的isa
指針指向元類,元類的isa
指針指向父類的元類,一直到根元類,最後根元類的isa
指針指向了自身,如圖:
類方法和實例方法有什麼區別?
methodLists
裏面,而實例方法位於類結構體的methodLists
中。介紹一下分類,能用分類作什麼?內部是如何實現的?它爲何會覆蓋掉原來的方法?
分類通過編譯後也會成爲一個結構體:
struct category_t {
const char *name; // 類名
classref_t cls; // 分類所屬的類
struct method_list_t *instanceMethods; // 實例方法列表
struct method_list_t *classMethods; // 類方法列表
struct protocol_list_t *protocols; // 遵循的協議列表
struct property_list_t *instanceProperties; // 屬性列表
};複製代碼
在運行時,category
會被附加到類上面,包括把category
的實例方法、協議以及屬性添加到類上和category
的類方法和協議添加到類的metaclass
上。
category
的方法沒有徹底替換掉原來類已經有的方法,也就是說若是category
和原來類都有methodA,那麼category
附加完成以後,類的方法列表裏會有兩個methodA,category
的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的後面,這也就是咱們日常所說的category
的方法會「覆蓋」掉原來類的同名方法,這是由於運行時在查找方法的時候是順着方法列表的順序查找的,它只要一找到對應名字的方法,就會罷休^_^,卻不知後面可能還有同樣名字的方法。
推薦你們閱讀美團出品的深刻理解Objective-C:Category,我是知識的搬運工~ 也歡迎你們給我推薦高質量的文章!獨樂樂不如衆樂樂~
運行時能增長成員變量麼?能增長屬性麼?若是能,如何增長?若是不能,爲何?
class_addIvar
給指定的類添加成員變量,可是不能爲已經生成的類添加,運行時規定,只能在objc_allocateClassPair
與objc_registerClassPair
兩個函數之間爲類添加變量。緣由在上面的題目中有過解釋。class_addProperty
給指定的類添加屬性,能夠成功添加了屬性可是不能用點調用法調用,能夠利用KVC/關聯方式來該表這個屬性的值runtime
的objc_setAssociatedObject
和objc_getAssociatedObject
方法來實現關聯,給分類添加屬性就是利用這個方法實現的。objc中向一個nil對象發送消息將會發生什麼?(返回值是對象,是標量,結構體)
[NSNull null]
對象發送消息時,是會crash的。參考博客: