這篇文章可能有點長已烤,所以分了三篇文章記錄。內(nèi)容來自網(wǎng)上的面試題以及自己面試過程中遇到的問題總結(jié)妓羊,也會定期更新,不合理的地方歡迎指正稍计。
- 更多技術(shù)題合集:
提升iOS開發(fā)技能學(xué)習(xí)網(wǎng)址:docs.qq.com/doc/DVWlQam9Qd3B1cEF2
為自己的面試躁绸,為自己的跳槽,加油吧 iOS開發(fā)
一臣嚣、分類和擴展
-
分類和擴展有什么區(qū)別净刮?
category
1、分類給類添加方法
2硅则、不能通過正常模式給類添加屬性淹父,但是可以通過 runtime 添加
3、如果在分類中通過@property定義屬性怎虫,那么只會對屬性的 getter setter 方法進行聲明暑认,不會實現(xiàn)。同時也不會生成帶下劃線的成員變量
4大审、在運行時才會編譯代碼
extension
1蘸际、擴展可以看成是特殊的分類 匿名分類
2、可以給類添加屬性徒扶,私有
3粮彤、可以給類添加方法,也是私有
4姜骡、在編譯時期就會編譯导坟,與 .h 文件中的@interface和.m文件里的@implement一起形成了一個完整的類
5、擴展一般用來隱藏類的信息圈澈,所以使用擴展的前提是要有類的源碼惫周!所以針對系統(tǒng)自帶的類,是無法使用擴展的士败。
為什么分類可以添加方法闯两,而不能添加成員變量褥伴??漾狼?
因為在運行時重慢,類的內(nèi)部布局早已經(jīng)確定,如果添加實例變量逊躁,會破壞類的內(nèi)部布局似踱。
-
分類的結(jié)構(gòu)體里面有哪些成員?
category 是一個指向類結(jié)構(gòu)體的指針稽煤,其結(jié)構(gòu)體的定義如下:
typedef struct objc_category *Category;
struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 分類名
char *class_name OBJC2_UNAVAILABLE; // 分類所屬的類名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 實例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 類方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分類所實現(xiàn)的協(xié)議列表
}
可以 與 objc_class 的結(jié)構(gòu)體進行對比:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息核芽,默認為0
long info OBJC2_UNAVAILABLE; // 類信息,供運行期使用的一些位標(biāo)識
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; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
經(jīng)過對比酵熙,發(fā)現(xiàn)少了一個 struct objc_ivar_list *ivars
成員變量列表T颉!匾二!這也就說明了 分類是不能添加成員變量的哮独。
-
分類加載和方法調(diào)用順序
1、 加載:先加載原始類的 load() 方法 察藐,再去加載 分類中的 load() 方法皮璧,如果有多個分類,則按照編譯順序加載
2分飞、調(diào)用:先調(diào)用分類中的方法悴务,再去調(diào)用原始類中的方法,如果要是重名譬猫,則會覆蓋原始類中的方法(因為在方法列表中讯檐,分類的方法會排在原始類中同名方法的前面)。
二删窒、atomic的實現(xiàn)機制裂垦;為什么不能保證絕對的線程安全(最好可以結(jié)合場景來說)?
-
atomic
atomic
1肌索、會對屬性的 setter/getter 方法進行加鎖蕉拢,這僅僅只能保證在 操作 setter/getter 方法是安全的。不能保證其他線程的安全
2诚亚、例如 : 線程1調(diào)用了某一屬性的setter方法并進行到了一半,線程2調(diào)用其getter方法,那么會執(zhí)行完setter操作后,在執(zhí)行g(shù)etter操作,線程2會獲取到線程1 setter后的完整的值.
3晕换、當(dāng)幾個線程同時調(diào)用同一屬性的setter、getter方法時,會get到一個完整的值,但get到的值不可控站宗。例如 : 線程1 調(diào)用getter 闸准,線程2 調(diào)用setter,線程3 調(diào)用setter梢灭,這3個線程并行同時開始,線程1會get到一個值,但是這個值不可控,可能是線程2,線程3 set之前的原始值,可能是線程2 set的值,也可能是線程3 set的值
-
atomic是線程安全的嗎?
不是,很多文章談到atomic和nonatomic的區(qū)別時,都說atomic是線程安全,其實這個說法是不準(zhǔn)確的.atomic只是對屬性的getter/setter方法進行了加鎖操作,這種安全僅僅是set/get 的讀寫安全,并非真正意義上的線程安全,因為線程安全還有讀寫之外的其他操作
比如:如果當(dāng)一個線程正在get或set時,又有另一個線程同時在進行release操作,可能會直接crash
-
nonatomic
nonatomic
系統(tǒng)生成的getter/setter方法沒有加鎖線程不安全,但更快當(dāng)多個線程同時訪問同一個屬性,會出現(xiàn)無法預(yù)料的結(jié)果
-
atomic的seter getter內(nèi)部實現(xiàn)
- (void)setCurrentImage:(UIImage *)currentImage
{
@synchronized(self) {
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
}
- (UIImage *)currentImage
{
@synchronized(self) {
return _currentImage;
}
}
-
nonatomic的seter getter內(nèi)部實現(xiàn)
- (void)setCurrentImage:(UIImage *)currentImage
{
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
- (UIImage *)currentImage
{
return _currentImage;
}
三夷家、被weak修飾的對象在被釋放的時候會發(fā)生什么蒸其?是如何實現(xiàn)的?知道sideTable么库快?里面的結(jié)構(gòu)可以畫出來么摸袁?
參考:http://www.reibang.com/p/b93d61418f17
這個問題在 數(shù)據(jù)結(jié)構(gòu)&&算法里面做了解答
四、關(guān)聯(lián)對象有什么應(yīng)用义屏,系統(tǒng)如何管理關(guān)聯(lián)對象靠汁?其被釋放的時候需要手動將其指針置空么?
我們在 iOS 開發(fā)中經(jīng)常需要使用分類(Category)闽铐,為已經(jīng)存在的類添加屬性的需求蝶怔,但是使用 @property 并不能在分類中正確創(chuàng)建實例變量和存取方法。這時候就會用到關(guān)聯(lián)對象兄墅。
分類中的 @property
@interface DKObject : NSObject
@property (nonatomic, strong) NSString *property;
@end
在使用上述代碼時會做三件事:
- 生成帶下劃線的實例變量 _property
- 生成 getter 方法 - property
- 生成 setter 方法 - setProperty:
@implementation DKObject {
NSString *_property;
}
- (NSString *)property {
return _property;
}
- (void)setProperty:(NSString *)property {
_property = property;
}
@end
這些代碼都是編譯器為我們生成的踢星,雖然你看不到它,但是它確實在這里隙咸,我們既然可以在類中使用 @property
生成一個屬性斩狱,那么為什么在分類中不可以呢?
我們來做一個小實驗:創(chuàng)建一個 DKObject 的分類 Category扎瓶,并添加一個屬性 categoryProperty
:
@interface DKObject (Category)
@property (nonatomic, strong) NSString *categoryProperty;
@end
看起來還是很不錯的,不過 Build 一下這個 Demo泌枪,會發(fā)現(xiàn)有這么一個警告:
在這里的警告告訴我們 categoryProperty 屬性的存取方法
需要自己手動去實現(xiàn)概荷,或者使用 @dynamic 在運行時實現(xiàn)這些方法。
換句話說碌燕,分類中的 @property 并沒有為我們生成實例變量以及存取方法
误证,而需要我們手動實現(xiàn)。
使用關(guān)聯(lián)對象
Q:我們?yōu)槭裁匆褂藐P(guān)聯(lián)對象修壕?
A:因為在分類中 @property 并不會自動生成實例變量以及存取方法
愈捅,所以一般使用關(guān)聯(lián)對象為已經(jīng)存在的類添加『屬性
』。
以下是與關(guān)聯(lián)對象有關(guān)的 API慈鸠,并在分類中實現(xiàn)一個偽屬性:
#import "DKObject+Category.h"
#import <objc/runtime.h>
@implementation DKObject (Category)
- (NSString *)categoryProperty {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setCategoryProperty:(NSString *)categoryProperty {
objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
這里的
_cmd
代指當(dāng)前方法的選擇子蓝谨,也就是@selector(categoryProperty)
。
我們使用了兩個方法 objc_getAssociatedObject
以及 objc_setAssociatedObject
來模擬『屬性』的存取方法青团,而使用關(guān)聯(lián)對象模擬實例變量譬巫。
在這里有必要解釋兩個問題:
- 為什么向方法中傳入
@selector(categoryProperty)?
-
OBJC_ASSOCIATION_RETAIN_NONATOMIC
是干什么的督笆?
關(guān)于第一個問題芦昔,我們需要看一下這兩個方法的原型:
id objc_getAssociatedObject(id object, const void *key);
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
@selector(categoryProperty)
也就是參數(shù)中的key
,其實可以使用靜態(tài)指針 static void *
類型的參數(shù)來代替娃肿,不過在這里咕缎,筆者強烈推薦使用 @selector(categoryProperty)
作為 key 傳入珠十。因為這種方法省略了聲明參數(shù)的代碼,并且能很好地保證 key 的唯一性
凭豪。
OBJC_ASSOCIATION_RETAIN_NONATOMIC
又是什么呢焙蹭?如果我們使用 Command 加左鍵查看它的定義:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
從這里的注釋我們能看到很多東西,也就是說不同的 objc_AssociationPolicy 對應(yīng)了不通的屬性修飾符:
objc_AssociationPolicy | modifier |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, strong |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy |
OBJC_ASSOCIATION_RETAIN | atomic, strong |
OBJC_ASSOCIATION_COPY | atomic, copy |
而我們在代碼中實現(xiàn)的屬性 categoryProperty 就相當(dāng)于使用了 nonatomic 和 strong 修飾符墅诡。
在obj dealloc時候會調(diào)用object_dispose壳嚎,檢查有無關(guān)聯(lián)對象,有的話_object_remove_assocations刪除
五末早、KVO的底層實現(xiàn)烟馅?如何取消系統(tǒng)默認的KVO并手動觸發(fā)(給KVO的觸發(fā)設(shè)定條件:改變的值符合某個條件時再觸發(fā)KVO)?
實現(xiàn)原理:
- 當(dāng)某個類的對象第一次被觀察時然磷,系統(tǒng)就會在運行期動態(tài)地創(chuàng)建該類的一個派生類郑趁,在這個派生類中重寫基類中任何被觀察屬性的 setter 方法。
- 派生類在被重寫的 setter 方法中實現(xiàn)真正的通知機制姿搜,就如前面手動實現(xiàn)鍵值觀察那樣寡润。這么做是基于設(shè)置屬性會調(diào)用 setter 方法,而通過重寫就獲得了 KVO 需要的通知機制舅柜。當(dāng)然前提是要通過遵循 KVO 的屬性設(shè)置方式來變更屬性值梭纹,如果僅是直接修改屬性對應(yīng)的成員變量,是無法實現(xiàn) KVO 的致份。
- 同時派生類還重寫了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個類变抽。然后系統(tǒng)將這個對象的 isa 指針指向這個新誕生的派生類,因此這個對象就成為該派生類的對象了氮块,因而在該對象上對 setter 的調(diào)用就會調(diào)用重寫的 setter绍载,從而激活鍵值通知機制。此外滔蝉,派生類還重寫了 dealloc 方法來釋放資源击儡。
KVO與Notification之間的區(qū)別:
notification是需要一個發(fā)送notification的對象,一般是notificationCenter蝠引,來通知觀察者阳谍。
KVO是直接通知到觀察對象,并且邏輯非常清晰立肘,實現(xiàn)步驟簡單边坤。
六、Autoreleasepool所使用的數(shù)據(jù)結(jié)構(gòu)是什么谅年?AutoreleasePoolPage結(jié)構(gòu)體了解么茧痒?
每創(chuàng)建一個池子,會在首部創(chuàng)建一個 哨兵 對象,作為標(biāo)記
最外層池子的頂端會有一個next指針融蹂。當(dāng)鏈表容量滿了旺订,就會在鏈表的頂端弄企,并指向下一張表。
Autorelease對象什么時候釋放区拳?
這個問題拿來做面試題拘领,問過很多人,沒有幾個能答對的樱调。很多答案都是“當(dāng)前作用域大括號結(jié)束時釋放”约素,顯然木有正確理解Autorelease機制。
在沒有手加Autorelease Pool的情況下笆凌,Autorelease對象是在當(dāng)前的runloop迭代結(jié)束時釋放的圣猎,而它能夠釋放的原因是系統(tǒng)在每個runloop迭代中都加入了自動釋放池Push和Pop
例子:
__weak id reference = nil;
- (void)viewDidLoad {
[super viewDidLoad]; NSString *str = [NSString stringWithFormat:@"sunnyxx"]; // str是一個autorelease對象,設(shè)置一個weak的引用來觀察它
reference = str;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"%@", reference);
// Console: sunnyxx
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"%@", reference);
// Console: (null)
}
當(dāng)然乞而,我們也可以手動干預(yù)Autorelease對象的釋放時機:
- (void)viewDidLoad
{
[super viewDidLoad];
@autoreleasepool { NSString *str = [NSString stringWithFormat:@"sunnyxx"];
} NSLog(@"%@", str);
// Console: (null)
}
Autorelease原理
AutoreleasePoolPage
ARC下送悔,我們使用@autoreleasepool{}來使用一個AutoreleasePool,隨后編譯器將其改寫成下面的樣子:
void *context = objc_autoreleasePoolPush();
// {}中的代碼objc_autoreleasePoolPop(context);
而這兩個函數(shù)都是對AutoreleasePoolPage的簡單封裝爪模,所以自動釋放機制的核心就在于這個類欠啤。
AutoreleasePoolPage是一個C++實現(xiàn)的類
- AutoreleasePool并沒有單獨的結(jié)構(gòu),而是由若干個
AutoreleasePoolPage以雙向鏈表
的形式組合而成(分別對應(yīng)結(jié)構(gòu)中的parent指針和child指針)屋灌。 - AutoreleasePool是按
線程一一對應(yīng)
的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程)洁段。 - AutoreleasePoolPage每個對象會開辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁的大小)共郭,除了上面的實例變量所占空間眉撵,剩下的空間全部用來儲存autorelease對象的
地址
。 - 上面的id *next指針作為游標(biāo)指向棧頂最新add進來的autorelease對象的
下一個位置
落塑。 - 一個AutoreleasePoolPage的空間被占滿時,會新建一個AutoreleasePoolPage對象罐韩,連接鏈表憾赁,后來的autorelease對象在新的page加入。
所以散吵,若當(dāng)前線程中只有一個AutoreleasePoolPage對象龙考,并記錄了很多autorelease對象地址時內(nèi)存如下圖:
圖中的情況,這一頁再加入一個autorelease對象就要滿了(也就是next指針馬上指向棧頂)矾睦,這時就要執(zhí)行上面說的操作晦款,建立下一頁page對象,與這一頁鏈表連接完成后枚冗,新page的next指針被初始化在棧底(begin的位置)缓溅,然后繼續(xù)向棧頂添加新對象。
所以赁温,向一個對象發(fā)送- autorelease消息坛怪,就是將這個對象加入到當(dāng)前AutoreleasePoolPage的棧頂next指針指向的位置
釋放時刻
每當(dāng)進行一次objc_autoreleasePoolPush調(diào)用時淤齐,runtime向當(dāng)前的AutoreleasePoolPage中add進一個哨兵對象,值為0(也就是個nil)袜匿,那么這一個page就變成了下面的樣子:
objc_autoreleasePoolPush的返回值正是這個哨兵對象的地址更啄,被objc_autoreleasePoolPop(哨兵對象)作為入?yún)ⅲ谑牵?/p>
1.根據(jù)傳入的哨兵對象地址找到哨兵對象所處的page
2.在當(dāng)前page中居灯,將晚于哨兵對象插入的所有autorelease對象都發(fā)送一次- release消息祭务,并向回移動next指針到正確位置
3.補充2:從最新加入的對象一直向前清理,可以向前跨越若干個page怪嫌,直到哨兵所在的page(在一個page中义锥,是從高地址向低地址清理)
剛才的objc_autoreleasePoolPop執(zhí)行后,最終變成了下面的樣子:
嵌套的AutoreleasePool
知道了上面的原理喇勋,嵌套的AutoreleasePool就非常簡單了缨该,pop的時候總會釋放到上次push的位置為止,多層的pool就是多個哨兵對象而已川背,就像剝洋蔥一樣贰拿,每次一層,互不影響熄云。
七膨更、class_ro_t 和 class_rw_t 的區(qū)別?
Class的結(jié)構(gòu)
class_rw_t
class_rw_t里面的methods缴允、properties荚守、protocols是二維數(shù)組,是可讀可寫的练般,包含了類的初始內(nèi)容矗漾、分類的內(nèi)容
class_ro_t
class_ro_t里面的baseMethodList、baseProtocols薄料、ivars敞贡、baseProperties是一維數(shù)組,是只讀的摄职,包含了類的初始內(nèi)容