最近由于換工作的原因,需要參加面試,然后就到處收集了一些當下的面試題供自己復習使用。下面就分享出來,希望能夠幫到你們~
1、ViewController 的loadView、viewDidLoad、viewDidUnload分別是什么時候調用的? 作用是什么?
(1) loadView 當第一次使用控制器的view時,會調用loadView方法創建view,一般在這里自定義view 。
(2) viewDidLoad 當控制器的view創建完畢時會調用,也就是在loadView后調用,一般在這里添加子控件、初始化數據。
(3) viewDidUnload 當控制器的view因為內存警告被銷毀時調用,一般在這里回收跟界面相關的資源(界面都會銷毀了,跟界面相關的資源肯定不要了)。
2、ViewController的didReceiveMemoryWarning是在什么時候調用的?默認的操作是什么?
當應用程序接收到系統的內容警告時,就有可能調用控制器的didReceiveMemoryWarning方法。
它的默認做法是:當控制器的view不在窗口上顯示時,就會直接銷毀,并且調用viewDidUnload方法。
3、控制器 View的生命周期及相關函數是什么?你在開發中是如何用的?
(1) 首先判斷控制器是否有視圖,如果沒有就調用loadView方法創建:通過storyboard或者代碼;
(2) 隨后調用viewDidLoad,可以進行下一步的初始化操作;(只會被調用一次)
(3) 在視圖顯示之前調用viewWillAppear;(該函數可以多次調用)
(4) 視圖已經出現,調用viewDidAppear;
(5) 在視圖消失之前調用viewWillDisappear;(該函數可以多次調用)
(6) 在布局變化前后,調用viewWill/DidLayoutSubviews處理相關信息。
4、AppDelegate代理方法調用順序:
//加載完main函數之后,調用此方法,進入程序載入
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"程序載入后");
return YES;
}
//程序將要失去Active狀態時調用,比如有電話進來或者按下Home鍵,之后程序進入后臺狀態
- (void)applicationWillResignActive:(UIApplication *)application {
NSLog(@"將要進入非活動狀態");
}
//點擊home鍵進入后臺時調用此方法
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(@"進入后臺");
}
//喚醒進入后臺的app時調用
- (void)applicationWillEnterForeground:(UIApplication *)application {
NSLog(@"從后臺進入前臺");
}
//app加載完主視圖之后進入活動狀態時或者從后臺喚醒之后調用
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSLog(@"進入活動狀態");
}
//程序被殺死時調用
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(@"程序將要退出");
}
5、定義屬性時,什么情況使用copy、assign、retain?
(1) copy:NSString、Block等類型
(2) assign:非OC對象類型,基本數據類型
(3) retain:OC對象類型
6、什么情況使用 weak 關鍵字?
(1) 在 ARC 中,在有可能出現循環引用的時候,往往要通過讓其中一端使用 weak 來解決,比如 delegate 代理屬性。
(2) 自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak。
(3) IBOutlet 控件屬性一般也使用 weak。當然,也可以使用 strong,但是建議使用 weak。
7、weak 和 assign 的不同點:
(1) weak 策略在屬性所指的對象遭到摧毀時,系統會將 weak 修飾的屬性對象的指針指向 nil,在 OC 給 nil 發消息是不會有什么問題的;如果使用 assign 策略在屬性所指的對象遭到摧毀時,屬性對象指針還指向原來的對象,由于對象已經被銷毀,這時候就產生了野指針,如果這時候在給此對象發送消息,很容造成程序奔潰。
(2) assigin 可以用于修飾非 OC 對象,而 weak 必須用于 OC 對象。
8、下面兩種方式都是弱引用代理對象,為什么第一種在代理對象被釋放后不會導致崩潰,而第二種會導致崩潰?
1.@property (nonatomic, weak) id delegate;
2.@property (nonatomic, assign) id delegate;
原因:weak和assign是一種“非擁有關系”的指針,通過這兩種修飾符修飾的指針變量,都不會改變被引用對象的引用計數。但是在一個對象被釋放后,weak會自動將指針指向nil,而assign則不會。在iOS中,向nil發送消息時不會導致崩潰的,所以assign就會導致野指針的錯誤unrecognized selector sent to instance。所以我們如果修飾代理屬性,還是用weak修飾吧,比較安全。
9、nonatomic 和 atomic的區別:
(1) nonatomic,非原子性,多線程訪問修改不加鎖。
(2) atomic,原子性,多線程訪問加鎖。
iOS 推薦我們使用 nonatomic,移動端的開發沒有復雜的多線程場景,不加鎖解鎖可以提高效率。
系統的可變對象,NSMutableArray,NSMutabelString 都是線程不安全的,多線程修改,需要加鎖。
10、@property屬性關鍵字詳解:
atomic: 默認關鍵字,也就是說如果什么都不寫,默認就是這個。表示該屬性是線程同步的。一般用不到,會影響性能。
nonatomic: 非線程同步,基本都是用這個。
readwrite: 默認關鍵字,表示可讀可寫。
readonly: 只能讀(get),不能寫(set),當你希望屬性不能被外界直接修改,但是可以訪問時使用。
writeonly: 只能寫(set),不能讀(get)。一般用不到。
assign: 默認關鍵字。非對象類型一般使用此關鍵字。
retain: 對象的引用計數+1。ARC下已經不再使用此關鍵字,用strong代替。
copy: 拷貝一個新的對象,新對象的引用計數+1,原對象不變。
strong: 對象的引用計數+1。作用于OC對象,能夠維持對象的生命。
11、深拷貝和淺拷貝區別:
(1) 深拷貝就是把內容拷貝一份產生一份新的對象,新對象計數器為1,源對象計數器不變。
(2) 淺拷貝是指針拷貝,把地址給你,你和我指向同一個對象,源對象計數器加一,源對象和副本的計數器相同。
(3) 我們知道在OC中的拷貝函數有copy和mutablecopy,只要你調用了copy不管是NSString,NSDictionary還是NSArray還是NSMutableString還是NSMutableDictionary,還是NSMutableArray都是copy出來是不可變的副本。
(4) 可變對象的copy和mutableCopy方法都是深拷貝;不可變對象的copy方法是淺拷貝,mutableCopy的方法是深拷貝。
12、如何令自己所寫的對象具有拷貝功能?
(1) 如果想讓自己的類具備copy方法,并返回不可邊類型,必須遵循NSCopying協議,并且實現
- (id)copyWithZone:(NSZone *)zone
。
(2) 如果讓自己的類具備mutableCopy方法,并且返回可變類型,必須遵守NSMutableCopying,并實現- (id)mutableCopyWithZone:(nullable NSZone *)zone
。
注意:再此說的copy對應不可邊類型和mutableCopy對應不可邊類型方法,都是遵從系統規則而已。如果你想實現自己的規則,也是可以的。
13、Block為什么使用copy修飾?
(1) Block內部沒有調用外部變量時存放在全局區(ARC和MRC下均是)。
(2) Block使用了外部變量,這種情況也正是我們平時所常用的方式,Block的內存地址顯示在棧區,棧區的特點就是創建的對象隨時可能被銷毀,一旦被銷毀后續再次調用空對象就可能會造成程序崩潰,在對block進行copy后,block將存放在堆區。所以在使用Block屬性時使用Copy修飾,而在ARC模式下,系統也會默認對Block進行copy操作。
14、你知道幾種iOS中消息傳遞方式?
(1) 通知:在iOS中由通知中心進行消息接收和消息廣播,是一種一對多的消息傳遞方式。
(2) 代理:是一種通用的設計模式,iOS中對代理支持的很好,由代理對象、委托者、協議三部分組成。
(3) block:iOS4.0中引入的一種回調方法,可以將回調處理代碼直接寫在block代碼塊中,看起來邏輯清晰代碼整齊。
(4) target action:通過將對象傳遞到另一個類中,在另一個類中將該對象當做target的方式,來調用該對象方法,從內存角度來說和代理類似。
(5) KVO:NSObject的Category-NSKeyValueObserving,通過屬性監聽的方式來監測某個值的變化,當值發生變化時調用KVO的回調方法。
15、self. 跟 self-> 什么區別?
(1) self. 是調用get方法或者set方法
(2) self是當前本身,是一個指向當前對象的指針
(3) self->是直接訪問成員變量
16、id 聲明的變量有什么特性?
id聲明的變量能指向任何OC對象。
17、id 和 NSObject*的區別:
(1) id是一個 objc_object 結構體指針,定義是 typedef struct objc_object *id。
(2) id可以理解為指向對象的指針。所有oc的對象 id都可以指向,編譯器不會做類型檢查,id調用任何存在的方法都不會在編譯階段報錯,當然如果這個id指向的對象沒有這個方法,該崩潰還是會崩潰的。
(3) NSObject *指向的必須是NSObject的子類,調用的也只能是NSObjec里面的方法否則就要做強制類型轉換。
(4) 不是所有的OC對象都是NSObject的子類,還有一些繼承自NSProxy。NSObject * 可指向的類型是id的子集。
18、談談 instancetype 和 id 的區別:
(1) 相同點:都可以作為方法的返回類型。
(2) 不同點:instancetype可以返回和方法所在類相同類型的對象,id只能返回未知類型的對象;instancetype只能作為返回值,不能像id那樣作為參數。
19、isKindOfClass和isMemberOfClass的區別:
isKindOfClass 用來確定一個對象是否是一個類的成員,或者是派生自該類的成員。
isMemberOfClass只能確定一個對象是否是當前類的成員。
20、setObject: forKey: 和 setValue: forKey: 的比較:
介紹:
(1)- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;
(字典專屬方法):
正常情況下就是這樣的,value 和 key都不為nil;
當兩者有一個為nil的時候就直接崩潰;
而且你要存空值只能存ObjectType類型的,也就是[NSNull null]空對象;
打印的時候如果key不存在,取出來的值是(null);
如果key存在,存的是空對象,打印就是<null>。
(2)- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;
(NSObject KVC的核心方法):
其實調用的就是上面介紹的setObject:forKey的方法 如果value是nil。那么就是調用remveObjectForKey了;
這里value可以為nil,但是key還是不能為nil;
當value為nil的時候就是remveObject:forKey;
打印的時候如果key不存在,取出來的值是(null)。
區別:
1.首先兩者的key都不能為nil,setVlue:forKey:
的key只能是字符串類型,setObject:forKey:
的key可以是任意對象。
2.setValue:forKey:
中的value可以為nil,為nil的時候調用removeObject:forKey:
,setObject:forKey:
中value不可以為nil。
3.setObject是字典專屬的方法,setVlue是任何對象的方法KVC的核心方法,例如
People *p1 = [[People alloc] init];
[p1 setValue:@"mkj" forKey:@"name"];
當對象有name屬性的時候就是通過KVC來賦值。
21、分類和繼承的區別:
(1) 分類可以在不修改原來類模型的基礎上拓充方法
(2) 分類只能擴充方法、不能擴充成員變量;繼承可以擴充方法和成員變量
(3) 繼承會產生新的類
22、分類和擴展區別:
(1) 分類是有名稱的,類擴展沒有名稱
(2) 分類只能擴充方法、不能擴充成員變量;類擴展可以擴充方法和成員變量
(3) 類擴展一般就寫在.m文件中,用來擴充私有的方法和成員變量(屬性)
23、Object-C有多繼承嗎?沒有的話用什么代替?
(1) OC是單繼承,沒有多繼承
(2) 有時可以用分類和協議來代替多繼承
24、@dynamic 與 @synthesize 關鍵詞:
(1) @property有兩個對應的詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那么默認的就是@syntheszie var = _var;
(2) @synthesize的語義是如果你沒有手動實現setter方法和getter方法,那么編譯器會自動為你加上這兩個方法。
(3) @dynamic告訴編譯器,屬性的setter與getter方法由用戶自己實現,不自動生成。(當然對于readonly的屬性只需提供getter即可)。假如一個屬性被聲明為@dynamic var,然后你沒有提供@setter方法和@getter方法,編譯的時候沒問題,但是當程序運行到instance.var =someVar,由于缺setter方法會導致程序崩潰;或者當運行到 someVar = var時,由于缺getter方法同樣會導致崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
25、static的作用?
(1) static修飾的函數是一個內部函數,只能在本文件中調用,其他文件不能調用
(2) static修飾的全部變量是一個內部變量,只能在本文件中使用,其他文件不能使用
(3) static修飾的局部變量只會初始化一次,并且在程序退出時才會回收內存
26、iOS 并行和并發:
概念:
1.并發:當有多個線程在操作時,如果系統只有一個CPU,則它根本不可能真正同時進行一個以上的線程,它只能把CPU運行時間劃分成若干個時間段,再將時間 段分配給各個線程執行,在一個時間段的線程代碼運行時,其它線程處于掛起狀。這種方式我們稱之為并發(Concurrent)。
2.并行:當系統有一個以上CPU時,則線程的操作有可能非并發。當一個CPU執行一個線程時,另一個CPU可以執行另一個線程,兩個線程互不搶占CPU資源,可以同時進行,這種方式我們稱之為并行(Parallel)。
區別:
并發和并行是即相似又有區別的兩個概念,并行是指兩個或者多個事件在同一時刻發生;而并發是指兩個或多個事件在同一時間間隔內發生。
舉例:
1.并發:一個送外賣的A需要把兩份外賣分別送到兩個客戶B和C手里。 A必須先送完B外賣才能接著送C的。這就是并發。
2.并行:客戶C 分別從餓了么和美團訂了一共兩份外賣。那么外賣員A和外賣員B需要把外賣一同送到客戶C手里。 這就是并行。
27、OC在向一個對象發送消息時,發生了什么?
根據對象的 isa 指針找到類對象 id,在查詢類對象里面的 methodLists 方法函數列表,如果沒有找到,在沿著 superClass ,尋找父類,再在父類 methodLists 方法列表里面查詢,最終找到 SEL ,根據 id 和 SEL 確認 IMP(指針函數),在發送消息。
28、一個OC對象的 isa 的指針指向什么?有什么作用?
每一個對象內部都有一個isa指針,這個指針是指向它的真實類型,根據這個指針就能知道將來調用哪個類的方法。
29、下面的代碼輸出什么?
@implementation Son : NSObject
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
答案:都輸出 Son
1.class 獲取當前方法的調用者的類,superClass 獲取當前方法的調用者的父類,super 僅僅是一個編譯指示器,就是給編譯器看的,不是一個指針。
2.本質:只要編譯器看到super這個標志,就會讓當前對象去調用父類方法,本質還是當前對象在調用。
這個題目主要是考察關于OC中對 self 和 super 的理解:
1.self 是類的隱藏參數,指向當前調用方法的這個類的實例。而 super 本質是一個編譯器標示符,和 self 是指向的同一個消息接受者。
2.當使用 self 調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中再找。
3.而當使用 super 時,則從父類的方法列表中開始找,然后調用父類的這個方法。
4.調用 [self class] 時,會轉化成 objc_msgSend 函數。
30、init方法中的 if (self = [super init]),在super init的時候,為什么要賦值給self,直接super init不就可以了么?
NSString alloc init之后,可能返回各種不同的對象,這些對象并不是NSString對象,那么,init的時候,肯定改變了返回的對象,這時候必須賦值給self,不然返回的對象獲取不到它的指針,內存就leak了。
31、什么時候會報unrecognized selector錯誤?iOS有哪些機制來避免走到這一步?
當發送消息的時候,我們會根據類里面的 methodLists 列表去查詢我們要動用的SEL,當查詢不到的時候,我們會一直沿著父類查詢,當最終查詢不到的時候我們會報 unrecognized selector 錯誤,當系統查詢不到方法的時候,會調用
+(BOOL)resolveInstanceMethod:(SEL)sel
動態解釋的方法來給我一次機會來添加調用不到的方法。或者我們可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector
重定向的方法來告訴系統,該調用什么方法,可以保證不會崩潰。
32、能否向編譯后得到的類中增加實例變量?能否向運行時創建的類中添加實例變量?為什么?
1、不能向編譯后得到的類增加實例變量。
2、能向運行時創建的類中添加實例變量。
原因:1. 編譯后的類已經注冊在 Runtime 中,類結構體中的 objc_ivar_list 實例變量的鏈表和 instance_size 實例變量的內存大小已經確定,Runtime會調用 class_setvarlayout 或 class_setWeaklvarLayout 來處理strong weak 引用。所以不能向存在的類中添加實例變量。2. 運行時創建的類是可以通過調用class_addIvar函數添加實例變量的。但是是在調用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。
33、Runtime 如何實現 weak 變量的自動置nil ?
Runtime 對注冊的類,會進行布局,對于 weak 對象會放入一個 hash 表中,用 weak 指向的對象內存地址作為 key。當此對象的引用計數為0的時候會 dealloc,假如 weak 指向的對象內存地址是a,那么就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設置為 nil。
34、給類添加一個屬性后,在類結構體里哪些元素會發生變化?
instance_size :實例的內存大小;
objc_ivar_list *ivars:屬性列表。
35、RunLoop是來做什么的?RunLoop和線程有什么關系?主線程默認開啟了RunLoop么?子線程呢?
RunLoop:從字面意思看:運行循環、跑圈,其實它內部就是 do-while 循環,在這個循環內部不斷地處理各種任務(比如Source、Timer、Observer)事件。
RunLoop 和線程的關系:一個線程對應一個 RunLoop,主線程的 RunLoop 默認創建并啟動,子線程的 RunLoop 需手動創建且手動啟動(調用run方法)。RunLoop 只能選擇一個 Mode 啟動,如果當前 Mode 中沒有任何Source(Sources0、Sources1)、Timer,那么就直接退出 RunLoop。
36、RunLoop的Mode是用來做什么的?有幾種Mode?
Mode 是 RunLoop 里面的運行模式,不同的模式下的 RunLoop 處理的事件和消息有一定的差別。
系統默認注冊了5個Mode:
(1)kCFRunLoopDefaultMode: 相當于NSDefaultRunLoopMode,App的默認 Mode,通常主線程是在這個 Mode 下運行的。
(2)UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響。
(3)UIInitializationRunLoopMode: 在剛啟動 App 時進入的第一個 Mode,啟動完成后就不再使用。
(4)GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到。
(5)kCFRunLoopCommonModes: 相當于NSRunLoopCommonModes,是 NSDefaultRunLoopMode 和 UITrackingRunLoopMode 的結合。在主線程中兩者都有,在子線程中只有NSDefaultRunLoopMode的功能。
37、為什么把NSTimer對象以 NSDefaultRunLoopMode 添加到主線程循環以后,滑動scrollview的時候NSTimer卻不動了?
NSTimer對象是在 NSDefaultRunLoopMode下面調用消息的,但是當我們滑動scrollview的時候,NSDefaultRunLoopMode模式就自動切換到UITrackingRunLoopMode模式下面,卻不可以繼續響應NSTimer發送的消息。所以如果想在滑動scrollview的情況下面還調用NSTimer的消息,我們可以把RunLoop的模式更改為NSRunLoopCommonModes。
38、為什么總是要把RunLoop和線程放在一起來講?
總的來講就是:RunLoop是保證線程不會退出,并且能在不處理消息的時候讓線程休眠,節約資源,在接收到消息的時候喚醒線程做出對應處理的消息循環機制。它是寄生于線程的,所以提到RunLoop必然會涉及到線程。
39、RunLoop本質:
RunLoop本質就是個Event Loop的do while循環,所以運行到這一行以后子線程就一直在進行接受消息->等待->處理的循環。所以不會運行[runLoop run];之后的代碼(這點需要注意,在使用RunLoop的時候如果要進行一些數據處理之類的要放在這個函數之前否則寫的代碼不會被執行),也就不會因為任務結束導致線程死亡進而銷毀。這也就是我們最常使用RunLoop的場景之一,就如小節標題保持線程的存活,而不是線性的執行完任務就退出了。
①.RunLoop是寄生于線程的消息循環機制,它能保證線程存活,而不是線性執行完任務就消亡。
②.RunLoop與線程是一一對應的,每個線程只有唯一與之對應的一個RunLoop。我們不能創建RunLoop,只能在當前線程當中獲取線程對應的RunLoop(主線程RunLoop除外)。
③.子線程默認沒有RunLoop,需要我們去主動開啟,但是主線程是自動開啟了RunLoop的。
④.RunLoop想要正常啟用需要運行在添加了事件源的Mode下。
⑤.RunLoop有三種啟動方式run、runUntilDate:(NSDate *)limitDate、runMode:(NSString *)mode beforeDate:(NSDate *)limitDate。第一種無條件永遠運行RunLoop并且無法停止,線程永遠存在。第二種會在時間到后退出RunLoop,同樣無法主動停止RunLoop。前兩種都是在NSDefaultRunLoopMode模式下運行。第三種可以選定運行模式,并且在時間到后或者觸發了非Timer的事件后退出。
40、蘋果是如何實現Autorelease Pool的?
Autorelease Pool作用:緩存池,可以避免我們經常寫relase的一種方式。其實就是延遲release,將創建的對象,添加到最近的Autorelease Pool中,等到Autorelease Pool作用域結束的時候,會將里面所有的對象的引用計數器Autorelease。
41、對象是什么時候被釋放的?
每個OC對象都有一個引用計數器,每個新對象的計數器是1,當對象的計數器減為0時,就會被銷毀。
42、用過NSOperationQueue嗎? 為什么要使用 NSOperationQueue?實現了什么? 簡述它和GCD的區別和類似的地方(提示:可以從兩者的實現機制和適用范圍來述)?
使用NSOperationQueue用來管理子類化的NSOperation對象,控制其線程并發數目。
GCD和NSOperation都可以實現對線程的管理,區別是NSOperation和NSOperationQueue是多線程的面向對象抽象。項目中使用NSOperation的優點是 NSOperation是對線程的高度抽象,在項目中使用它,會使項目的程序結構更好,子類化 NSOperation的設計思路,是具有面向對象的優點(復用、封裝),建議在復雜項目中使用。
GCD的優點是GCD本身非常簡單、易用,對于不復雜的多線程操作,會節省代碼量,而Block參數的使用,會使代碼更為易讀,建議在簡單項目中使用。
區別:
(1) GCD是純C語言的API,NSOperationQueue是基于GCD的OC版本封裝。
(2) GCD只支持FIFO的隊列,NSOperationQueue可以很方便地調整執行順序、設置最大并發數量。
(3) NSOperationQueue可以在輕松在Operation間設置依賴關系,而GCD 需要寫很多的代碼才能實現。
(4) NSOperationQueue支持KVO,可以監測operation是否正在執行 (isExecuted)、是否結束(isFinished),是否取消(isCanceld) 。
(5) GCD的執行速度比NSOperationQueue快,任務之間不太互相依賴。
43、GCD內部是怎么實現的?
(1)iOS和OS X的核心是XNU內核,GCD是基于XNU內核實現的
(2)GCD的API全部在libdispatch庫中
(3)GCD的底層實現主要有Dispatch Queue和Dispatch Source
(4)Dispatch Queue:管理block(操作)
(5)Dispatch Source:處理事件
44、UIKit類主要在哪一個應用線程上使用?
UIKit的界面類只能在主線程上使用,對界面進行更新。多線程環境中要對界面進行更新必須要切換到主線程上。
45、不同情況下delegate和block的選擇?
(1) 多個消息傳遞,應該使用delegate。在有多個消息傳遞時,用delegate實現更合適,看起來也更清晰。block就不太好了,這個時候block反而不便于維護,而且看起來非常臃腫,很別扭。例如UIKit的UITableView中有很多代理如果都換成block實現,代碼塊會非常臃腫。
(2) 一個委托對象的代理屬性只能有一個代理對象,如果想要委托對象調用多個代理對象的回調應該用block。
46、KVC和KVO的本質:
KVC(Key-value coding)鍵值編碼,就是指iOS的開發中,可以允許開發者通過Key名直接訪問對象的屬性,或者給對象的屬性賦值。而不需要調用明確的存取方法。
(1).KVC要設值,那么就要對象中對應的key,KVC在內部是按什么樣的順序來尋找key的。當調用setValue:屬性值 forKey:@”name“的代碼時,如果沒有找到Set<Key>方法的話,會按照_key,_iskey,key,iskey的順序搜索成員并進行賦值操作;
(2).KVC要取值,當調用valueForKey:@”name“的代碼時,KVC對key的搜索方式不同于setValue:屬性值 forKey:@”name“,其搜索方式如下:首先按get<Key>,<key>,is<Key>的順序方法查找getter方法,找到的話會直接調用。
KVO的本質:簡而言之,蘋果使用了一種isa交換的技術,當objectA被觀察后,objectA對象的isa指針被指向了一個新建的ASClassA的子類NSKVONotifying_ASClassA,且這個子類重寫了被觀察值的setter方法和class方法,dealloc和_isKVO方法,然后使objectA對象的isa指針指向這個新建的類,然后事實上objectA變為了NSKVONotifying_ASClassA的實例對象,執行方法要從這個類的方法列表里找。
47、說說你對通知NSNotification的理解(從同步和異步發送通知來說):
1.同步通知(常用的通知中心發送的通知):等消息發完之后要等處理了消息才跑發送消息之后的代碼,這跟多線程中的同步概念相似。
[[NSNotificationCenter defaultCenter] postNotificationName:K_NOTIFICATION_NAME object:nil];
2.異步通知(通知隊列發送的通知):發送完之后就繼續跑下面的代碼,不需要去管接受通知的處理。
NSNotificationQueue * notificationQueue = [NSNotificationQueue defaultQueue];
NSNotification * notification = [NSNotification notificationWithName:K_NOTIFICATION_NAME object:nil];
[notificationQueue enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationCoalescingOnName forModes:nil];
3.開啟一個其他線程去發送以上通知:(1)同步通知正常同步發送,不影響;(2)異步通知的NSPostingStyle如果為NSPostWhenIdle(空閑時候發送)則不會發送通知;若改為NSPostNow,則會立即同步發送通知。(3)每個線程都有一個通知隊列,當線程結束了,通知隊列就被釋放了,所以當前選擇發送時機為NSPostWhenIdle時也就是空閑的時候發送通知,通知隊列就已經釋放了,所以通知發送不出去了。
如果線程不結束,就可以發送通知了,用runloop讓線程不結束。
4.如果通知隊列發送通知的NSNotificationCoalescing為NSNotificationCoalescingOnName(按名稱合并通知)
如果NSPostingStyle為NSPostWhenIdle(空閑時候發送),則多條信息會合并成一條發送;若為NSPostNow(現在發送),則同步發送多條消息。
就跟dispatch_sync原理一樣,就是得發送因為NSPostNow是同步的,所以發送第一條通知,得等處理完第一條通知,才跑發送第二條通知,這樣肯定就沒有合并消息一說了,因為這有點類似線程阻塞的意思,只有異步,就是三個發送通知全部跑完,在處理通知的時候看是否需要合并和怎么合并,再去處理。
5.通知隊列也可以實現異步,但是真正的異步還是得通過port,底層所有的消息觸發都是通過端口來進行操作的.
48、NSCache優于NSDictionary的幾點?
(1) NSCache 是一個容器類,類似于NSDIctionary,通過key-value 形式存儲和查詢值,用于臨時存儲對象。
注意一點它和NSDictionary區別就是,NSCache 中的key不必實現copy,NSDictionary中的key必須實現copy。
NSCache中存儲的對象也不必實現NSCoding協議,因為畢竟是臨時存儲,類似于內存緩存,程序退出后就被釋放了。
(2) NSCache可以提供自動刪減緩存功能,而且保證線程安全,與字典不同,不會拷貝鍵。
(3) NSCache可以設置緩存上限,限制對象個數和總緩存開銷。定義了刪除緩存對象的時機。這個機制只對NSCache起到指導作用,不會一定執行。
(4) NSPurgeableData搭配NSCache使用,可以自動清除數據。
49、堆和棧的區別?
(1) 堆空間的內存是動態分配的,一般存放對象,并且需要手動釋放內存。
(2) 棧空間的內存由系統自動分配,一般存放局部變量等,不需要手動管理內存。
50、TCP,UDP和socket,Http之間聯系和區別:
(1)TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接的、可靠的、基于字節流的傳輸層通信協議。
(2)UDP 是User Datagram Protocol的簡稱, 中文名是用戶數據報協議,是OSI(Open System Interconnection,開放式系統互聯) 參考模型中一種無連接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務。
(3)socket:網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。
(4)HTTP(超文本傳輸協議)是利用TCP在兩臺電腦(通常是Web服務器和客戶端)之間傳輸信息的協議。客戶端使用Web瀏覽器發起HTTP請求給Web服務器,Web服務器發送被請求的信息給客戶端。
51、TCP和UDP:
TCP:面向連接,可靠傳輸(保證數據正確性,順序性),用于傳輸大量數據(流模式)、速度慢,建立連接開銷比較大(時間,系統資源);流模式:在連接持續的過程中,基本上都是從同一個主機發出的,因此,需要保證數據是有序的到達;三次握手(建立TCP連接,需要C和S發送三個包),四次揮手(TCP連接的斷開需要發送4個包)。
UDP:非連接,不可靠傳輸,速度快,用于傳輸少量數據;只要知道接收端的ip和端口,任何主機都可以向接收端發送數據。
52、https 通信的優點:
(1) 客戶端產生的密鑰只有客戶端和服務器端能得到;
(2) 加密的數據只有客戶端和服務器端才能得到明文;
(3) 客戶端到服務端的通信是安全的。
53、https 建立連接過程:
(1) 客戶端發送請求到服務器端;
(2) 服務器端返回證書和公開密鑰,公開密鑰作為證書的一部分而存在;
(3) 客戶端驗證證書和公開密鑰的有效性,如果有效,則生成共享密鑰并使用公開密鑰加密發送到服務器端;
(4) 服務器端使用私有密鑰解密數據,并使用收到的共享密鑰加密數據,發送到客戶端;
(5) 客戶端使用共享密鑰解密數據;
(6) SSL加密建立。
54、TCP/IP協議中,TCP協議提供可靠的連接服務,采用三次握手建立一個連接:
(1) 第一次握手:建立連接時,客戶端A發送SYN包(SYN=j)到服務器B,并進入SYN_SEND狀態,等待服務器B確認。
(2) 第二次握手:服務器B收到SYN包,必須確認客戶A的SYN(ACK=j+1),同時自己也發送一個SYN包(SYN=k),即SYN+ACK包,此時服務器B進入SYN_RECV狀態。
(3) 第三次握手:客戶端A收到服務器B的SYN+ACK包,向服務器B發送確認包ACK(ACK=k+1),此包發送完畢,客戶端A和服務器B進入ESTABLISHED狀態,完成三次握手。完成三次握手,客戶端與服務器開始傳送數據。
55、按鈕或者其它 UIView控件的事件傳遞的具體過程
觸摸事件的傳遞是從父控件傳遞到子控件也就是UIApplication->window->尋找處理事件最合適的view。注 意: 如果父控件不能接受觸摸事件,那么子控件就不可能接收到觸摸事件。
應用如何找到最合適的控件來處理事件?
(1) 首先判斷主窗口(keyWindow)自己是否能接受觸摸事件。
(2) 判斷觸摸點是否在自己身上。
(3) 子控件數組中從后往前遍歷子控件,重復前面的兩個步驟(所謂從后往前遍歷子控件,就是首先查找子控件數組中最后一個元素,然后執行1、2步驟)。
(4) view,比如叫做fitView,那么會把這個事件交給這個fitView,再遍歷這個fitView的子控件,直至沒有更合適的view為止。
(5) 如果沒有符合條件的子控件,那么就認為自己最合適處理這個事件,也就是自己是最合適的view。
56、簡單說一下時間響應的流程?
(1) 一個 UIView 發出一個事件之后,首先上傳給其父視圖;
(2) 然后父視圖上傳給其所在的控制器;
(3) 如果其控制器對事件進行處理,事件傳遞將終止,否則繼續上傳父視圖;
(4) 直到遇到響應者才會停止,否則事件將一直上傳,直到 UIWindow。
57、UIView不能接收觸摸事件的三種情況:
(1) 不允許交互:userInteractionEnabled = NO。
(2) 隱藏:如果把父控件隱藏,那么子控件也會隱藏,隱藏的控件不能接受事件。
(3) 透明度:如果設置一個控件的透明度<0.01,會直接影響子控件的透明度。alpha:0.0~0.01為透明。
注 意:默認UIImageView不能接受觸摸事件,因為不允許交互,即userInteractionEnabled = NO。所以如果希望UIImageView可以交互,需要設置UIImageView的userInteractionEnabled = YES。
58、iOS程序在調用main函數之前的啟動:
系統先讀取App的可執行文件(Mach-O文件),從里面獲得dyld的路徑,然后加載dyld,dyld去初始化運行環境,開啟緩存策略,加載程序相關依賴庫(其中也包含我們的可執行文件),并對這些庫進行鏈接,最后調用每個依賴庫的初始化方法,在這一步,runtime被初始化。當所有依賴庫的初始化后,輪到最后一位(程序可執行文件)進行初始化,在這時runtime會對項目中所有類進行類結構初始化,然后調用所有的load方法。最后dyld返回main函數地址,main函數被調用,我們便來到了熟悉的程序入口。
59、簡單說一下 APP的啟動過程,從 main文件開始說起:
有storyboard情況下:
(1) main函數;
(2) UIApplicationMain,創建UIApplication對象,創建UIApplication的delegate對象;
(3) 根據Info.plist獲得最主要storyboard的文件名,加載最主要的 storyboard(有storyboard);
(4) 創建UIWindow;
(5) 創建和設置UIWindow的rootViewController;
(6) 顯示窗口;
沒有storyboard情況下:
(1) main函數;
(2) UIApplicationMain,創建UIApplication對象,創建UIApplication的delegate對象;
(3) delegate對象開始處理(監聽)系統事件(沒有storyboard);
(4) 程序啟動完畢的時候, 就會調用代理的;application:didFinishLaunchingWithOptions:方法,并在application:didFinishLaunchingWithOptions:中創建UIWindow;
(5) 創建和設置UIWindow的rootViewController;
(6) 顯示窗口。
60、鏈表和數組的區別:
二者都屬于一種數據結構。
從邏輯結構來看:
(1) 數組必須事先定義固定的長度(元素個數),不能適應數據動態地增減的情況。當數據增加時,可能超出原先定義的元素個數;當數據減少時,造成內存浪費;數組可以根據下標直接存取。
(2) 鏈表動態地進行存儲分配,可以適應數據動態地增減的情況,且可以方便地插入、刪除數據項。(數組中插入、刪除數據項時,需要移動其它數據項,非常繁瑣)鏈表必須根據 next 指針找到下一個元素。
從內存存儲來看:
(1) (靜態)數組從棧中分配空間,對于程序員方便快速,但是自由度小 。
(2) 鏈表從堆中分配空間,,自由度大但是申請管理比較麻煩。
從上面的比較可以看出,如果需要快速訪問數據,很少或不插入和刪除元素,就應該用數組;相反,如果需要經常插入和刪除元素就需要用鏈表數據結構了。
61、load 和 initialize 有什么用處?
load:
(1) 當類對象被引入項目時,Runtime 會向每一個類對象發送 load 消息。
(2) load 方法會在每一個類甚至分類被引入時僅調用一次。調用的順序:父類優先于子類, 子類優先于分類。
(3) 由于 load 方法會在類被 import 時調用一次,而這時往往是改變類的行為的最佳時機,在這里可以使用例如 method swizlling 來修改原有的方法。
(4) load 方法不會被類自動繼承。
initialize:
也是在第一次使用這個類的時候會調用這個方法,也就是說 initialize 也是懶加載。
62、load和initialize區別:
(1) load和initialize方法都會在實例化對象之前調用,以main函數為分水嶺,前者在main函數之前調用,后者在之后調用。這兩個方法會被自動調用,不能手動調用它們。
(2) load和initialize方法都不用顯示的調用父類的方法而是自動調用,即使子類沒有initialize方法也會調用父類的方法,而load方法則不會調用父類。
(3) load方法通常用來進行Method Swizzle,initialize方法一般用于初始化全局變量或靜態變量。
(4) load和initialize方法內部使用了鎖,因此它們是線程安全的。實現時要盡可能保持簡單,避免阻塞線程,不要再使用鎖。
(5) 父有 子有:load和initialize均先調用父類,然后調用子類;
父有 子無:load只調用父類,initialize會調用兩次父類,其中一次是為子類調用;
父無 子有:load和initialize均只調用子類。
63、沙盒目錄結構是怎樣的?各自用于那些場景?
Application:存放程序源文件,上架前經過數字簽名,上架后不可修改。
Documents:常用目錄,iCloud 備份目錄,存放數據。
Library:(1) Caches:存放體積大又不需要備份的數據; (2) Preference:設置目錄,iCloud 會備份設置信息。
tmp:存放臨時文件,不會被備份,而且這個文件下的數據有可能隨時被清除的可能。
64、iOS開發中方法延遲執行的幾種方式?
(1) NSObject 的 performSelector方法
(2) NSTimer定時器
(3) NSThread線程的sleep
(4) GCD 的 dispatch_after
65、通過結構體 category_t 可以知道,在 Category 中我們可以增加實例方法、類方法、協議、屬性。我們這里簡述下 Category 的實現原理:
(1) 在編譯時期,會將分類中實現的方法生成一個結構體 method_list_t 、將聲明的屬性生成一個結構體 property_list_t ,然后通過這些結構體生成一個結構體 category_t 。
(2) 然后將結構體 category_t 保存下來
(3) 在運行時期,Runtime 會拿到編譯時期我們保存下來的結構體 category_t
(4) 然后將結構體 category_t 中的實例方法列表、協議列表、屬性列表添加到主類中
(5) 將結構體 category_t 中的類方法列表、協議列表添加到主類的 metaClass 中
這里需要注意的是:category_t 中的方法列表是插入到主類的方法列表前面(類似利用鏈表中的 next 指針來進行插入),所以這里 Category 中實現的方法并不會真正的覆蓋掉主類中的方法,只是將 Category 的方法插到方法列表的前面去了。運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應名字的方法,就會停止查找,這里就會出現覆蓋方法的這種假象了。
66、Category 為什么不能添加實例變量?
通過結構體 category_t ,我們就可以知道,在 Category 中我們可以增加實例方法、類方法、協議、屬性。這里沒有 objc_ivar_list 結構體,代表我們不可以在分類中添加實例變量。
因為在運行期,對象的內存布局已經確定,如果添加實例變量就會破壞類的內部布局,這個就是 Category 中不能添加實例變量的根本原因。
67、lldb(gdb)常用的控制臺調試命令?
(1) p 輸出基本類型。是打印命令,需要指定類型。是print的簡寫
p (int)[[[self view] subviews] count]。
(2) po 打印對象,會調用對象description方法。是print-object的簡寫
po [self view]。
(3) expr 可以在調試時動態執行指定表達式,并將結果打印出來。常用于在調試過程中修改變量的值。
(4) bt:打印調用堆棧,是thread backtrace的簡寫,加all可打印所有thread的堆棧。
(5) br l:是breakpoint list的簡寫。
68、你一般是如何調試Bug的?
(1) 在運行過程中,如果出現EXC_BAD_ACCESS 異常,往往提示的信息很少或者沒有提示,啟用NSZombieEnabled后在控制臺能打印出更多的提示信息,便于debug,請注意,僵尸模式下的調試工作只能在模擬器中實現,我們無法在物理設備上完成這一診斷流程。
(2) 異常斷點,一般程序crash時Xcode一般會定位到main函數中,得不到詳細的crash信息,打上異常斷點后就極大可能定位到程序的crash處,利于debug。
(3) 一般來說,在創建工程的時候,應該在Build Settings啟用Analyze During 'Build',這樣每次編譯時都會自動靜態分析。這樣的話,寫完一小段代碼之后,就馬上知道是否存在內存泄露或其他bug問題,并且可以修bug。
(4) 如果你想在運行的時候查看APP是否存在內存泄露,你可以使用Xcode上instruments工具上的Leaks模塊進行內存分析。但是有些內存泄露是很難檢查出來,有時只有通過手動覆蓋dealloc方法,看它最終有沒有調用。
69、如何解決TableView卡的問題:
(1) 復用單元格;
(2) 單元格中的視圖盡量都使用不透明的,單元格中盡量少使用動畫;
(3) 圖片加載使用異步加載;
(4) 滑動時不加載圖片,停止滑動時開始加載;
(5) 單元格中的內容可以在自定義cell類中的drawRect方法內自己繪制;
(6) 如非必要,減少reloadData全部cell,只reloadRowsAtIndexPaths;
(7) 如果cell是動態行高,計算出高度后緩存;
(8) cell高度固定的話直接使用cell.rowHeight設置高度。
70、卡頓產生的原因:
在 VSync(垂直同步) 信號到來后,系統圖形服務會通過 CADisplayLink 等機制通知 App,App 主線程開始在 CPU 中計算顯示內容,比如視圖的創建、布局計算、圖片解碼、文本繪制等。隨后 CPU 會將計算好的內容提交到 GPU 去,由 GPU 進行變換、合成、渲染。隨后 GPU 會把渲染結果提交到幀緩沖區去,等待下一次 VSync 信號到來時顯示到屏幕上。由于垂直同步的機制,如果在一個 VSync 時間內,CPU 或者 GPU 沒有完成內容提交,則那一幀就會被丟棄,等待下一次機會再顯示,而這時顯示屏會保留之前的內容不變。這就是界面卡頓的原因。
71、離屏渲染:
(1) 如何檢測我的項目里面離屏渲染了:
打開的正確方式:模擬器的 debug -> 選取 color Offscreen-Rendered。開啟后會把那些需要離屏渲染的圖層高亮成黃色,這就意味著黃色圖層可能存在性能問題。
(2) 離屏渲染會導致CPU在后臺保存一份bitmap,所以會導致CPU多余運算。而避免的方式則是避免去做觸發的動作:重寫drawRect方法;masksToBounds;其他一些手動觸發離屏渲染的動作。
(3) 圖像渲染工作原理:由CPU計算好顯示內容,GPU 渲染完成后將渲染結果放入幀緩沖區,隨后視頻控制器會按照HSync信號逐行讀取幀緩沖區的數據,經過可能的數模轉換傳遞給顯示器顯示。
72、如何減小離屏渲染帶來的影響?
(1) 慎用離屏渲染,因為絕大多數時候離屏渲染會影響性能;
(2) 重寫drawRect方法,設置圓角、陰影、模糊效果,光柵化都會導致離屏渲染;
(3) 設置陰影效果是加上陰影路徑;
(4) 滑動時若需要圓角效果,開啟光柵化;
以上就是我最近復習到的面試題,希望可以幫到你們,歡迎指正交流~