今天博主有一個iOS核心面試題的需求,遇到了一些困難點,在此和大家分享,希望能夠共同進步.
1.怎么用 copy 關鍵字?
一般使用 retain 或者 strong 修飾屬性時,是使引用對象的指針指向同一個對象,即為同一塊內存地址。只要其中有一個指針變量被修改時所有其他引用該對象的變量都會被改變。
而使用 copy 關鍵字修飾在賦值時是釋放舊對象,拷貝新對象內容。重新分配了內存地址。以后該指針變量被修改時就不會影響舊對象的內容了。
copy 只有實現NSCopying協議的對象類型才有效。
常用於NSString和Block。
2.這個寫法會出什么問題: @@property (copy) NSMutableArray *array;
當一個NSMutableArray對象使用 initWithArray: 初始化方法創建時,並將該對象賦值給了array屬性。那么之后array執行可變數組的方法,比如: removeObjectAtIndex: 時會出現unrecognized selector sent to instance的崩潰。原因在於array屬性在被賦值(setter)的時候默認執行了copy方法后變為了不可變NSArray對象。
3.如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?
該類必須要實現NSCopying協議。實現 - (id)copyWithZone:(NSZone *)zone; 方法。
重寫copy關鍵字的setter時,需要調用一下傳入對象的copy方法。然后賦值給該setter的方法對應的成員變量。
4.@property 的本質是什么?ivar、getter、setter 是如何生成並添加到這個類中的.
property在編譯時編譯器會自動的為我們生成一個私有的成員變量和setter與getter方法的聲明和實現。反編譯property大致生成五個東西
OBJC IVAR $類名$屬性名稱 該屬性的偏移量
setter與getter方法對應的實現函數
ivar_list 就是成員變量列表
method_list 方法列表
prop_list 屬性列表
也就是說我們每次在增加一個屬性,系統都會在ivar_list中添加一個成員變量的描述,在method_list中增加setter與getter方法的描述,在屬性列表中增加一個屬性的描述,然后計算該屬性在對象中的偏移量,然后產生setter與getter方法對應的實現,在setter方法方法中從偏移量的位置開始賦值,在getter方法中從偏移量開始取值,為了能夠讀取正確字節數,系統對象偏移量的指針類型進行了類型強轉。
5.@protocol 和 category 中如何使用 @property
在protocol中使用property只會生成setter和getter方法聲明,我們使用屬性的目的,是希望遵守我協議的對象的實現該屬性
category 使用 @property 也是只會生成setter和getter方法的聲明,如果我們真的需要給category增加屬性的實現,需要借助於運行Objective-C動態運行機制中的兩個函數:
objc_setAssociatedObject
objc_getAssociatedObject
6.@synthesize和@dynamic分別有什么作用?
@property有兩個對應的詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那么默認的就是@syntheszie var = var,var為property變量。可以手動修改屬性var對應的實例變量。例如:@syntheszie var = var1
@synthesize的語義是如果你沒有手動實現setter方法和getter方法,那么編譯器會自動為你加上這兩個方法, 在Xcode4.4之后的版本可以省略不寫.
@dynamic告訴編譯器不要自動生成成員變量的getter和setter方法,而是開發者自己手工生成或者運行時生成.
7.用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什么?如果改用strong關鍵字,可能造成什么問題?
使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用copy無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本.
如果使用 strong .這個屬性有可能指向一個可變對象,如果這個可變對象被外部意外的修改了,由於可變對象被改變之后起始地址不會發生變化。而附有strong修飾的屬性依然指向這這塊內存地址,下次讀取的時候就會是被改變以后的對象了.也就是說如果使用 strong 可能會被外部意外的修改。
8.objc中向一個對象發送消息[obj foo]和objc_msgSend()函數之間有什么關系?
[obj foo]變量編譯之后就是objc_msgSend()函數的調用。該方法編譯后的形式大致如下:objc_msgSend(obj, sel_registerName(“foo”));
9.一個objc對象如何進行內存布局?(考慮有父類的情況)
每一個objc對象都是一個類的實例。在Objective-C語言的內部,每一個對象都有一個名為isa的指針,指向該對象的類(類對象)。每一個類描述了一系列它的實例的特點,包括成員變量的列表,成員函數的列表等。每一個對象都可以接受消息,而對象能夠接收的消息列表是保存在它所對應的類對象中。如下圖:
類對象內部還有一個指向superclass的指針,指向他的父類對象,根類為NSObject類它的superclass指針指向nil,如下圖:
類對象既然稱為對象那它也是一個實例。類對象中也有一個isa指針指向它的元類(meta class),即類對象是元類的實例。元類內部存放的是類方法列表,根元類的isa指針指向自己,superclass指針指向NSObject類
10.一個objc對象的isa的指針指向什么?有什么作用?
objc對象的isa指針指向類對象,用於尋找對象的方法。
11.objc中的類方法和實例方法有什么本質區別和聯系?
類方法
類方法保存在類對象的元類中
類方法只能通過類對象調用
類方法中的self是類對象
類方法中不能訪問成員變量
類方法中不能直接調用對象方法
實例方法
保存在類對象的對象模型中
實例方法只能通過實例對象調用
實例方法中的self是實例對象
實例方法中可以訪問成員變量
實例方法中可以訪問成員變量
實例方法中可以訪問成員變量
12.runtime如何實現weak變量的自動置nil?
/* 編譯器的模擬源代碼 */
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);
通過objc_initWeak函數初始化附有 weak修飾符的變量,在變量作用域結束時通過objc_destoryWeak函數釋放該變量。objc_initWeak函數將附有 weak修飾符的變量初始化為0后,將會賦值的對象作為參數調用objc_storeWeak函數。
obj1 = 0;
obj_storeWeak(&obj1, obj);
然后obj_destroyWeak函數將0作為參數的調用objc_storeWeak函數。
obj_destroyWeak(&obj1, 0);
前面的源代碼與下列源代碼相同。
/ * 編譯器的模擬代碼 */
id obj1;
obj1 = 0;
objc_storeWeak&obj1, obj);
objc_storeWeak(&obj1, 0);
objc_storeWeak函數的第二參數的賦值對象的地址作為鍵值,將第一個參數的附有__weak修飾符的變量的地址注冊到weak表中。如果第二參數為0,則把變量的地址從weak表中刪除。
13._objc_msgForward函數是做什么的,直接調用它將會發生什么?
這個沒接觸過,也沒找到相關資料。但是跟objc動態運行機制中的 forwardingTargetForSelector: 和 forwardInvocation: 方法有點相似。猜測是該方法編譯后的源碼,都用於消息轉發機制
14.以+ scheduledTimerWithTimeInterval…的方式觸發的timer,在滑動頁面上的列表時,timer會暫定回調,為什么?如何解決?
當前主線程的 RunLoop 正在以 UITrackingRunLoopMode 的模式運行。 這個時候 RunLoop 只會處理與 UITrackingRunLoopMode “綁定”的源, 比如觸摸、滾動等事件;而 NSTimer 是默認“綁定”到 NSRunLoopDefaultMode 上的, 所以 Timer 是事情是不會被 RunLoop 處理的,定時器被暫停了!
常見的解決方案是把Timer“綁定”到 NSRunLoopCommonModes 模式上:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
>
這樣這個Timer就可以和當前組中的兩種模式 UITrackingRunLoopMode 和 kCFRunLoopDefaultMode 相關聯了。 RunLoop在這兩種模式下,Timer都可以正常運行了。
15.BAD_ACCESS在什么情況下出現?
對一個已經釋放的對象執行了release
執行了死循環
16.蘋果是如何實現autoreleasepool的?
autoreleasepool以一個隊列數組的形式實現,主要通過下列三個函數完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_aurorelease
看函數名就可以知道,對autorelease分別執行push,和pop操作。銷毀對象時執行release操作。
17.如何用GCD同步若干個異步調用?(如根據若干個url異步加載多張圖片,然后在都下載完成后合成一張整圖)
使用Dispatch Group追加block到Global Group Queue,這些block如果全部執行完畢,就會執行Main Dispatch Queue中的結束處理的block。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加載圖片1 */ });
dispatch_group_async(group, queue, ^{ /*加載圖片2 */ });
dispatch_group_async(group, queue, ^{ /*加載圖片3 */ });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合並圖片
});
18.dispatch_barrier_async的作用是什么?
dispatch_barrier_async函數會等待追加到Concurrent Dispatch Queue並行隊列中的操作全部執行完之后,然后再執行dispatch_barrier_async函數追加的處理,等dispatch_barrier_async追加的處理執行結束之后,Concurrent Dispatch Queue才恢復之前的動作繼續執行。
打個比方:比如你在看電視每個台都在播放精彩的內容,突然插進來一則廣告。於是你就換台,結果發現每個台都在放這個廣告,等這個廣告播放完之后,各個台才開始恢復原來播放的內容。dispatch_barrier_async函數追加的內容就如這條廣告。
19.KVC的keyPath中的集合運算符如何使用?
必須用在集合對象上或普通對象的集合屬性上
簡單集合運算符有@avg, @count , @max , @min ,@sum,
格式 @”@sum.age”或 @”集合屬性.@max.age”
20.如何調試BAD_ACCESS錯誤
重寫object的respondsToSelector方法,現實出現EXEC_BAD_ACCESS前訪問的最后一個object
通過NSZombieEnabled
設置全局斷點快速定位問題代碼所在行
21.__block 和 __weak 之間的區別.
• 前者用於指明當前聲明的變量在被 block 捕獲之后, 可以在 block 中改變變量的值. 因為在 block 聲明的同時會截獲該 block 所使用的全部自動變量的值, 而這些值只在 block 中只具有"使用權"而不具有"修改權". 而 __block 說明符就為 block 提供了變量的修改權.
• 后者是所有權修飾符, 什么是所有權修飾符? 這里涉及到另一個問題, 因為在 ARC 有效時, id 類型和對象類型同 C 語言中的其他類型不同, 必須附加所有權修飾符. 所有權修飾符一種有 4 種:
◦ __strong
◦ __weak
◦ __unsafe_unretained
◦ __autorelease
· __weak 與 weak 的區別只在於, 前者用於變量的聲明, 而后者用於屬性的聲明.
22.什么是method swizzling?
在Objective-C中調用一個方法,其實是向一個對象發送消息,查找消息的唯一依據是selector的名字。利用Objective-C的動態特性,可以實現在運行時偷換selector對應的方法實現,達到給方法掛鈎的目的。
每個類都有一個方法列表,存放着selector的名字和方法實現的映射關系。IMP有點類似函數指針,指向具體的Method實現。
23.為什么其他語言里叫函數調用,Object-C里則叫給我對象發消息(或者談下對runtime的理解)
在java中,類和方法在編譯期就綁定在一起
在OC中,方法調用是向類發送消息,如(bady cry)在運行時會轉換成objc_msgSend(bady,cry),向對象發送消息時根據isa指針找到類,在根據類的調度表查找方法,沒找到方法則在父類中查找直至基類,如果始終沒有找到返回nil
Objective-C 的 Runtime 鑄就了它動態語言的特性,這些深層次的知識雖然平時寫代碼用的少一些,但是卻是每個 Objc 程序員需要了解的。Objc Runtime使得C具有了面向對象能力,在程序運行時創建,檢查,修改類、對象和它們的方法。可以使用runtime的一系列方法實現。
24.TCP和UDP有什么區別?
TCP是面向連接的,建立連接需要經歷三次握手,保證數據正確性和數據順序
UDP是非連接的協議,傳送數據受生成速度,傳輸帶寬等限制,可能造成丟包
UDP一台服務端可以同時向多個客戶端傳輸信息
TCP報頭體積更大,對系統資源要求更多
25.+(void)load; +(void)initialize;有什么用處?
當類對象被引入項目時, runtime 會向每一個類對象發送 load 消息. load 方法還是非常的神奇的, 因為它會在每一個類甚至分類被引入時僅調用一次, 調用的順序是父類優先於子類, 子類優先於分類. 而且 load 方法不會被類自動繼承, 每一個類中的 load 方法都不需要像 viewDidLoad 方法一樣調用父類的方法. 由於 load 方法會在類被 import 時調用一次, 而這時往往是改變類的行為的最佳時機. 我在 DKNightVersion 中使用 method swizlling 來修改原有的方法時, 就是在分類 load 中實現的.
initialize 方法和 load 方法有一些不同, 它雖然也會在整個 runtime 過程中調用一次, 但是它是在該類的第一個方法執行之前調用, 也就是說 initialize 的調用是惰性的, 它的實現也與我們在平時使用的惰性初始化屬性時基本相同. 我在實際的項目中並沒有遇到過必須使用這個方法的情況, 在該方法中主要做靜態變量的設置並用於確保在實例初始化前某些條件必須滿足.
在Objective-C中,runtime會自動調用每個類的兩個方法。+load會在類初始加載時調用,+initialize會在第一次調用類的類方法或實例方法之前被調用。這兩個方法是可選的,且只有在實現了它們時才會被調用。
共同點:兩個方法都只會被調用一次。
26.使用drawRect有什么影響?(這個可深可淺,你至少得用過。。)
drawRect方法依賴Core Graphics框架來進行自定義的繪制,但這種方法主要的缺點就是它處理touch事件的方式:每次按鈕被點擊后,都會用setNeddsDisplay進行強制重繪;而且不止一次,每次單點事件觸發兩次執行。這樣的話從性能的角度來說,對CPU和內存來說都是欠佳的。特別是如果在我們的界面上有多個這樣的UIButton實例。
這個方法的主要作用是根據傳入的 rect 來繪制圖像 參見文檔. 這個方法的默認實現沒有做任何事情, 我們可以在這個方法中使用 Core Graphics 和 UIKit 來繪制視圖的內容.
這個方法的調用機制也是非常特別. 當你調用 setNeedsDisplay 方法時, UIKit 將會把當前圖層標記為 dirty, 但還是會顯示原來的內容, 直到下一次的視圖渲染周期, 才會為標記為 dirty 的圖層重新建立 Core Graphics 上下文, 然后將內存中的數據恢復出來, 再使用 CGContextRef 進行繪制.
27.sip是什么?
1> SIP(Session Initiation Protocol),會話發起協議
2> SIP是建立VOIP連接的 IETF 標准,IETF是全球互聯網最具權威的技術標准化組織
3> 所謂VOIP,就是網絡電話,直接用互聯網打電話,不用耗手機話費
28.AFN 與 ASI 有什么區別
1> AFN基於NSURL,ASI基於底層的CFNetwork框架,因此ASI的性能優於AFN
2> AFN采取block的方式處理請求,ASI最初采取delegate的方式處理請求,后面也增加了block的方式
3> AFN只封裝了一些常用功能,滿足基本需求,直接忽略了很多擴展功能,比如沒有封裝同步請求;ASI提供的功能較多,預留了各種接口和工具供開發者自行擴展
4> AFN直接解析服務器返回的JSON、XML等數據,而ASI比較原始,返回的是NSData二進制數據
29.在異步線程中下載很多圖片,如果失敗了,該如何處理?請結合RunLoop來談談解決方案.(提示:在異步線程中啟動一個RunLoop重新發送網絡請求,下載圖片)
1> 重新下載圖片
2> 下載完畢, 利用RunLoop的輸入源回到主線程刷新UIImageVIUew
30.如果后期需要增加數據庫中的字段怎么實現,如果不使用CoreData呢?
編寫SQL語句來操作原來表中的字段
1> 增加表字段
ALTER TABLE 表名 ADD COLUMN 字段名 字段類型;
2> 刪除表字段
ALTER TABLE 表名 DROP COLUMN 字段名;
3> 修改表字段
ALTER TABLE 表名 RENAME COLUMN 舊字段名 TO 新字段名;
31.簡單描述下客戶端的緩存機制?
1. 緩存可以分為:內存數據緩存、數據庫緩存、文件緩存
2. 每次想獲取數據的時候
1> 先檢測內存中有無緩存
2> 再檢測本地有無緩存(數據庫\文件)
3> 最終發送網絡請求
4> 將服務器返回的網絡數據進行緩存(內存、數據庫、文件), 以便下次讀取
32.利用Socket建立網絡連接的步驟
建立Socket連接至少需要一對套接字,其中一個運行於客戶端,稱為ClientSocket ,另一個運行於服務器端,稱為ServerSocket 。
套接字之間的連接過程分為三個步驟:服務器監聽,客戶端請求,連接確認。
1。服務器監聽:服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態,等待客戶端的連接請求。
2。客戶端請求:指客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然后就向服務器端套接字提出連接請求。
3。連接確認:當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求時,就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述發給客戶端,一旦客戶端確認了此描述,雙方就正式建立連接。而服務器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求