2020 阿里、字节iOS面试题之Runtime相关问题3(附答案)

目录

runtime相关问题之内存部分的关联属性或者hook相关的Method Swizzle

经过前两期内容 我们这期来讲一下 内存部分的剩余问题 主要包含如下:

  1. Method Swizzle注意事项
  2. 属性修饰符atomic的内部实现是怎么样的?能保证线程安全吗
  3. iOS 中内省的几个方法有哪些?内部实现原理是什么
  4. classobjc_getClassobject_getclass 方法有什么区别?

Method Swizzle注意事项

  1. 需要注意的是交换方法实现后的副作用, method_exchangeImplementations().交换方法函数最终会以objc_msgSend()方式调用,副作用主要集中在第一个参数 如下示例
objc_msgSend(payment, @selector(quantity))

方法交换后再去调用quantity方法将有可能会crash.解决这种副作用的方式是使用method_setImplementation()来替换原来的交换方式,这样才最为合理, 具体原理请参照 Objc 黑科技 - Method Swizzle 的一些注意事项

  1. 避免交换父类方法

    如果当前类没有实现被交换的方法且父类实现了,此时父类的实现会被交换,若此父类的多个继承者都在交换时会引起多次交换导致混乱,同时调用父类方法有可能因为找不到方法签名而crash.
    所以交换前都应该check能否为当前类添加被交换的函数的新的实现IMP,这个过程大概分为3步骤

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

给类cls的SEL添加一个实现IMP, 返回YES则表明类cls并未实现此方法,返回NO则表明类已实现了此方法。注意:添加成功与否,完全由该类本身来决定,与父类有无该方法无关。

  • class_replaceMethod 替换类cls的SEL的函数实现为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方法

这个前面讲消息转发的时候讲过,+load不是消息转发的方式实现的且在运行时初始化过程中类被加载的时候调用,而且父类,当前类,category,子类等 都会调用一次.所以这里最适合写方法交换的hook(Method Swizzle).

  1. 交换的分类方法应该添加自定义前缀,避免冲突

    这个毫无疑问,方法名称一样的时候会出现,分类的方法会覆盖类中同名的方法.

method swizzling你应该注意的点

属性修饰符atomic的内部实现是怎么样的?能保证线程安全吗?

atomic内部实现

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自旋锁实现的.

能保证线程安全吗?

atomic通过这种方法.在运行时仅仅是保证了set,get方法的原子性.所以使用atomic并不能保证线程安全。

iOS 中内省的几个方法有哪些?内部实现原理是什么?

首先要明白一个名词 introspection 反省,内省的意思,在iOS开发中我们会称它为反射.

内省方法 例如常用的NSObject中的isKindOfClass: 通过实例对象判断class这就是一种内省方法或者叫反射方法,但我认为NSClassFromString()这个应该也算一种反射方法.

iOS 中内省的几个方法

我们从NSObject.h中看下吧

- (BOOL)isKindOfClass:(Class)aClass; //判断是否是这个类或者这个类的子类的实例
- (BOOL)isMemberOfClass:(Class)aClass; //判断是否是这个类的实例
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;  //判断是否遵守某个协议
+ (BOOL)conformsToProtocol:(Protocol *)protocol; //判断某个类是否遵守某个协议
- (BOOL)respondsToSelector:(SEL)aSelector;  //判读实例是否有这样方法
+ (BOOL)instancesRespondToSelector:(SEL)aSelector; //判断类是否有这个方法
...

内部实现原理

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()函数拿到指向元类的存储isa指针数据的地址bit位按位与上相关掩码的方式判断当前是否是某个类的子类.
实例方法是通过objc_object::getIsa()函数通过存储的tag_ext表形式拿到isa对于的class来取出class平check来实现的.

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 拿到相关协议然后判断是否存在相关协议 如下代码:

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循环 取出相关protocol指针 然后通过指针和传入的参数生成的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]);
}

这个源码比较麻烦 我简单叙述一下吧 实际上调用栈比较深就是一直寻找到当前实例能响应哪些方法,当前类没有就去父类,父类没有则直到元类.

respondsToSelector:
    |__ class_respondsToSelector_inst()
        |__ lookUpImpOrNil()
            |__ lookUpImpOrForward()
                返回IMP结果

这就是整个消息转发的过程 就不在这里赘述了.感兴趣回看一下第二章 消息转发部分

我上述列举了一些常用的内省方法,其它的都方法基本没什么特别之处都是拿到isa各种操作内部的获取相关属性的函数返回结.

classobjc_getClassobject_getclass 方法有什么区别?

我用xcode随便建了一个demo 打印一下viewcontrooller的内容

@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()
传入参数 N/a id类型 类名的字符串
操作对象 obj 这个id的isa指针所指向的Class 这个类的类对象
实例对象时 object_getclass()一致 class一致 N/a
类对象/元类对象时 返回的消息对象本身 返回的是下一个对象 N/a

原因:因为class返回的是self,而object_getClass返回的是isa指向的对象

总结

以上就是"一套高效的iOS面试题之runtime相关问题3"中的内存剩余部分,问题答案虽然简短 但是每道题都问的非常到位,值得一看!

推荐

收录:原文地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258