iOS面試準備
基礎(chǔ)
1. 為什么說Objective-C是一門動態(tài)的語言?
編譯期:即編譯器對語言的編譯階段,編譯時只是對語言進行最基本的檢查報錯,包括詞法分析、語法分析等等闹击,將程序代碼翻譯成計算機能夠識別的語言(例如匯編等),編譯通過并不意味著程序就可以成功運行成艘。
運行時: 即程序通過了編譯這一關(guān)之后編譯好的代碼被裝載到內(nèi)存中跑起來的階段赏半,這個時候會具體對類型進行檢查贺归,而不僅僅是對代碼的簡單掃描分析,此時若出錯程序會崩潰断箫。
OC語言的動態(tài)性主要體現(xiàn)在三個方面:動態(tài)類型(Dynamic typing)拂酣、動態(tài)綁定(Dynamic binding)和動態(tài)加載(Dynamic loading)。
動態(tài)性:即OC的動態(tài)類型仲义、動態(tài)綁定和動態(tài)加載特性婶熬,將對象類型的確定、方法調(diào)用的確定埃撵、代碼和資源的裝載等推遲到運行時進行赵颅,更加靈活;
多態(tài):多態(tài)是面向?qū)ο笞兂烧Z言的特性暂刘,OC作為一門面向?qū)ο蟮恼Z言饺谬,自然具備這種多態(tài)性,多態(tài)性指的是來自不同類的對象可以接受同一消息的能力谣拣,或者說不同對象以自己的方式響應(yīng)相同的消息的能力募寨。
2. 講一下MVC和MVVM,MVP森缠?
這三種都是代碼設(shè)計模式拔鹰。
MVC把代碼分為三層,Model層辅鲸,View層格郁,Controller層。
Model層負責(zé)封裝數(shù)據(jù)独悴、存儲和處理數(shù)據(jù)運算等工作
view層 負責(zé)數(shù)據(jù)展示、監(jiān)聽用戶觸摸等工作
Controller層負責(zé)業(yè)務(wù)邏輯锣尉、事件響應(yīng)刻炒、數(shù)據(jù)加工等工作
3. 為什么代理要用weak?代理的delegate和dataSource有什么區(qū)別自沧?block和代理的區(qū)別?
代理就是一個對象坟奥,在使用代理的時候是:obj.delegate = self;
當(dāng)self持有了obj對象的時候回造成引用循環(huán)。代理用weak可以打破引用循環(huán)拇厢。
delegate和dataSource的區(qū)別在于爱谁,delegate通常在處理交互的事件。dataSource是要獲取數(shù)據(jù)來展示孝偎。
4. 屬性的實質(zhì)是什么访敌?包括哪幾個部分?屬性默認的關(guān)鍵字都有哪些衣盾?@dynamic關(guān)鍵字和@synthesize關(guān)鍵字是用來做什么的寺旺?
屬性的實質(zhì)是實例對象中數(shù)據(jù)存儲的地址爷抓。
包含三部分:(對象的實例變量,包括類型和名字) 獲取屬性方法 設(shè)置屬性方法
@property = ivar + getter + setter;
@property (nonatomic, copy) NSString *name;
//T@"NSString",C,N,V_name
//T 類型
//C copy
//N nonatomic
//V 實例變量
@synthesize的語義是如果你沒有手動實現(xiàn)setter方法和getter方法阻塑,那么編譯器會自動為你加上這兩個方法蓝撇。
@dynamic告訴編譯器,屬性的setter與getter方法由用戶自己實現(xiàn),不自動生成陈莽。(當(dāng)然對于readonly的屬性只需提供getter即可)渤昌。假如一個屬性被聲明為@dynamic var,然后你沒有提供@setter方法和@getter方法走搁,編譯的時候沒問題独柑,但是當(dāng)程序運行到instance.var =someVar,由于缺setter方法會導(dǎo)致程序崩潰朱盐;
5. 屬性的默認關(guān)鍵字是什么群嗤?
對應(yīng)基本數(shù)據(jù)類型默認關(guān)鍵字是
atomic,readwrite,assign
對于普通的OC對象
atomic,readwrite,strong
6. NSString為什么要用copy關(guān)鍵字,如果用strong會有什么問題兵琳?(注意:這里沒有說用strong就一定不行狂秘。使用copy和strong是看情況而定的)
NSString *str1 = @"abc"; //如果strongStr是Strong修飾的話 self.strongStr = str1; self.copyStr = str1; str1 = @"zxc"; //self.strongStr -> @"zxc",strongStr的指針是指向str1的內(nèi)存地址躯肌,修改str1的時候者春,strongStr也會跟著改變,可能造成意外的結(jié)果清女。 //而copy修飾的屬性會自動生成新的地址钱烟,self.copyStr->@"abc",copyStr把str的值復(fù)制到新的地址上。
7. 如何令自己所寫的對象具有拷貝功能?
遵守NSCopying協(xié)議
實現(xiàn)- (id)copyWithZone:(nullable NSZone *)zone;
協(xié)議方法
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
Person *p = [[[self class] alloc] init]; // <== 注意這里
p.userId = self.userId;
return p;
}
@end
@implementation Student
- (id)copyWithZone:(NSZone *)zone
{
Student *s = [super copyWithZone:zone];
s.studentId = self.studentId;
return s;
}
@end
8. 可變集合類 和 不可變集合類的 copy 和 mutablecopy有什么區(qū)別嫡丙?如果是集合是內(nèi)容復(fù)制的話拴袭,集合里面的元素也是內(nèi)容復(fù)制么?
使用copy未必是深拷貝曙博,但使用mutablecopy一定是深拷貝拥刻。
淺拷貝:指針(地址)拷貝,不會產(chǎn)生新對象父泳;深拷貝:內(nèi)容拷貝般哼,會產(chǎn)生新對象
當(dāng)不可變字符串(或不可變數(shù)組,不可變集合惠窄,不可變字典)調(diào)用copy的時候蒸眠,指針地址沒有發(fā)生改變,也就意味著沒有產(chǎn)生新的對象杆融,所以屬于淺拷貝楞卡;
當(dāng)可變字符串(或可變數(shù)組,可變集合,可變字典)調(diào)用copy的時候臀晃,指針地址發(fā)生了改變觉渴,也就意味著產(chǎn)生新的對象,所以屬于深拷貝徽惋;
當(dāng)可變字符串(或可變數(shù)組案淋,可變集合,可變字典調(diào)用mutableCopy的時候险绘,指針地址發(fā)生了改變踢京,意味著產(chǎn)生新的對象,所以屬于深拷貝宦棺。
當(dāng)不可變字符串(或不可變數(shù)組瓣距,不可變集合,不可變字典)調(diào)用mutableCopy的時候代咸,指針地址發(fā)生了改變蹈丸,意味著產(chǎn)生新的對象,所以屬于深拷貝呐芥。
集合對象的內(nèi)容復(fù)制僅限于對象本身逻杖,對象元素仍然是指針復(fù)制。
// 屬性為copy的時候思瘟,等價于下面的代碼
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name copy];
}
}
9. 為什么IBOutlet修飾的UIView也適用weak關(guān)鍵字荸百?
使用storyboard(xib不行)創(chuàng)建的vc,會有一個叫_topLevelObjectsToKeepAliveFromStoryboard的私有數(shù)組強引用所有top level的對象滨攻,所以這時即便outlet聲明成weak也沒關(guān)系
10. nonatomic和atomic的區(qū)別够话?atomic是絕對的線程安全么?為什么光绕?如果不是女嘲,那應(yīng)該如何實現(xiàn)?
atomic
:默認是有該屬性的诞帐,這個屬性是為了保證程序在多線程情況下澡为,編譯器會自動生成一些互斥加鎖代碼,避免該變量的讀寫不同步問題景埃。
nonatomic
:如果該對象無需考慮多線程的情況,請加入這個屬性顶别,這樣會讓編譯器少生成一些互斥加鎖代碼谷徙,可以提高效率。
在iOS中使用同步鎖的開銷比較大驯绎, 這會帶來性能問題完慧。
如果不加鎖的話(或者說使用nonatomic語義),那么當(dāng)其中一個線程正在改寫某屬性值的時候剩失,另外一個線程也許會突然闖入屈尼,把尚未修改好的屬性值讀取出來册着。發(fā)證這種情況時,線程讀取到屬性值可能不對脾歧。
一般iOS程序中甲捏,所有屬性都聲明為nonatomic。這樣做的原因是:
在iOS中使用同步鎖的開銷比較大鞭执, 這會帶來性能問題司顿。一般情況下并不要求屬性必須是“原子的”,因為這并不能保證“線程安全”(thread safety)兄纺,若要實現(xiàn)“線程安全”的操作大溜,還需采用更為深層的鎖定機制才醒。
nonatomic的內(nèi)存管理語義是非原子性的估脆,非原子性的操作本來就是線程不安全钦奋,而atomic的操作是原子性的,但并不意味著他就是線程安全的疙赠,它會增加正確的幾率付材,能夠更好的避免線程錯誤,但仍舊是不安全的棺聊。
幾種線程鎖:
OSSpinLock: 46.15 ms
dispatch_semaphore: 56.50 ms
pthread_mutex: 178.28 ms
NSCondition: 193.38 ms
NSLock: 175.02 ms
pthread_mutex(recursive): 172.56 ms
NSRecursiveLock: 157.44 ms
NSConditionLock: 490.04 ms
@synchronized: 371.17 ms
總的來說:
OSSpinLock和dispatch_semaphore的效率遠遠高于其他伞租。
@synchronized和NSConditionLock效率較差。
鑒于OSSpinLock的不安全限佩,所以我們在開發(fā)中如果考慮性能的話葵诈,建議使用dispatch_semaphore。
如果不考慮性能祟同,只是圖個方便的話作喘,那就使用@synchronized。
11. UICollectionView自定義layout如何實現(xiàn)晕城?
12. 用StoryBoard開發(fā)界面有什么弊端泞坦?如何避免?
目中用到StoryBoard的地方較多談下想法,首先團隊開發(fā)中用StoryBoard,如果模塊劃分的不是那么獨立,會有多人共同寫一個模塊的情況,那最后不要用StoryBoard,應(yīng)為SVN經(jīng)常會沖突導(dǎo)致更新下的代碼會有問題,可能需要恢復(fù)到上導(dǎo)致沖突前一版本才能解決砖顷。
13. 進程和線程的區(qū)別贰锁?同步異步的區(qū)別?
進程本身不會運行滤蝠,是線程的容器豌熄。程序本身只是指令的集合,進程才是程序(那些指令)的真正運行物咳。若干進程有可能與同一個程序相關(guān)系锣险,且每個進程皆可以同步(循序)或不同步(平行)的方式獨立運行。進程為現(xiàn)今分時系統(tǒng)的基本運作單位
進程:每個進程之間是獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)
線程:操作系統(tǒng)能夠進行運算調(diào)度的最小單位芯肤。它被包涵在進程之中巷折,一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程崖咨,每條線程并行執(zhí)行不同的任務(wù)锻拘。
1個進程要想執(zhí)行任務(wù),必須得有線程(每1個進程至少要有1條線程)
14. 線程間通信掩幢?
線程間通信的體現(xiàn):
(1 ).一個線程傳遞數(shù)據(jù)給另一個線程
(2).在一個線程中執(zhí)行完特定任務(wù)后逊拍,轉(zhuǎn)到另一個線程繼續(xù)執(zhí)行任務(wù)
在子線程計算然后 在主線程刷新也是一種線程傳遞數(shù)據(jù)
15. GCD的一些常用的函數(shù)?(group际邻,barrier芯丧,信號量,線程同步)
16. 如何使用隊列來避免資源搶奪世曾?
當(dāng)我們使用多線程來訪問同一個數(shù)據(jù)的時候缨恒,就有可能造成數(shù)據(jù)的不準確性。這個時候我么可以使用線程鎖的來來綁定轮听。也是可以使用串行隊列來完成骗露。如:fmdb就是使用FMDatabaseQueue,來解決多線程搶奪資源血巍。
17. 數(shù)據(jù)持久化的幾個方案
plist文件
NSUserDefaults
NSKeyedArchiver
SQLite 3 (FMDB封裝了SQLite3)
CoreData
18. 說一下AppDelegate的幾個方法萧锉?從后臺到前臺調(diào)用了哪些方法?第一次啟動調(diào)用了哪些方法述寡?從前臺到后臺調(diào)用了哪些方法柿隙?
– (void)applicationDidFinishLaunching:(UIApplication *)application; 此方法基本已經(jīng)棄用,改用第2個方法代替鲫凶。
– (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions: 當(dāng)應(yīng)用程序啟動時(不包括已在后臺的情況下轉(zhuǎn)到前臺)禀崖,調(diào)用此回調(diào)。launchOptions是啟動參數(shù)螟炫,假如用戶通過點擊push通知啟動的應(yīng)用波附,這個參數(shù)里會存儲一些push通知的信息。
– (void)applicationDidBecomeActive:(UIApplication *)application; 當(dāng)應(yīng)用程序全新啟動昼钻,或者在后臺轉(zhuǎn)到前臺掸屡,完全激活時,都會調(diào)用這個方法然评。如果應(yīng)用程序是以前運行在后臺折晦,這時可以選擇刷新用戶界面。
– (void)applicationWillResignActive:(UIApplication *)application; 當(dāng)應(yīng)用從活動狀態(tài)主動到非活動狀態(tài)的應(yīng)用程序時會調(diào)用這個方法沾瓦。這可導(dǎo)致產(chǎn)生某些類型的臨時中斷(如傳入電話呼叫或SMS消息)。或者當(dāng)用戶退出應(yīng)用程 序贯莺,它開始過渡到的背景狀態(tài)风喇。使用此方法可以暫停正在進行的任務(wù),禁用定時器缕探,降低OpenGL ES的幀速率魂莫。游戲應(yīng)該使用這種方法來暫停游戲。 調(diào)用時機可能有以下幾種:鎖屏爹耗,按HOME鍵耙考,下接狀態(tài)欄,雙擊HOME鍵彈出低欄潭兽,等情況倦始。
– (BOOL)application:(UIApplication )application handleOpenURL:(NSURL )url; 這個方法已不再支持,可能會在以后某個版本中去掉山卦。建議用下面方法代替
– (BOOL)application:(UIApplication )application openURL:(NSURL )url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation NS_AVAILABLE_IOS(4_2); 當(dāng)用戶通過其它應(yīng)用啟動本應(yīng)用時鞋邑,會回調(diào)這個方法,url參數(shù)是其它應(yīng)用調(diào)用openURL:方法時傳過來的账蓉。
– (void)applicationDidReceiveMemoryWarning:(UIApplication *)application; 當(dāng)應(yīng)用可用內(nèi)存不足時枚碗,會調(diào)用此方法,在這個方法中铸本,應(yīng)該盡量去清理可能釋放的內(nèi)存肮雨。如果實在不行,可能會被強行退出應(yīng)用箱玷。
– (void)applicationWillTerminate:(UIApplication *)application; 當(dāng)應(yīng)用退出怨规,并且進程即將結(jié)束時會調(diào)到這個方法,一般很少主動調(diào)到汪茧,更多是內(nèi)存不足時是被迫調(diào)到的椅亚,我們應(yīng)該在這個方法里做一些數(shù)據(jù)存儲操作。
-(void)application:(UIApplication )application didRegisterForRemoteNotificationsWithDeviceToken: -(void)application:(UIApplication )application didFailToRegisterForRemoteNotificationsWithError:(NSError )error NS_AVAILABLE_IOS(3_0); 當(dāng)客戶端注冊遠程通知時舱污,會回調(diào)上面兩個方法呀舔。 如果成功,則回調(diào)第一個扩灯,客戶端把deviceToken取出來發(fā)給服務(wù)端媚赖,push消息的時候要用。 如果失敗了珠插,則回調(diào)第二個惧磺,可以從error參數(shù)中看一下失敗原因。 注:注冊遠程通知使用如下方法: UIRemoteNotificationType t = UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeSound; [[UIApplication sharedApplication] registerForRemoteNotificationTypes:t];
– (void)application:(UIApplication )application didReceiveRemoteNotification: 當(dāng)應(yīng)用在前臺運行中捻撑,收到遠程通知時磨隘,會回調(diào)這個方法缤底。 當(dāng)應(yīng)用在后臺狀態(tài)時,點擊push消息啟動應(yīng)用番捂,也會回調(diào)這個方法个唧。
– (void)application:(UIApplication )application didReceiveLocalNotification:(UILocalNotification )notification NS_AVAILABLE_IOS(4_0); 當(dāng)應(yīng)用收到本地通知時會調(diào)這個方法,同上面一個方法類似设预。 如果在前臺運行狀態(tài)直接調(diào)用徙歼,如果在后臺狀態(tài),點擊通知啟動時鳖枕,也會回調(diào)這個方法
– (void)applicationDidEnterBackground:(UIApplication *)application NS_AVAILABLE_IOS(4_0); 當(dāng)用戶從臺前狀態(tài)轉(zhuǎn)入后臺時魄梯,調(diào)用此方法。使用此方法來釋放資源共享宾符,保存用戶數(shù)據(jù)酿秸,無效計時器,并儲存足夠的應(yīng)用程序狀態(tài)信息的情況下被終止后吸奴,將應(yīng)用 程序恢復(fù)到目前的狀態(tài)允扇。如果您的應(yīng)用程序支持后臺運行,這種方法被調(diào)用则奥,否則調(diào)用applicationWillTerminate:用戶退出考润。
– (void)applicationWillEnterForeground; 當(dāng)應(yīng)用在后臺狀態(tài),將要進行動前臺運行狀態(tài)時读处,會調(diào)用此方法糊治。如果應(yīng)用不在后臺狀態(tài),而是直接啟動罚舱,則不會回調(diào)此方法井辜。
19. NSCache優(yōu)于NSDictionary的幾點?
NSDictionary *dic = @{
@(1) : @(1),
@(2) : @(2)
};
NSLog(@"%@",dic.allKeys);
注意一點它和NSDictionary區(qū)別就是管闷,NSCache 中的key不必實現(xiàn)copy粥脚,NSDictionary中的key必須實現(xiàn)copy
NSCache優(yōu)于NSDictionary的幾點:
- 當(dāng)系統(tǒng)資源將要耗盡時,NSCache具備自動刪減緩沖的功能包个。并且還會先刪減“最久未使用”的對象刷允。
- NSCache不拷貝鍵,而是保留鍵碧囊。因為并不是所有的鍵都遵從拷貝協(xié)議(字典的鍵是必須要支持拷貝協(xié)議的树灶,有局限性)。
- NSCache是線程安全的:不編寫加鎖代碼的前提下糯而,多個線程可以同時訪問NSCache天通。
20. 知不知道Designated Initializer?使用它的時候有什么需要注意的問題熄驼?
Designated Initializer 指定初始化器
- 每個類的正確初始化過程應(yīng)當(dāng)是按照從子類到父類的順序像寒,依次調(diào)用每個類的Designated Initializer烘豹。并且用父類的Designated Initializer初始化一個子類對象,也需要遵從這個過程萝映。
- 如果子類指定了新的初始化器吴叶,那么在這個初始化器內(nèi)部必須調(diào)用父類的Designated Initializer。并且需要重寫父類的Designated Initializer序臂,將其指向子類新的初始化器
- (instancetype)initWithFrame:(CGRect)frame andName:(NSString *)name{
//incorrect
if (self = [super init]){
self.name=name;
}
return self;
}
21. 實現(xiàn)description方法能取到什么效果?
重寫該方法实束。
- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level
在使用NSObject類替換%@占位符時奥秆,會調(diào)用description相關(guān)方法,所以只要實現(xiàn)此方法咸灿,就可以起到修改打印內(nèi)容的作用构订。因此對于系統(tǒng)的類,可用增加分類的方式實現(xiàn)避矢,而自己的類悼瘾,就是增加方法。
22. objc使用什么機制管理對象內(nèi)存审胸?
1).MRC(manual retain-release)手動內(nèi)存管理
2).ARC(automatic reference counting)自動引用計數(shù)
Block
block的實質(zhì)是什么亥宿?一共有幾種block?都是什么情況下生成的砂沛?
block也是OC的對象
_NSConcreteStackBlock 保存在棧中的block仁烹,出棧時會被銷毀
_NSConcreteGlobalBlock 全局的靜態(tài)block酵使,不會訪問任何外部變量
_NSConcreteMallocBlock 保存在堆中的block,當(dāng)引用計數(shù)為0時會被銷毀
為什么在默認情況下無法修改被block捕獲的變量? __block都做了什么扶歪?
配置在全局的GlobalBlock可以出了作用域還是能繼續(xù)訪問,但是在棧上的StackBlock就廢棄了勿她,因此為了出了作用域能繼續(xù)使用适荣,OC提供了把Block和__block這兩個東西從棧上復(fù)制到堆上的方法來解決這個問題。而_forwarding其實既可以指向自己苹享,也可以指向復(fù)制后的自己双絮,也就是說有了這個指針的存在,無論__block變量配置在堆上還是棧上都能夠正確的訪問__block變量富稻。
模擬一下循環(huán)引用的一個情況掷邦?block實現(xiàn)界面反向傳值如何實現(xiàn)?
self.blockClass = [[LMBlockClass alloc] init];
[self.blockClass testBlock1:^{
self.string = @"ssss";
}];
但如果你使用一些參數(shù)中可能含有 ivar 的系統(tǒng) api 椭赋,如 GCD 抚岗、NSNotificationCenter就要小心一點:比如GCD 內(nèi)部如果引用了 self,而且 GCD 的其他參數(shù)是 ivar哪怔,則要考慮到循環(huán)引用:
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakSelf;
[strongSelf dismissModalViewControllerAnimated:YES];
}];
Runtime
objc在向一個對象發(fā)送消息時宣蔚,發(fā)生了什么向抢?
1.消息由接收者,選擇子及參數(shù)構(gòu)成胚委。給某對象“發(fā)送消息”(invoke a method)也就相當(dāng)于在該對象上調(diào)用方法挟鸠。
//1.self是接收者 2.messageName:是選擇子 3.選擇子和參數(shù)合起來稱為“消息”
id returnValue = [self messageName:@"messageValue"];
2.發(fā)給某對象的全部消息都要由“動態(tài)消息派發(fā)系統(tǒng)”來處理,該系統(tǒng)會查出對應(yīng)的方法亩冬,并執(zhí)行其代碼艘希。
編譯器看到消息后,會將上面轉(zhuǎn)換為一條標準的C語言函數(shù)調(diào)用硅急。
第一個參數(shù)是接收者 第二個參數(shù)是選擇子 后面的參數(shù)就是消息中的參數(shù)
void objc_msgSend(id self,SEL cmd,....);
id objc_returnValue = objc_msgSend(self,@selector(messageName:),@"messageValue");
執(zhí)行過程 objc_msgSend 會根據(jù)接收者和選擇子的類型來調(diào)用適當(dāng)?shù)姆椒?br> * 1.在接收者所在的類中搜其“方法列表”覆享,找到相符的方法,就跳至其實現(xiàn)代碼营袜。
* 2.找不到就沿著繼承體系繼續(xù)往上找撒顿,等找到合適的方法后再跳轉(zhuǎn);
* 3.如果最終還是找不到相符的荚板,就執(zhí)行“消息轉(zhuǎn)發(fā)”操作
什么時候會報unrecognized selector錯誤凤壁?iOS有哪些機制來避免走到這一步?
當(dāng)對象無法響應(yīng)該方法和消息轉(zhuǎn)發(fā)沒有處理該方法時就報該錯誤跪另。
能否向編譯后得到的類中增加實例變量拧抖?能否向運行時創(chuàng)建的類中添加實例變量?為什么罚斗?
不能向編譯后得到的類中增加實例變量徙鱼;
能向運行時創(chuàng)建的類中添加實例變量;
因為編譯后的類已經(jīng)注冊在 runtime 中针姿,類結(jié)構(gòu)體中的 objc_ivar_list 實例變量的鏈表 和 instance_size 實例變量的內(nèi)存大小已經(jīng)確定袱吆,同時runtime 會調(diào)用 class_setIvarLayout 或 class_setWeakIvarLayout 來處理 strong weak 引用。所以不能向存在的類中添加實例變量距淫;
運行時創(chuàng)建的類是可以添加實例變量绞绒,調(diào)用 class_addIvar 函數(shù)。但是得在調(diào)用 objc_allocateClassPair 之后榕暇,objc_registerClassPair 之前蓬衡,原因同上。
runtime如何實現(xiàn)weak變量的自動置nil彤枢?
runtime 對注冊的類狰晚, 會進行布局,對于 weak 對象會放入一個 hash 表中缴啡。 用 weak 指向的對象內(nèi)存地址作為 key壁晒,當(dāng)此對象的引用計數(shù)為0的時候會 dealloc,假如 weak 指向的對象內(nèi)存地址是a业栅,那么就會以a為鍵秒咐, 在這個 weak 表中搜索谬晕,找到所有以a為鍵的 weak 對象,從而設(shè)置為 nil携取。
我們可以設(shè)計一個函數(shù)(偽代碼)來表示上述機制:
objc_storeWeak(&a, b)函數(shù):
objc_storeWeak函數(shù)把第二個參數(shù)--賦值對象(b)的內(nèi)存地址作為鍵值key攒钳,將第一個參數(shù)--weak修飾的屬性變量(a)的內(nèi)存地址(&a)作為value,注冊到 weak 表中雷滋。如果第二個參數(shù)(b)為0(nil)不撑,那么把變量(a)的內(nèi)存地址(&a)從weak表中刪除,
你可以把objc_storeWeak(&a, b)理解為:objc_storeWeak(value, key)晤斩,并且當(dāng)key變nil燎孟,將value置nil。
在b非nil時尸昧,a和b指向同一個內(nèi)存地址,在b變nil時旷偿,a變nil烹俗。此時向a發(fā)送消息不會崩潰:在Objective-C中向nil發(fā)送消息是安全的。
而如果a是由assign修飾的萍程,則: 在b非nil時幢妄,a和b指向同一個內(nèi)存地址,在b變nil時茫负,a還是指向該內(nèi)存地址蕉鸳,變野指針。此時向a發(fā)送消息極易崩潰忍法。
RunLoop
runloop是來做什么的潮尝?runloop和線程有什么關(guān)系?主線程默認開啟了runloop么饿序?子線程呢勉失?
RunLoop 的概念
一般來講,一個線程一次只能執(zhí)行一個任務(wù)原探,執(zhí)行完成后線程就會退出乱凿。如果我們需要一個機制,讓線程能隨時處理事件但并不退出咽弦,通常的代碼邏輯是這樣的:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
RunLoop 實際上就是一個對象徒蟆,iOS系統(tǒng)提供了兩個這樣的對象:NSRunLoop 和 CFRunLoopRef。這個對象管理了其需要處理的事件和消息型型,并提供了一個入口函數(shù)來執(zhí)行上面 Event Loop 的邏輯段审。線程執(zhí)行了這個函數(shù)后,就會一直處于這個函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中输莺,直到這個循環(huán)結(jié)束(比如傳入 quit 的消息)戚哎,函數(shù)返回裸诽。
RunLoop這個對象在循環(huán)中用來處理程序運行過程中出現(xiàn)的各種事件(比如說觸摸事件、UI刷新事件型凳、定時器事件丈冬、Selector事件),從而保持程序的持續(xù)運行甘畅;而且在沒有事件處理的時候埂蕊,會進入睡眠模式,從而節(jié)省CPU資源疏唾,提高程序性能蓄氧。
CFRunLoopRef 是在 CoreFoundation 框架內(nèi)的,它提供了純 C 函數(shù)的 API槐脏,所有這些 API 都是線程安全的喉童。
NSRunLoop 是基于 CFRunLoopRef 的封裝,提供了面向?qū)ο蟮?API顿天,但是這些 API 不是線程安全的堂氯。
runloop和線程有什么關(guān)系
- 一條線程對應(yīng)一個RunLoop對象,每條線程都有唯一一個與之對應(yīng)的RunLoop對象牌废。
- 我們只能在當(dāng)前線程中操作當(dāng)前線程的RunLoop咽白,而不能去操作其他線程的RunLoop。
- RunLoop對象在第一次獲取RunLoop時創(chuàng)建鸟缕,銷毀則是在線程結(jié)束的時候晶框。
- 主線程的RunLoop對象系統(tǒng)自動幫助我們創(chuàng)建好了(原理如下),而子線程的RunLoop對象需要我們主動創(chuàng)建懂从。
主線程默認開啟了runloop授段,子線程的runloop默認關(guān)閉,第一次獲取的時候才創(chuàng)建莫绣,有時候用不到浪費資源畴蒲。runloop有資源才不會銷毀,沒資源會自定銷毀对室。
runloop的mode是用來做什么的模燥?有幾種mode?
一個 RunLoop 包含若干個 Mode掩宜,每個 Mode 又包含若干個 Source/Timer/Observer蔫骂。每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode牺汤,這個Mode被稱作 CurrentMode辽旋。如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進入补胚。這樣做主要是為了分隔開不同組的 Source/Timer/Observer码耐,讓其互不影響。
CFRunLoopModeRef
系統(tǒng)默認定義了多種運行模式(CFRunLoopModeRef)溶其,如下:
- kCFRunLoopDefaultMode:App的默認運行模式骚腥,通常主線程是在這個運行模式下運行
- UITrackingRunLoopMode:跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他Mode影響)
- UIInitializationRunLoopMode:在剛啟動App時第進入的第一個 Mode瓶逃,啟動完成后就不再使用
- GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件束铭,通常用不到
- kCFRunLoopCommonModes:偽模式,不是一種真正的運行模式(后邊會用到)
其中kCFRunLoopDefaultMode厢绝、UITrackingRunLoopMode契沫、kCFRunLoopCommonModes是我們開發(fā)中需要用到的模式
而當(dāng)我們拖動ScllorView的時候,RunLoop就結(jié)束NSDefaultRunLoopMode昔汉,切換到了UITrackingRunLoopMode模式下懈万,這個模式下沒有添加NSTimer,所以我們的NSTimer就不工作了靶病。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
钞速,也就是將定時器添加到當(dāng)前RunLoop的UITrackingRunLoopMode下,你就會發(fā)現(xiàn)定時器只會在拖動Text View的模式下工作嫡秕,而不做操作的時候定時器就不工作
我們之前說過的偽模式(kCFRunLoopCommonModes),這其實不是一種真實的模式苹威,而是一種標記模式昆咽,意思就是可以在打上Common Modes標記的模式下運行。
CFRunLoopTimerRef是定時源(RunLoop模型圖中提到過)牙甫,理解為基于時間的觸發(fā)器掷酗,基本上就是NSTimer
CFRunLoopSourceRef是事件源(RunLoop模型圖中提到過),CFRunLoopSourceRef有兩種分類方法窟哺。
同時我們可以看到11行中有Sources0泻轰,也就是說我們點擊事件是屬于Sources0函數(shù)的,點擊事件就是在Sources0中處理的且轨。
而至于Sources1浮声,則是用來接收、分發(fā)系統(tǒng)事件旋奢,然后再分發(fā)到Sources0中處理的泳挥。
CFRunLoopObserverRef是觀察者,用來監(jiān)聽RunLoop的狀態(tài)改變
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進入Loop:1
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer:2
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source:4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠:32
kCFRunLoopAfterWaiting = (1UL << 6), // 即將從休眠中喚醒:64
kCFRunLoopExit = (1UL << 7), // 即將從Loop中退出:128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 監(jiān)聽全部狀態(tài)改變
};
蘋果是如何實現(xiàn)Autorelease Pool的至朗?
autoreleasepool 以一個隊列數(shù)組的形式實現(xiàn),主要通過下列三個函數(shù)完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_autorelease
看函數(shù)名就可以知道屉符,對 autorelease 分別執(zhí)行 push,和 pop 操作。銷毀對象時執(zhí)行release操作矗钟。
舉例說明:我們都知道用類方法創(chuàng)建的對象都是 Autorelease 的唆香,那么一旦 Person 出了作用域,當(dāng)在 Person 的 dealloc 方法中打上斷點吨艇,我們就可以看到這樣的調(diào)用堆棧信息:
類結(jié)構(gòu)
isa指針躬它?(對象的isa,類對象的isa秸应,元類的isa都要說)
類方法和實例方法有什么區(qū)別虑凛?
類方法:
類方法是屬于類對象的
類方法只能通過類對象調(diào)用
類方法中的self是類對象
類方法可以調(diào)用其他的類方法
類方法中不能訪問成員變量
類方法中不能直接調(diào)用對象方法
實例方法:
實例方法是屬于實例對象的
實例方法只能通過實例對象調(diào)用
實例方法中的self是實例對象
實例方法中可以訪問成員變量
實例方法中直接調(diào)用實例方法
實例方法中也可以調(diào)用類方法(通過類名)
介紹一下分類,能用分類做什么软啼?內(nèi)部是如何實現(xiàn)的桑谍?它為什么會覆蓋掉原來的方法?
運行時能增加成員變量么祸挪?能增加屬性么锣披?如果能,如何增加贿条?如果不能雹仿,為什么?
objc中向一個nil對象發(fā)送消息將會發(fā)生什么整以?(返回值是對象胧辽,是標量,結(jié)構(gòu)體)
如果 spouse 對象為 nil公黑,那么發(fā)送給 nil 的消息 mother 也將返回 nil邑商。 2. 如果方法返回值為指針類型,其指針大小為小于或者等于sizeof(void*)凡蚜,float人断,double,long double 或者 long long 的整型標量朝蜘,發(fā)送給 nil 的消息將返回0恶迈。 2. 如果方法返回值為結(jié)構(gòu)體,發(fā)送給 nil 的消息將返回0。結(jié)構(gòu)體中各個字段的值將都是0谱醇。 2. 如果方法的返回值不是上述提到的幾種情況暇仲,那么發(fā)送給 nil 的消息的返回值將是未定義的。
高級
UITableview的優(yōu)化方法(緩存高度副渴,異步繪制熔吗,減少層級,hide佳晶,避免離屏渲染)
有沒有用過運行時桅狠,用它都能做什么?(交換方法,創(chuàng)建類中跌,給新創(chuàng)建的類增加方法咨堤,改變isa指針)
看過哪些第三方框架的源碼?都是如何實現(xiàn)的漩符?(如果沒有一喘,問一下多圖下載的設(shè)計)
SDWebImage的緩存策略?
AFN為什么添加一條常駐線程嗜暴?
KVO的使用凸克?實現(xiàn)原理?(為什么要創(chuàng)建子類來實現(xiàn))
當(dāng)你觀察一個對象時闷沥,一個新的類會被動態(tài)創(chuàng)建萎战。這個類繼承自該對象的原本的類,并重寫了被觀察屬性的 setter 方法舆逃。重寫的 setter 方法會負責(zé)在調(diào)用原 setter 方法之前和之后蚂维,通知所有觀察對象:值的更改。最后通過 isa 混寫(isa-swizzling) 把這個對象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個對象的類是什么 ) 指向這個新創(chuàng)建的子類路狮,對象就神奇的變成了新創(chuàng)建的子類的實例虫啥。我畫了一張示意圖,
KVC的使用奄妨?實現(xiàn)原理涂籽?(KVC拿到key以后,是如何賦值的砸抛?知不知道集合操作符又活,能不能訪問私有屬性,能不能直接訪問_ivar)
KVC 支持實例變量锰悼,KVO 只能手動支持手動設(shè)定實例變量的KVO實現(xiàn)監(jiān)聽