iOS底層面試題

runtime的學(xué)習(xí)整理

對象

消息

應(yīng)用程序加載渊抽、類、分類初始化

相關(guān)面試題

1. loadinitialize方法的調(diào)用原則和調(diào)用順序?

  • load方法

    • load方法在應(yīng)用程序加載過程中(dyld)完成調(diào)用丧叽,在main函數(shù)之前
    • 在底層進(jìn)行load_images處理時(shí),維護(hù)了兩個(gè)load加載表,一個(gè)的表洗做,另一個(gè)為分類的表,優(yōu)先對類的load方法發(fā)起調(diào)用
    • 在對類load方法進(jìn)行處理時(shí)彰居,進(jìn)行了遞歸處理诚纸,以確保父類優(yōu)先被處理
    • 所以load方法的調(diào)用順序?yàn)?code>父類、子類陈惰、分類
    • 分類load方法的調(diào)用順序根據(jù)編譯順序為準(zhǔn)
  • initialize方法

    • initialize第一次消息發(fā)送的時(shí)候調(diào)用畦徘,所以load先于initialize調(diào)用
    • 分類的?法是在類realize之后attach進(jìn)去的插在前?,所以如果分類中實(shí)現(xiàn)了initialize方法,會優(yōu)先調(diào)?分類的initialize方法
    • initialize內(nèi)部實(shí)現(xiàn)原理是消息發(fā)送旧烧,所以如果子類沒有實(shí)現(xiàn)initialize會調(diào)用父類的initialize方法影钉,并且會調(diào)用兩次
    • 因?yàn)閮?nèi)部同時(shí)使用了遞歸,所以如果子類父類都實(shí)現(xiàn)了initialize方法掘剪,那么會優(yōu)先調(diào)用父類的平委,再調(diào)用子類

具體的實(shí)現(xiàn)以及底層邏輯在類的加載(上)-- _objc_init&read_images
中。

補(bǔ)充c++構(gòu)造函數(shù)

  • 在分析dyld之后夺谁,可以確定這樣的一個(gè)調(diào)用順序廉赔,load->c++->main函數(shù)
  • 但是如果c++寫在objc工程中,在objc_init()調(diào)用時(shí)匾鸥,會通過static_init()方法優(yōu)先調(diào)用c++函數(shù)蜡塌,而不需要等到_dyld_objc_notify_registerdyld注冊load_images之后再調(diào)用
  • 同時(shí),如果objc_init()自啟的話也不需要dyld進(jìn)行啟動(dòng)勿负,也可能會發(fā)生c++函數(shù)在load方法之前調(diào)用的情況

2.Runtime是什么馏艾?

  • Runtime是由CC++匯編實(shí)現(xiàn)的?套API,為OC語?加?了?向?qū)ο?/code>奴愉,運(yùn)?時(shí)的功能
  • 運(yùn)?時(shí)(Runtime)是指將數(shù)據(jù)類型的確定由編譯時(shí)推遲到了運(yùn)?時(shí)琅摩,如類擴(kuò)展分類的區(qū)別
  • 平時(shí)編寫的OC代碼,在程序運(yùn)?過程中锭硼,其實(shí)最終會轉(zhuǎn)換成RuntimeC語?代碼房资,RuntimeObject-C的幕后?作者

3.?法的本質(zhì),sel是什么檀头?IMP是什么轰异?兩者之間的關(guān)系?是什么?

  • ?法的本質(zhì):發(fā)送消息暑始,消息會有以下?個(gè)流程:
    • 快速查找objc_msgSend)~ cache_t 緩存消息
    • 慢速查找~ 遞歸??或?類 ~ lookUpImpOrForward
    • 查找不到消息: 動(dòng)態(tài)?法解析 ~ resolveInstanceMethod
    • 消息快速轉(zhuǎn)發(fā) ~ forwardingTargetForSelector
    • 消息慢速轉(zhuǎn)發(fā) ~ methodSignatureForSelectorforwardInvocation
  • sel?法編號搭独,在read_images期間就編譯進(jìn)?了內(nèi)存
    • sel的內(nèi)存結(jié)構(gòu):typedef struct objc_selector *SEL;
  • imp就是我們函數(shù)實(shí)現(xiàn)指針,找imp就是找函數(shù)的過程
  • sel就相當(dāng)于書本的?錄tittle廊镜,imp就是書本的?碼

4.能否向編譯后的得到的類中增加實(shí)例變量戳稽?能否向運(yùn)?時(shí)創(chuàng)建的類中添加實(shí)例變量?

  • 不能向編譯后的得到的類中增加實(shí)例變量
    • 編譯好的實(shí)例變量存儲的位置在ro期升,?旦編譯完成,內(nèi)存結(jié)構(gòu)就完全確定互躬;
    • 可以通過分類向類中添加方法屬性關(guān)聯(lián)對象
  • 可以運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量播赁,只要內(nèi)沒有注冊到內(nèi)存還是可以添加
    • 可以通過objc_allocateClassPair在運(yùn)行時(shí)創(chuàng)建類,并向其中添加成員變量和屬性吼渡,見下面代碼:
// 使用objc_allocateClassPair創(chuàng)建一個(gè)類Class
const char * className = "SelClass";
Class SelfClass = objc_getClass(className);
if (!SelfClass){
    Class superClass = [NSObject class];
    SelfClass = objc_allocateClassPair(superClass, className, 0);
}
        
// 使用class_addIvar添加一個(gè)成員變量
BOOL isSuccess = class_addIvar(SelfClass, "name", sizeof(NSString *), log2(_Alignof(NSString *)), @encode(NSString *));

class_addMethod(SelfClass, @selector(addMethodForMyClass:), (IMP)addMethodForMyClass, "V@:");
     

5.[self class][super class]區(qū)別和解析容为?
通過代碼案例分析這個(gè)問題,首先創(chuàng)建LGTeacher繼承LGPerson,并在LGTeacherinit初始化方法中坎背,調(diào)用了[self class]和[super class]替劈,查看輸出結(jié)果。

    // LGPerson
    @interface LGPerson : NSObject
    @end

    @implementation LGPerson
    @end
    
    // LGTeacher
    @interface LGTeacher : LGPerson
    @end

    @implementation LGTeacher
    - (instancetype)init{
        self = [super init];
        if (self) {
           NSLog(@"%@ - %@", [self class], [super class]);
        }
        return self;
    }
    @end

案例分析:
很清楚LGTeaherLGPerson都沒有實(shí)現(xiàn)class方法得滤,那么根據(jù)消息發(fā)送的原理陨献,他們最終都會調(diào)用到NSObject的實(shí)例方法class,該方法實(shí)現(xiàn)如下:

    - (Class)class {
        return object_getClass(self);
    }

調(diào)用方法的本質(zhì)是發(fā)送消息objc_msgSend懂更,并且有兩個(gè)隱藏參數(shù)眨业,分別是id selfSEL sel,這里的隱藏參數(shù)self就是我們要分析的類型沮协。

  • [self class]輸出是LGTeacher龄捡,這個(gè)沒有什么問題稳其!因?yàn)橄⒌陌l(fā)送者是LGTeacher對象耙册,通過消息發(fā)送機(jī)制,找到NSObejct并調(diào)用class方法续挟,但是消息的接受者沒有發(fā)生改變行瑞,依然是LGTeacher對象奸腺!
  • [super class]就不一樣了,同過xcrun查看main.cpp文件蘑辑,查看底層源碼得出以下:
    main.cpp查看super底層

    super關(guān)鍵字洋机,在底層最終使用了objc_msgSendSuper方法,同時(shí)其接受者是(id)self洋魂,全局搜搜objc_msgSendSuper的邏輯绷旗,見下圖:
    objc_msgSendSuper

    根據(jù) Objc-818.2源碼查看objc_super如下:
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

可以看到結(jié)構(gòu)體中只有兩個(gè)參數(shù),分別是id receiverClass super_class副砍,其中super_class表示第一個(gè)要去查找的類衔肢,至此我們可以得出結(jié)論,在LGTeacher中調(diào)用[super class]豁翎,其內(nèi)部會調(diào)用objc_msgSendSuper方法角骤,并且會傳入?yún)?shù)objc_super,其中receiverLGTeacher對象心剥,super_classLGTeacher類通過class_getsuperclass獲取的父類邦尊,也就是要第一個(gè)查找的類。

通過下符號斷點(diǎn)--objc_msgSendSuper2优烧,查看寄存器蝉揍,其中第一個(gè)地址為發(fā)放的第一個(gè)隱藏參數(shù),也就是objc_super畦娄,通過類型強(qiáng)制又沾,該結(jié)構(gòu)體封裝的recevierLGTeacher弊仪,super_classLGPerson,具體看下圖:

查看寄存器

得出結(jié)論:[super class]的接收者依然是LGTeacher對象杖刷,去調(diào)用父類的方法励饵。

最后查看運(yùn)行結(jié)果:

查看運(yùn)行結(jié)果

果然輸出都是LGTeacher!;肌役听!

補(bǔ)充:objc_msgSendSuper為什么會調(diào)用到了objc_msgSendSuper2
通過 Objc-818.2源碼查看的出:


全局搜索objc_msgSendSuper不瓶,進(jìn)入?yún)R編實(shí)現(xiàn)流程中禾嫉,在匯編流程中,最終會調(diào)用objc_msgSendSuper2蚊丐,見下圖:
調(diào)用objc_msgSendSuper2

注意:這題還不夠明白的話建議參考以上的消息相關(guān)文章熙参,寫得比較詳細(xì)哦。

5.指針平移和消息發(fā)送原理案例分析
LGPerson類有一個(gè)實(shí)例方法saySomething麦备,在viewDidLoad中通過兩種方式調(diào)用該方法孽椰,一種是通過創(chuàng)建LGPerson對象調(diào)用,另一種是通過橋接調(diào)用凛篙,見下面代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LGPerson *person = [LGPerson alloc];
    [person saySomething];
    
    Class cls = [LGPerson class];
    void  *kc = &cls;
    [(__bridge id)kc saySomething];
}

@implementation LGPerson
- (void)saySomething{
    NSLog(@"%s - %@",__func__);
}
@end

問題1:是否能夠調(diào)用成功黍匾?

  • 方法調(diào)用的本質(zhì)是發(fā)送消息,通過對象的isa找到地址呛梆,進(jìn)行地址平移锐涯,通過sel找到對應(yīng)的方法實(shí)現(xiàn)imp
    • 毋庸置疑,person saySomething];此種方式肯定是沒問題的
      通過person對象的isa指針找到對應(yīng)的類填物,在類中進(jìn)行地址平移纹腌,首先在
      cache_t快速查找,如果找不到滞磺,則在方法列表以及父類的方法列表中查找升薯,總結(jié)一下就是:以類的地址作為入口,進(jìn)行地址平移击困,最終找到對應(yīng)的imp涎劈。

    • [(__bridge id)kc saySomething];是否可以呢?
      首先Class cls = [LGPerson class];阅茶,cls是什么蛛枚?cls是一個(gè)指針,Class的定義是一個(gè)指針脸哀,指向一個(gè)objc_class的指針坤候,這里就是指向LGPerson類。將cls的地址賦值給kc企蹭,此時(shí)kccls的地址白筹,也指向了

分析得出:兩者調(diào)用的入口是一致的谅摄,從同一個(gè)地址開始進(jìn)行方法查找流程徒河,肯定是可以調(diào)用到的,person除了有地址送漠,還有內(nèi)存數(shù)據(jù)結(jié)構(gòu)顽照;kc只有一個(gè)地址,是一個(gè)偽裝的person對象闽寡。請看下圖:

查找流程

通過lldb調(diào)試可以發(fā)現(xiàn)代兵,kc指向類,見下圖:

查看kc指向class

最后運(yùn)行代碼:


運(yùn)行代碼

案例擴(kuò)展
LGPerson中添加一個(gè)屬性kc_name爷狈,實(shí)現(xiàn)代碼如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    LGPerson *person = [LGPerson alloc];
    person.kc_name = @"name123";
    [person saySomething];

    Class cls = [LGPerson class];
    void  *kc = &cls;
    [(__bridge id)kc saySomething];
}

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
- (void)saySomething;
@end

@implementation LGPerson
- (void)saySomething{
   NSLog(@"%s - %@", __func__, self.kc_name);
}
@end

那么這樣子的輸出結(jié)果優(yōu)勢怎么樣子呢植影?是不是跟上面的一樣都能輸出呢?哈哈涎永,以下繼續(xù)進(jìn)行lldb調(diào)試思币,請繼續(xù)走!

lldb調(diào)試

經(jīng)過調(diào)試可以知道person進(jìn)行地址平移獲取屬性kc_name羡微,此數(shù)據(jù)結(jié)構(gòu)是在中谷饿,而kc只是一個(gè)地址,獲取kc數(shù)據(jù)結(jié)構(gòu)只是輸出了其在中的數(shù)據(jù)信息妈倔。

引申出壓棧的概念
通過上面的案例分析博投,可以知道根本原因是棧中地址平移的問題,那么在程序運(yùn)行過程中盯蝴,壓棧邏輯是怎樣的呢毅哗?先入后出,這個(gè)比較清楚结洼,那結(jié)構(gòu)體是如何壓棧的呢黎做,函數(shù)調(diào)用中參數(shù)的壓棧邏輯又是怎樣的?

  • 壓棧松忍,地址從大到小蒸殿,先進(jìn)去的地址大(棧開辟由高地址到低地址
    image.png
  • 添加結(jié)構(gòu)體,查看棧中的地址
    查看棧中的地址

    添加完結(jié)構(gòu)體后鸣峭,通過lldb的出下圖:
    壓棧過程

    明顯看出結(jié)構(gòu)體占用了16字節(jié)宏所,那么結(jié)構(gòu)體內(nèi)容在棧中的位置是怎么樣子的呢?繼續(xù)進(jìn)行lldb調(diào)試:
    查看結(jié)構(gòu)體棧中布局

    通過lldb輸出結(jié)構(gòu)體中兩個(gè)屬性的地址摊溶,發(fā)現(xiàn)爬骤,num1num2的上面,所以在壓棧過程中莫换,按照下圖中的方式進(jìn)行的:
    具體壓棧過程

函數(shù)參數(shù)壓棧順序
通過下面的案例代碼進(jìn)行進(jìn)一步探索:


有上面可以得出:

  • viewDidLoad方法中person指針的地址和kcFunctionperson指針地址是不一樣的霞玄,雖然他們都執(zhí)行了同一片堆區(qū)
  • 根據(jù)指針的地址發(fā)現(xiàn)骤铃,參數(shù)在壓棧時(shí)是根據(jù)參數(shù)的順序進(jìn)行的,第一個(gè)參數(shù)先入棧坷剧,然后依次壓棧

補(bǔ)充:runtime面試題持續(xù)更新中哦惰爬。。惫企。敬請期待撕瞧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市狞尔,隨后出現(xiàn)的幾起案子丛版,更是在濱河造成了極大的恐慌,老刑警劉巖偏序,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件页畦,死亡現(xiàn)場離奇詭異,居然都是意外死亡禽车,警方通過查閱死者的電腦和手機(jī)寇漫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來殉摔,“玉大人州胳,你說我怎么就攤上這事∫菰拢” “怎么了栓撞?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碗硬。 經(jīng)常有香客問我瓤湘,道長,這世上最難降的妖魔是什么恩尾? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任弛说,我火速辦了婚禮,結(jié)果婚禮上翰意,老公的妹妹穿的比我還像新娘木人。我一直安慰自己,他們只是感情好冀偶,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布醒第。 她就那樣靜靜地躺著,像睡著了一般进鸠。 火紅的嫁衣襯著肌膚如雪稠曼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天客年,我揣著相機(jī)與錄音霞幅,去河邊找鬼漠吻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蝗岖,可吹牛的內(nèi)容都是我干的侥猩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼抵赢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了唧取?” 一聲冷哼從身側(cè)響起铅鲤,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枫弟,沒想到半個(gè)月后邢享,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淡诗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年骇塘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片韩容。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡款违,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出群凶,到底是詐尸還是另有隱情插爹,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布请梢,位于F島的核電站赠尾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏毅弧。R本人自食惡果不足惜气嫁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望够坐。 院中可真熱鬧寸宵,春花似錦、人聲如沸咆霜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛾坯。三九已至光酣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脉课,已是汗流浹背救军。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工财异, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唱遭。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓戳寸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拷泽。 傳聞我的和親對象是個(gè)殘疾皇子疫鹊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容