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

目錄

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

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

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

Method Swizzle注意事項(xiàng)

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

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

  1. 避免交換父類方法

    如果當(dāng)前類沒有實(shí)現(xiàn)被交換的方法且父類實(shí)現(xiàn)了,此時(shí)父類的實(shí)現(xiàn)會被交換,若此父類的多個(gè)繼承者都在交換時(shí)會引起多次交換導(dǎo)致混亂,同時(shí)調(diào)用父類方法有可能因?yàn)檎也坏椒椒ê灻鴆rash.
    所以交換前都應(yīng)該check能否為當(dāng)前類添加被交換的函數(shù)的新的實(shí)現(xiàn)IMP,這個(gè)過程大概分為3步驟

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

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

  • class_replaceMethod 替換類cls的SEL的函數(shù)實(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. 交換方法應(yīng)在+load方法

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

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

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

method swizzling你應(yīng)該注意的點(diǎn)

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

atomic內(nèi)部實(shí)現(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自旋鎖實(shí)現(xiàn)的.

能保證線程安全嗎?

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

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

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

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

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

我們從NSObject.h中看下吧

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

內(nèi)部實(shí)現(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)掩碼的方式判斷當(dāng)前是否是某個(gè)類的子類.
實(shí)例方法是通過objc_object::getIsa()函數(shù)通過存儲的tag_ext表形式拿到isa對于的class來取出class平check來實(shí)現(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;
}

兩個(gè)方法最終還是去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]);
}

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

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

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

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

class、objc_getClass、object_getclass 方法有什么區(qū)別?

我用xcode隨便建了一個(gè)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 這個(gè)id的isa指針?biāo)赶虻腃lass 這個(gè)類的類對象
實(shí)例對象時(shí) object_getclass()一致 class一致 N/a
類對象/元類對象時(shí) 返回的消息對象本身 返回的是下一個(gè)對象 N/a

原因:因?yàn)閏lass返回的是self,而object_getClass返回的是isa指向的對象

總結(jié)

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

推薦

收錄:原文地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容