likes
comments
collection
share

iOS 底层原理探索 之 一份试卷

作者站长头像
站长
· 阅读数 19
写在前面: iOS底层原理探索的阶段总结是对于底层探索的总结内容,
篇章不会太多,主要是对于探索中的细节总结。 希望对大家能有帮助。

总结来自以下专栏内容


从一份试卷开始

选择题

  • 1、 在LP64下,一个指针有多少字节?
  • A、4
  • B、8
  • C、16
  • D、64
B
  • 2、 一个实例对象的内存结构存在哪些元素?
  • A、成员变量
  • B、superClass
  • C、cache_t
  • D、bit
A
实例对象的大小是由其成员变量来决定的;
在这里不要和类的内存结构混淆了,
类的内存结构中,才会包含superClass、cache_t和bit的数据。
  • 3、 下面图片中 sizeof(struct3)大小等于
struct LGStruct1 {
    char b;
    int c;
    double a;
    short d;
}

struct LGStruct2 {
    double a;
    int b;
    char c;
    short d;
}

struct LGStruct2 {
    double a;
    int b;
    char c;
    struct LGStruct1 str1;
    short d;
    int e;
    struct LGStruct2 str2;
}
  • A、48
  • B、56
  • C、64
  • D、72
C
这是一道基础题,需要耐心。在这里给大家介绍一个方法 整体法。
在 LGStruct1 结构体中,可以看到 有一个 double8字节;
那么意味着逢double 8字节 就归零,也就是说前面的 char int8字节,
double8字节, short2字节,共 18 字节;
在结合内存对齐结构体大小必须是内部最大变量的整数倍 所以就是 24 字节;

同样的, 在 LGStruct2 结构体中,double8 字节, int 4 ,
char 1 short 2 ,对齐一下 16 字节;

最后, LGStruct3 : double 8int + char 8, str1 24
short + int 8, str2 16, 共 64字节。

这里在贴一下内存对齐原则:
1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第 一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置 要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是 数组,结构体等)的整数倍开始(比如int4字节,则要从4的整数倍地址 开始存储。
2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从 其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b, b里有char,int ,double等元素,那b应该从8的整数倍开始存储) 
3:收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员 的整数倍,不足的要补⻬。
  • 4、 下列代码: re1 re2 re3 re4 re5 re6 re7 re8 输出结果
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
    BOOL re3 = [(id)[SMPerson class] isKindOfClass:[SMPerson class]];       //
    BOOL re4 = [(id)[SMPerson class] isMemberOfClass:[SMPerson class]];     //
    NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
    BOOL re7 = [(id)[SMPerson alloc] isKindOfClass:[SMPerson class]];       //
    BOOL re8 = [(id)[SMPerson alloc] isMemberOfClass:[SMPerson class]];     //
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
  • A、1011 1111
  • B、1100 1011
  • C、1000 1111
  • D、1101 1111
C

源码如下:
+ (BOOL)isKindOfClass:(Class)cls { 
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES; 
    }
    return NO; 
}

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

1、[(id)[NSObject class] isKindOfClass:[NSObject class]];     
-  获取`NSObject`类的元类,此时`tcls`是NSObjectMateClass,`cls`是NSObjectClass,所以不相等;
-  `for`循环,`tcls`等于根元类的父类 NSObjectClass,也就是根类,相等;
-  在循环的第二次判断是返回`YES`;
-  最终结果为`YES`。

2、[(id)[NSObject class] isMemberOfClass:[NSObject class]];
- 根元类 NSObjectMateClass和根类NSObjectClass比较 返回 NO

3、[(id)[SMPerson class] isKindOfClass:[SMPerson class]];     - 获取 SMperson 的元类,此时 tcls 是 SMPersonMateClass, cls = SMPersonClass 不相等; 
- for 循环, tcls = SMPersonMateClass的父类 SMPersonSuperClassMateClass, 和 SMPersonClass 不相等;
- for 循环  tcls = SMPersonSuperClassMateClass的父类,也就是 NSOBjectMateClass, 和 SMPersonClass 不相等;
- for 循环 tcls = NSObjectMateClass的父类,也就是 NSObjectClass,和 SMPersonClass 不相等;
- for循环 tcls = NSObjectClass 的父类,也就是nil; 退出循环 返回 NO

4、[(id)[SMPerson class] isMemberOfClass:[SMPerson class]];
- SMPersonMateClass 不等于 SMPersonClass 返回 NO

5、[(id)[NSObject alloc] isKindOfClass:[NSObject class]];     
- NSObjectClassNSObjectClass相等 返回 YES

6、[(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
- NSObjectClassNSObjectClass相等 返回 YES

7、[(id)[SMPerson alloc] isKindOfClass:[SMPerson class]];     -  SMPersonClass 和SMPersonClass相等 返回 YES
    
8、[(id)[SMPerson alloc] isMemberOfClass:[SMPerson class]];  
- SMPersonClass 和 SMPersonClass相等 返回 YES

总结: 
isKindOfClass: 会先通过获取方法调用者的ISA找到类或者元类 tcls,
然后进行一次和cls的比较,相同则返回YES
不同则:顺着tcls的继承链一直找下去匹配上了返回YES
否则,直到NSObject的父类为nil则返回NO

isMemberOfClass:只是通过方法调用者的ISA找到类或者元类,然后和cls比较,直接返回比较多结果

  • 5、 (x+7)&~7这个算法是几字节对齐
  • A、7
  • B、8
  • C、14
  • D、16
B
if x=0001 那么,x+7 = 8 = 1000 ; ~7 = 1000
8 & ~7 = 1000 & 1000 = 1000;
所以即使是1都会输出升到8,所以是 8 字节对齐。
  • 6、 判断下列数据结构大小
   union kc_t {
       unitptr_t bits;
       struct {
           int a;
           char b;
       }
   }
  • A、8
  • B、12
  • C、13
  • D、16
A
kc_t 是一个联合体,
unitptr_t8 字节, struct 也是 8 字节, 但是并不是相加,
因为 联合体内部数据 是一个互斥的关系。
  • 7、 元类的 isa 指向谁, 根元类的父类是谁

  • A、自己 , 根元类

  • B、自己 , NSObject

  • C、根元类 , 根元类

  • D、根元类 , NSObject

B D
这道题 需了解 isa 的走位图

iOS 底层原理探索 之 一份试卷

  • 8、 查找方法缓存的时候发现是乱序的, 为什么? 哈希冲突怎么解决的
  • A、: 哈希函数原因 , 不解决
  • B、: 哈希函数原因 , 再哈希
  • C、: 他存他的我也布吉岛 , 再哈希
  • D、: 他乱由他乱,清风过山岗 , 不解决
B
  • 9、 消息的流程是
  • A、: 先从缓存快速查找
  • B、: 慢速递归查找methodlist (自己的和父类的,直到父类为nil)
  • C、: 动态方法决议
  • D、: 消息转发流程
A B C D
  • 10、 类方法动态方法决议为什么在后面还要实现 resolveInstanceMethod

  • A、: 类方法存在元类(以对象方法形式存在), 元类的父类最终是 NSObject 所以我们可以通过resolveInstanceMethod 防止 NSObject 中实现了对象方法!

  • B、: 因为在oc的底层最终还是对象方法存在

  • C、: 类方法存在元类以对象方法形式存在.

  • D、: 咸吃萝卜,淡操心! 苹果瞎写的 不用管

A

判断题

  • 11、光凭我们的对象地址,无法确认对象是否存在关联对象 -- ❌
isa 底层结构中 第二位 `has_assoc`:关联对象标志位,0没有,1存在
  • 12、int c[4] = {1,2,3,4}; int *d = c; c[2] = *(d+2) -- ✅
  • 13、@interface LGPerson : NSObject{ UIButton *btn } 其中 btn 是实例变量 -- ✅
  • 14、NSObject 除外 元类的父类 = 父类的元类 -- ✅
  • 15、对象的地址就是内存元素的首地址 -- ✅
  • 16、类也是对象 -- ✅

简答题

  • 17、 怎么将上层OC代码还原成C++代码(2种方式)
1、 clang -rewrite-objc xxx.m -o xxxx.cpp
2、 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxxx.cpp
  • 18、 怎么打开汇编查看流程,有什么好处 ?
1、通过 Xcode-debug-debug workflow-always show disassembly 看汇编
2、查看汇编可以从更底层了解当前函数的汇编层面的执行,为objc源码分析提供
信息避免方向性错误,结合 memory read 可以更清楚的看到寄存器之间是如何
互相配合处理的;使用汇编查看流程可以在不确定源码出处和执行流程的情况下,
跟踪内部代码,并可以找到出处!同时,结合下符号断点的方式,能够更清晰的跟
踪源码实现。
  • 19、 x/4gx和p/x以及p*$0代表什么意思 ?
x/4gx : 输出一段内存地址,以8字节的形式输出4端

p/x : 输出一个数据结构的首地址

P *$0$0 为指向某一个数据空间的指针,而该指令输出的是该数据的数据结构 
  • 20、 类方法存在哪里?为什么要这么设计 ?
对象方法存储在类中,类方法存储在元类中;
对象方法是由类实例化出来的,类是由元类实例化出来的;
这样设计由以下3个方面:
1、底层不用对类方法和对象方法做区分,本质上都是对象方法,方法调用都可以理解为消息发送,只不过消息的接受者即方法查找对象不一致;
2、这样设计更加的基于对象,类的一切信息都存储在元类中,对象的一切信息都存储阿紫啊类中,类是元类的实例化对象,对象是类的实例化对象,存储也更符合面向对象的特点;
3、OC语言设计早期借鉴了另一种 smalltalk 的语言,samlltalk 中也是这么设计的。
  • 21、 方法慢速查找过程中的二分查找流程,请用伪代码实现。
二分查找的前提是数据必须有序排列;2、不断的找起始位置(base)和有效数据
量(count);3、当前目标位置 = 起始位置(base) + 有效数据量
(count)/ 24、查找对应位置之后不断的向前偏移,主要是为了找到第一个
符合条件的数据,即找到最前面的category中满足条件的方法。
  • 22、 ISA_MASK = 0x00007ffffffffff8ULL 那么这个 ISA_MASK 的算法意义是 什么 ?
这个算法主要是为了得到isa中存储的class信息,大部分的isa都是不纯的isa,
是一个倡导64gebyte为的联合体位域数据,而存储class信息的部分只有其中的
部分byte位置,剩下的位置存储了其他的信息;读取的时候必须把其他无效位置的
byte位数据给遮盖住,所以需要使用 isa_maske 进行按位与操作,都只会保留 
is_maske 对应 byte 位置的数据,这个算法的意义就是将无用的数据去掉,这
样就能让isa中存储更多的信息。
  • 23、 类的结构里面为什么会有rw和ro以及rwe ?
ro 是只读, 属于 clean memory ,在编译期即确定的内存空间,加载后不会
改变内容的空间;
rw 可读可写, 属于 dirty memory,是运行时结构,可以向类中添加属性、方
法等,在运行时会改变的内存;
rwe 是将rw中不常用的部分拆分出来 , 是为了节省内存。因为只有很少的类会
真正的改变了他们的方法。
运行时, 如果需要动态向类中添加方法协议等,会创建rwe,并将ro的数据优先
attache到rwe中,在读取时会优先返回rwe到数据,如果rwe没有被初始化,则返
回ro到数据。

rw 中包括 ro 和 rwe,其中 rw 是 dirtymemory,ro 是 clean memory;为
了让 dirty memory 占用更少的空间,把 rw 中可变的部分抽取出来为 rwe;
clean memory 越多越好,dirty memory 越少越好,因为 iOS 系统底层虚内
存机制的原 因,内存不足时会把一部分内存回收掉,后面需要再次使用时从硬盘中
加载出来即 swap 机制,clean memory 是可以从硬盘中重新加载的内存,iOS 
中的 macho 文件动态 库都属于此类型;dirty memory 是运行时产生的数据,
这部分数据不能从硬盘中重新 加载所以必须一直占据内存,当系统物理内存紧张的
时候,会回收掉 clean memory 内 存,如果 dirty memory 过大则直接会被回
收掉;所以 clean memory 越多越好,dirty memory 越少越好;苹果对 rw、
ro、rwe 进行这么细致的划分都是为了能更好更细致 的区别 cleanmemory 和 
dirty memory;
  • 24、 cache 在什么时候开始扩容 , 为什么 ?
1、一般情况下,如果当前方法cache之后,缓存的使用容量会超过总容量的3/4,
那么此时就不会先插入,而是现出发扩容操作,扩容为原来的2倍,然后再插入本次
的方法;

2、某些特殊预处理宏定义编译命令下,首次会存储满之后再开始扩容;

3、扩容时选用3/4作为负载因子是和hash表底层使用的链表以及红黑树的数据结
构有关,0.75是最符合波松分布概率计算得出的数值,在这个数值下哈希表的空间
和时间效率都是最高的。
  • 25、objc_msgSend 为什么用汇编写 , objc_msgSend 是如何递归找到 imp ?
1、使用汇编响应速度快;
2、这个过程中使用了两个循环;
循环1:通过前面获取的mask,与要查找的_cmd进行hash运算,获取下标,
从而得到_cmd对应的bucket地址,然后进行向前平移查找,每次平移16个字节,
如果找到对应的sel,则caheHit;当平移到buckets到首地址,依然没有查找
到,则进入第二个循环;
循环2:首先会获取末尾bucket到地址,同样采用向前查找到方式,向_cmd对应的
地址进行平移查找。
  • 26、一个类的类方法没有实现为什么可以调用 NSObject 同名对象方法 ?
这里设计到isa和superclass的走位以及方法查找到逻辑。
首先类的isa指向元类,在方法快速查找时,会根据类的isa找到元类,
元类中没有改方法就会走到lookUpImpOrForward慢速查找流程中。
在慢速查找流程中,会进行for递归查找,根据superclass查找到元类的父类,也就是根源类,而根元类的superclass指向类NSObject,所以会调用到
NSObject的同名对象方法。
转载自:https://juejin.cn/post/6988027474768560164
评论
请登录