2020 阿里蚜点、字節(jié)iOS面試題之Runtime相關(guān)問題3(附答案)

目錄

runtime相關(guān)問題之內(nèi)存部分的關(guān)聯(lián)屬性或者hook相關(guān)的Method Swizzle

經(jīng)過前兩期內(nèi)容 我們這期來講一下 內(nèi)存部分的剩余問題 主要包含如下:

  1. Method Swizzle注意事項
  2. 屬性修飾符atomic的內(nèi)部實現(xiàn)是怎么樣的?能保證線程安全嗎
  3. iOS 中內(nèi)省的幾個方法有哪些胰坟?內(nèi)部實現(xiàn)原理是什么
  4. classobjc_getClass泞辐、object_getclass 方法有什么區(qū)別?

Method Swizzle注意事項

  1. 需要注意的是交換方法實現(xiàn)后的副作用, method_exchangeImplementations().交換方法函數(shù)最終會以objc_msgSend()方式調(diào)用,副作用主要集中在第一個參數(shù) 如下示例
objc_msgSend(payment, @selector(quantity))

方法交換后再去調(diào)用quantity方法將有可能會crash.解決這種副作用的方式是使用method_setImplementation()來替換原來的交換方式,這樣才最為合理, 具體原理請參照 Objc 黑科技 - Method Swizzle 的一些注意事項

  1. 避免交換父類方法

    如果當前類沒有實現(xiàn)被交換的方法且父類實現(xiàn)了,此時父類的實現(xiàn)會被交換,若此父類的多個繼承者都在交換時會引起多次交換導致混亂,同時調(diào)用父類方法有可能因為找不到方法簽名而crash.
    所以交換前都應該check能否為當前類添加被交換的函數(shù)的新的實現(xiàn)IMP,這個過程大概分為3步驟

    • class_addMethod check能否添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

給類cls的SEL添加一個實現(xiàn)IMP, 返回YES則表明類cls并未實現(xiàn)此方法笔横,返回NO則表明類已實現(xiàn)了此方法。注意:添加成功與否咐吼,完全由該類本身來決定吹缔,與父類有無該方法無關(guān)。

  • class_replaceMethod 替換類cls的SEL的函數(shù)實現(xiàn)為imp
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                 const char * _Nullable types)

  • method_exchangeImplementations 最終方法交換
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)

  1. 交換方法應在+load方法

這個前面講消息轉(zhuǎn)發(fā)的時候講過,+load不是消息轉(zhuǎn)發(fā)的方式實現(xiàn)的且在運行時初始化過程中類被加載的時候調(diào)用,而且父類,當前類,category,子類等 都會調(diào)用一次.所以這里最適合寫方法交換的hook(Method Swizzle).

  1. 交換的分類方法應該添加自定義前綴,避免沖突

    這個毫無疑問,方法名稱一樣的時候會出現(xiàn),分類的方法會覆蓋類中同名的方法.

method swizzling你應該注意的點

屬性修飾符atomic的內(nèi)部實現(xiàn)是怎么樣的?能保證線程安全嗎?

atomic內(nèi)部實現(xiàn)

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    ...
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;  
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    return objc_autoreleaseReturnValue(value);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    ...
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
    objc_release(oldValue);
}

propertyatomic 是采用 spinlock_t自旋鎖實現(xiàn)的.

能保證線程安全嗎?

atomic通過這種方法.在運行時僅僅是保證了set,get方法的原子性.所以使用atomic并不能保證線程安全锯茄。

iOS 中內(nèi)省的幾個方法有哪些厢塘?內(nèi)部實現(xiàn)原理是什么?

首先要明白一個名詞 introspection 反省,內(nèi)省的意思,在iOS開發(fā)中我們會稱它為反射.

內(nèi)省方法 例如常用的NSObject中的isKindOfClass: 通過實例對象判斷class這就是一種內(nèi)省方法或者叫反射方法,但我認為NSClassFromString()這個應該也算一種反射方法.

iOS 中內(nèi)省的幾個方法

我們從NSObject.h中看下吧

- (BOOL)isKindOfClass:(Class)aClass; //判斷是否是這個類或者這個類的子類的實例
- (BOOL)isMemberOfClass:(Class)aClass; //判斷是否是這個類的實例
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;  //判斷是否遵守某個協(xié)議
+ (BOOL)conformsToProtocol:(Protocol *)protocol; //判斷某個類是否遵守某個協(xié)議
- (BOOL)respondsToSelector:(SEL)aSelector;  //判讀實例是否有這樣方法
+ (BOOL)instancesRespondToSelector:(SEL)aSelector; //判斷類是否有這個方法
...

內(nèi)部實現(xiàn)原理

1.isKindOfClass:

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); 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;
}

類方法是通過ISA()函數(shù)拿到指向元類的存儲isa指針數(shù)據(jù)的地址bit位按位與上相關(guān)掩碼的方式判斷當前是否是某個類的子類.
實例方法是通過objc_object::getIsa()函數(shù)通過存儲的tag_ext表形式拿到isa對于的class來取出class平check來實現(xiàn)的.

2.isMemberOfClass:

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

這倆方法非常簡單直接 拿到isa指針對比

3.conformsToProtocol:

+ (BOOL)conformsToProtocol:(Protocol *)protocol {
    if (!protocol) return NO;
    for (Class tcls = self; tcls; tcls = tcls->superclass) {
        if (class_conformsToProtocol(tcls, protocol)) return YES;
    }
    return NO;
}

- (BOOL)conformsToProtocol:(Protocol *)protocol {
    if (!protocol) return NO;
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (class_conformsToProtocol(tcls, protocol)) return YES;
    }
    return NO;
}

兩個方法最終還是去isa->data()->protocols 拿到相關(guān)協(xié)議然后判斷是否存在相關(guān)協(xié)議 如下代碼:

BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen)
{
    protocol_t *proto = newprotocol(proto_gen);  
    if (!cls) return NO;
    if (!proto_gen) return NO;
    mutex_locker_t lock(runtimeLock);
    checkIsKnownClass(cls);
    ASSERT(cls->isRealized())
    for (const auto& proto_ref : cls->data()->protocols) {
        protocol_t *p = remapProtocol(proto_ref);
        if (p == proto || protocol_conformsToProtocol_nolock(p, proto)) {
            return YES;
        }
    }
    return NO;
}

這里可以清晰的看到for循環(huán) 取出相關(guān)protocol指針 然后通過指針和傳入的參數(shù)生成的proto對比

4.respondsToSelector:

+ (BOOL)respondsToSelector:(SEL)sel {
    return class_respondsToSelector_inst(self, sel, self->ISA());
}

- (BOOL)respondsToSelector:(SEL)sel {
    return class_respondsToSelector_inst(self, sel, [self class]);
}

這個源碼比較麻煩 我簡單敘述一下吧 實際上調(diào)用棧比較深就是一直尋找到當前實例能響應哪些方法,當前類沒有就去父類,父類沒有則直到元類.

respondsToSelector:
    |__ class_respondsToSelector_inst()
        |__ lookUpImpOrNil()
            |__ lookUpImpOrForward()
                返回IMP結(jié)果

這就是整個消息轉(zhuǎn)發(fā)的過程 就不在這里贅述了.感興趣回看一下第二章 消息轉(zhuǎn)發(fā)部分

我上述列舉了一些常用的內(nèi)省方法,其它的都方法基本沒什么特別之處都是拿到isa各種操作內(nèi)部的獲取相關(guān)屬性的函數(shù)返回結(jié).

classobjc_getClass肌幽、object_getclass 方法有什么區(qū)別?

我用xcode隨便建了一個demo 打印一下viewcontrooller的內(nèi)容

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    Class cls1 = [self class];
    Class cls2 = object_getClass(cls1);
    Class cls3 = objc_getClass(object_getClassName([self class]));
    NSLog(@"%p",cls1);
    NSLog(@"%p",cls2);
    NSLog(@"%p",cls3);
}
@end

輸出

2020-08-31 16:15:48.150285+0800 ClassDemo[5582:55836] 0x10205b3b0
2020-08-31 16:15:48.150456+0800 ClassDemo[5582:55836] 0x10205b3d8
2020-08-31 16:15:48.150575+0800 ClassDemo[5582:55836] 0x10205b3b0

我簡單列舉了一張表格

class object_getclass() objc_getClass()
傳入?yún)?shù) N/a id類型 類名的字符串
操作對象 obj 這個id的isa指針所指向的Class 這個類的類對象
實例對象時 object_getclass()一致 class一致 N/a
類對象/元類對象時 返回的消息對象本身 返回的是下一個對象 N/a

原因:因為class返回的是self晚碾,而object_getClass返回的是isa指向的對象

總結(jié)

以上就是"一套高效的iOS面試題之runtime相關(guān)問題3"中的內(nèi)存剩余部分,問題答案雖然簡短 但是每道題都問的非常到位,值得一看!

推薦

  • 更多:iOS面試題大全
  • 更多:《BAT面試答案文集.PDF》喂急,獲取可加iOS技術(shù)交流圈:937194184迄薄。

收錄:原文地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市煮岁,隨后出現(xiàn)的幾起案子讥蔽,更是在濱河造成了極大的恐慌涣易,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冶伞,死亡現(xiàn)場離奇詭異新症,居然都是意外死亡,警方通過查閱死者的電腦和手機响禽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門徒爹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人芋类,你說我怎么就攤上這事隆嗅。” “怎么了侯繁?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵胖喳,是天一觀的道長。 經(jīng)常有香客問我贮竟,道長丽焊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任咕别,我火速辦了婚禮技健,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惰拱。我一直安慰自己雌贱,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布偿短。 她就那樣靜靜地躺著帽芽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翔冀。 梳的紋絲不亂的頭發(fā)上导街,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音纤子,去河邊找鬼搬瑰。 笑死,一個胖子當著我的面吹牛控硼,可吹牛的內(nèi)容都是我干的泽论。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼卡乾,長吁一口氣:“原來是場噩夢啊……” “哼翼悴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起幔妨,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鹦赎,失蹤者是張志新(化名)和其女友劉穎谍椅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體古话,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡雏吭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了陪踩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杖们。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肩狂,靈堂內(nèi)的尸體忽然破棺而出摘完,到底是詐尸還是另有隱情,我是刑警寧澤傻谁,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布孝治,位于F島的核電站,受9級特大地震影響栅螟,放射性物質(zhì)發(fā)生泄漏荆秦。R本人自食惡果不足惜篱竭,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一力图、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掺逼,春花似錦吃媒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至氯质,卻和暖如春募舟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闻察。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工拱礁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辕漂。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓呢灶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钉嘹。 傳聞我的和親對象是個殘疾皇子鸯乃,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354