1. 什么是ARC?
引用計數(shù)機制
在 Objective-C 中,利用 引用計數(shù)器 來進行內存管理:每個對象都有一個對應的引用計數(shù),當這個對象被持有的時,其引用計數(shù)就會遞增,當這個對象的某個持有被釋放時,對象的引用計數(shù)就會遞減,當這個對象的引用計數(shù)為 0 的時候,這個對象就會被釋放。
每個對象都對應著一個引用計數(shù),在內存中,通過一個 SideTable RefcountMap 來存儲這個對應關系:對象的地址作為 Key,引用計數(shù)的值作為 Value。
struct SideTable {
// 保證原子操作的自旋鎖
spinlock_t slock;
// 引用計數(shù)的 hash 表
RefcountMap refcnts;
// weak 引用全局 hash 表
weak_table_t weak_table;
}
struct weak_table_t {
// 保存了所有指向指定對象的 weak 指針
weak_entry_t *weak_entries;
// 存儲空間
size_t num_entries;
// 參與判斷引用計數(shù)輔助量
uintptr_t mask;
// hash key 最大偏移值
uintptr_t max_hash_displacement;
};
在 iOS 中,Objective-C中提供了 兩種機制來管理對象的引用計數(shù)器,第一種是MRC(內存的手動管理),第二種是ARC(自動管理內存)
- MRC: 需要
手動添加的用來處理內存管理的引用計數(shù)的代碼,與對變量的管理相關的方法有:retain,release和autorelease。retain和release方法操作的是引用計數(shù),當引用計數(shù)為零時,便自動釋放內存 - 在ARC的內存管理中,都是由
系統(tǒng)去管理的,編譯器自動幫我們添加代碼,不需要我們去做任何內存操作。
2. block一般用那個關鍵字修飾,為什么?
copy修飾,把MRC下的棧block拷貝到堆里,防止訪問的時候block銷毀,造成崩潰
3. 用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什么?如果改用strong關鍵字,可能造成什么問題?
- 用@property聲明 NSString、NSArray、NSDictionary 經常使用copy關鍵字,是因為他們
有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。 - 如果我們使用是strong,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。
如果我們使用是strong,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。
5. @property 的本質是什么?ivar、getter、setter 是如何生成并添加到這個類中的。
“屬性”(property)有兩大概念:ivar(實例變量)、存取方法(access method=getter),即@property = ivar + getter + setter
Xcode5.0 之后編譯器默認幫我們實現(xiàn)了這些
6. 分別寫一個setter方法用于完成
@property (nonatomic,retain)NSString *name
@property (nonatomic,copy) NSString *name
-(void)setName:(NSString *)name
{
[name retain];
[_name release];
_name = name;
}
-(void)setName:(NSString *)name
{
[_name release];
_name = [name copy];
}
7. 說說assign vs weak,_block vs _weak的區(qū)別
8. 請說出下面代碼是否有問題,如果有問題請修改?
@autoreleasepool {
for (int i=0; i[largeNumber; i++) { (因識別問題,該行代碼中尖括號改為方括號代替)
Person *per = [[Person alloc] init];
[per autorelease];
}
}
autorelease雖然會使引用計數(shù)減一,但是它并不是立即減一,它的本質功能只是把對象放到離他最近的自動釋放池里。當自動釋放池銷毀了,才會向自動釋放池中的每一個對象發(fā)送release消息。這道題的問題就在autorelease。因為largeNumber是一個很大的數(shù),autorelease又不能使引用計數(shù)立即減一,所以在循環(huán)結束前會造成大次數(shù)循環(huán)內存暴漲溢出。
@autoreleasepool {
for (int i=0; i[100000; i++) { (因識別問題,該行代碼中尖括號改為方括號代替)
@autoreleasepool {
Person *per = [[Person alloc] init];
[per autorelease];
}
}
}
9. 請問下面代碼是否有問題,如有問題請修改?
@autoreleasepool {
NSString *str = [[NSString alloc] init];
[str retain];
[str retain];
str = @"jxl";
[str release];
[str release];
[str release];
}
這道題跟上題一樣存在內存泄露問題
1.內存泄露
2.指向常量區(qū)的對象不能release。
指針變量str原本指向一塊開辟的堆區(qū)空間,但是經過重新給str賦值,str的指向發(fā)生了變化,由原來指向堆區(qū)空間,到指向常量區(qū)。常量區(qū)的變量根本不需要釋放,這就導致了原來開辟的堆區(qū)空間沒有釋放,照成內存泄露。
10. 什么情況下使用weak關鍵字,相比assign有什么不同?什么情況使用weak關鍵字?
什么情況使用 weak 關鍵字?
- 在 ARC 中,在有可能出現(xiàn)
循環(huán)引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate、block。 - 自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義
IBOutlet 控件屬性一般也使用 weak,使用 storyboard(xib 不行)創(chuàng)建的 vc,會有一個叫 _topLevelObjectsToKeepAliveFromStoryboard 的私有數(shù)組強引用所有 top level 的對象,所以這時即便 outlet 聲明成 weak 也沒關系。當然,也可以使用 strong。
weak 和 assign 的不同點:
- weak、assign 修飾的屬性指向一個對象時
都不會增加對象的引用計數(shù)。然而在所指的對象被釋放時,weak 屬性值會被置為 nil,而assign 屬性不會。 - assign 可以用非 OC 對象以及基本類型,而 weak 必須用于 OC 對象。
11.內存管理語義(assign、strong、weak等的區(qū)別)
- assign
主要用于修飾基本數(shù)據(jù)類型,例如NSInteger,CGFloat,存儲在棧中,內存不用程序員管理。assign是可以修飾對象的,但是會出現(xiàn)野指針問題。 - weak
weak 修飾符指向但是并不持有該對象(弱引用),引用計數(shù)也不會加1。在 Runtime 中對該屬性進行了相關操作,無需處理,可以自動銷毀。weak用來修飾對象,多用于避免循環(huán)引用的地方。weak不可以修飾基本數(shù)據(jù)類型 - unsafe_unretained
此特質的語義和assign相同,但是它適用于“對象類型”,該特質表達一種“非擁有關系”,當目標對象遭到推毀時,屬性值不會自動清空,這一點與weak有區(qū)別。 - copy
copy關鍵字和 strong類似,copy 多用于修飾有可變類型的不可變對象上 NSString,NSArray,NSDictionary上 - Strong
Strong 修飾符表示指向并持有該對象(強引用),其修飾對象的引用計數(shù)會加1。該對象只要引用計數(shù)不為0就不會被銷毀。當然可以通過將變量強制賦值nil來進行銷毀。 - retain
retain屬性的setter方法是保留新值并釋放舊值,然后更新實例變量,令其指向新值。
12. @synthesize和@dynamic分別有什么作用?
- @property 有兩個對應的詞,一個是
@synthesize,一個是@dynamic。如果 @synthesize 和 @dynamic 都沒寫,那么默認的就是 @syntheszie var = _var;。 - @synthesize 的語義是如果你沒有手動實現(xiàn) setter 方法和 getter 方法,那么編譯器會自動為你加上這兩個方法。
- @dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實現(xiàn),不自動生成。(當然對于 readonly 的屬性只需提供 getter 即可)。假如一個屬性被聲明為 @dynamic var,然后你沒有提供 @setter 方法和 @getter 方法,編譯的時候沒問題,但是當程序運行到 instance.var = someVar,由于缺 setter 方法會導致程序崩潰;或者當運行到 someVar = var 時,由于缺 getter 方法同樣會導致崩潰。編譯時沒問題,運行時才執(zhí)行相應的方法,這就是所謂的動態(tài)綁定。
13. @property中有哪些屬性關鍵字?
屬性可以擁有的特質分為四類:
-
原子性--- nonatomic 特質
在默認情況下,由編譯器合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備 nonatomic 特質,則不使用自旋鎖。請注意,盡管沒有名為“atomic”的特質(如果某屬性不具備 nonatomic 特質,那它就是“原子的” ( atomic) ),但是仍然可以在屬性特質中寫明這一點,編譯器不會報錯。若是自己定義存取方法,那么就應該遵從與屬性特質相符的原子性。 -
讀/寫權限---readwrite(讀寫)、readonly (只讀) -
內存管理語義---assign、strong、 weak、unsafe_unretained、copy -
方法名---getter=<name> 、setter=<name>
14. ARC下,不顯式指定任何屬性關鍵字時,默認的關鍵字都有哪些?
- 對應基本數(shù)據(jù)類型默認關鍵字是
atomic, readwrite, assign - 對于普通的 Objective-C 對象
atomic, readwrite, strong
15. 如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?
若想令自己所寫的對象具有
拷貝功能,則需實現(xiàn)NSCopying協(xié)議。如果自定義的對象分為可變版本與不可變版本,那么就要同時實現(xiàn)NSCopying與NSMutableCopying協(xié)議。
具體步驟:
- 需聲明該類遵從 NSCopying 協(xié)議
- 實現(xiàn) NSCopying 協(xié)議。該協(xié)議只有一個方法:
- (id)copyWithZone:(NSZone *)zone;
對于很多現(xiàn)有類,如NSString,NSDictionary,。。。這個方法已經實現(xiàn)
至于如何重寫帶 copy 關鍵字的 setter這個問題,
如果拋開本例來回答的話,如下:
- (void)setName:(NSString *)name {
//[_name release]; MRC
_name = [name copy];
}
15. 如何調試BAD_ACCESS錯誤
野指針:指針指向的對象已經被回收掉了.這個指針就叫做野指針
僵尸對象 : 一個OC對象引用計數(shù)為0被釋放后就變成僵尸對象了,僵尸對象的內存已經被系統(tǒng)回收,雖然可能該對象還存在,數(shù)據(jù)依然在內存中,但僵尸對象已經是不穩(wěn)定對象了,不可以再訪問或者使用,它的內存是隨時可能被別的對象申請而占用的
BAD_ACCESS:野指針錯誤,主要的原因是,當某個對象被完全釋放,也就是retainCount引用計數(shù)為0后。再去通過該對象去調用release或者訪問成員變量就會發(fā)生野指針錯誤
- 重寫object的respondsToSelector方法,現(xiàn)實出現(xiàn)EXEC_BAD_ACCESS前訪問的最后一個object
- 通過Zombie
- 設置全局斷點快速定位問題代碼所在行
-
Xcode 7 已經集成了BAD_ACCESS捕獲功能:Address Sanitizer。 用法如下:在配置中勾選?Enable Address Sanitizer
image.png
使用野指針訪問僵尸對象.有的時候會出問題報錯(EXC_BAD_ACCESS),有的時候不會出問題
- 當野指針指向的僵尸對象所占用的空間
還沒有分配給別人的時候,這個時候其實是可以訪問的.因為對象的數(shù)據(jù)還在. - 當野指針指向的對象所占用的空間
分配給了別人的時候 這個時候訪問就會出問題. 所以,你不要通過1個野指針去訪問1個僵尸對象.
16. iOS nil,Nil,NULL,NSNULL的區(qū)別
nil (id)0
是OC對象的空指針,可正常調用方法(返回空值,false,零值等)
Nil (Class)0
是OC類的空指針,主要運用于runtime中,Class c = Nil; 其他特性與nil一致
NULL (void *)0
是C指針的空值,在OC中對非對象指針賦空值,如C指針,int *p = NULL
NSNULL [NSNULL null]
是OC中的空對象,可補足NSArray,NSDictinory中不能存儲nil的缺陷,在命令行輸出一般為"null"
17. 反射機制
內?。?code>反射)機制是面向對象語言的一個強大特性 , 檢查對象自己在運行時的信息(在繼承樹上的位置,是否遵循特定的協(xié)議,是否可以響應特定的消息)來避免出現(xiàn)未識別方法等問題。
1、獲取Class對象
// 獲取Class對象
+ (Class)class;
Class對象其實本質上就是一個結構體,這個結構體中的成員變量還是自己,這種設計方式非常像鏈表的數(shù)據(jù)結構。
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
}
//實例對象獲取Class對象
[self class]
//類對象獲取Class對象
[Person class]
2、動態(tài)的調用方法
// SEL和字符串轉換
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
// Class和字符串轉換
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class __nullable NSClassFromString(NSString *aClassName);
// Protocol和字符串轉換
FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) NS_AVAILABLE(10_5, 2_0);
FOUNDATION_EXPORT Protocol * __nullable NSProtocolFromString(NSString *namestr) NS_AVAILABLE(10_5, 2_0);
通過這些方法,我們可以在運行時選擇創(chuàng)建那個實例,并動態(tài)選擇調用哪個方法。
3、檢查繼承關系
// 當前對象是否這個類或其子類的實例
- (BOOL)isKindOfClass:(Class)aClass;
// 當前對象是否是這個類的實例
- (BOOL)isMemberOfClass:(Class)aClass;
// 當前對象是否遵守這個協(xié)議
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 當前對象是否實現(xiàn)這個方法
- (BOOL)respondsToSelector:(SEL)aSelector;
18. 一個NSObject對象占多大的內存?


所以我們可以很好的回答這個問題,系統(tǒng)分配了16個字節(jié)空間給NSObject對象,但是在64位環(huán)境下,NSObject只使用了8個字節(jié)
19. 你知道有哪些情況會導致app崩潰,分別可以用什么方法攔截并化解?
- unrecognized selector crash
可以利用
Runtime的消息轉發(fā)機制,通過重寫NSObject的forwardingTargetForSelector方法,我們就可以將無法識別的方法進行攔截并且將消息轉發(fā)到安全的樁類對象
- KVO crash
參考
KVOViewCOntroller,創(chuàng)建一個中間代理對象,被觀察對象dealloc之前,可以通過delegate自動將與自己有關的KVO關系都注銷掉,避免了KVO的被觀察者dealloc時仍然注冊著KVO導致的crash
- NSNotification crash
NSNotification Crash的防護原理很簡單, 利用
method swizzlinghook NSObject的dealloc函數(shù),再對象真正dealloc之前先調用一下removeObserver:即可。
同時hook了NSNotificationCenter的addObserver函數(shù),在其添加observer的時候,對observer動態(tài)添加標記flag。這樣在observer dealloc的時候,就可以通過flag標記來判斷其是否有必要調用removeObserver函數(shù)了。
- NSTimer的crash
參考
NSTimer防止循環(huán)應用的方法
- Container crash(數(shù)組越界NSRangeException,插nil等)
- NSString crash (字符串操作的crash)
NSArray/NSMutableArray/NSDictionary/NSMutableDictionary/NSCache的一些常用的會導致崩潰的API進行method swizzling,然后在swizzle的新方法中加入一些條件限制和判斷,從而讓這些API變的安全,比如AvoidCrash就是給各個系統(tǒng)類的添加分類實現(xiàn)method swizzling
- UI not on Main Thread Crash (非主線程刷UI)
- 野指針、僵尸對象
20. [self class] 與 [super class]
下面代碼輸出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
self和super的區(qū)別:
self 是類的一個隱藏參數(shù),每個方法的實現(xiàn)的第一個參數(shù)即為self。
super 并不是隱藏參數(shù),它實際上只是一個”編譯器標示符”,它負責告訴編譯器,當調用方法時,去調用父類的方法,而不是本類中的方法。
在調用[super class]的時候,runtime會去調用objc_msgSendSuper方法,而不是objc_msgSend
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
struct objc_super {
id receiver;
Class cls; // the class to search
}
在objc_msgSendSuper方法中,第一個參數(shù)是一個 objc_super 的結構體,這個結構體里面有兩個變量,一個是接收消息的 receiver,一個是 當前類的父類 super_class。
objc_msgSendSuper 的工作原理:
從objc_super結構體指向的superClass父類的方法列表開始查找selector,找到后以objc->receiver去調用這個selector。注意,最后的調用者是objc->receiver
objc_super->receiver = self
21. isKindOfClass 與 isMemberOfClass
下面代碼輸出什么?
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}
先來分析一下源碼
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
return ISA();
}
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return (Class)(isa.bits & ISA_MASK);
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}

第一行res1輸出應該為YES
第二行res2輸出NO
第三行的res3輸出為NO
第四行res4輸出NO
Root class(meta) 的 superclass 就是 Root class(class),也就是NSObject本身。所以第二次循環(huán)相等,于是第一行res1輸出應該為YES。
22. Objective-C 如何實現(xiàn)多重繼承?
Object-c的類沒有多繼承,只支持單繼承,如果要實現(xiàn)多繼承的話,可使用如下幾種方式間接實現(xiàn)
1. 通過組合實現(xiàn)
A和B組合,作為C類的組件
2. 通過協(xié)議實現(xiàn)
C類實現(xiàn)A和B類的協(xié)議方法
3. 消息轉發(fā)實現(xiàn)
forwardInvocation:方法
23. LLDB常用的調試命令有哪些?
po:print object的縮寫,表示顯示對象的文本描述,如果對象不存在則打印nil。
p:可以用來打印基本數(shù)據(jù)類型。
call:執(zhí)行一段代碼 如:call NSLog(@"%@", @"yang")
bt:打印當前線程堆棧信息 (bt all打印所有線程堆棧信息)
expr:動態(tài)執(zhí)行指定表達式
image:常用來尋找棧地址對應代碼位置 如:image lookup --address 0xxxx
breakpoint:斷點操作
