面试题的答案都是抛砖引玉,具体细节还得深刻了解iOS底层原理
复制代码
一、一个NSObject对象占用多少内存?
系统分配了16个字节给NSObject对象(经过malloc_size函数得到)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,能够经过class_getInstanceSize函数得到)
二、对象的isa指针指向哪里?
instance对象的isa指向class对象
class对象的isa指向meta-class对象
meta-class对象的isa指向基类的meta-class对象
三、OC的类信息存放在哪里?
对象方法、属性、成员变量、协议信息,存放在class对象中
类方法,存放在meta-class对象中
成员变量的具体值,存放在instance对象中
四、iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
利用RuntimeAPI动态生成一个子类,而且让instance对象的isa指向这个全新的子类
当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数 √ willChangeValueForKey: √ 父类原来的setter √ didChangeValueForKey: √ 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
五、如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:
六、直接修改为员变量或属性会触发KVO么?
七、KVC的赋值和取值过程是怎样的?原理是什么?
7.一、KVC赋值面试
// 1.1 建立人
PTLPerson *p = [[PTLPerson alloc] init];
self.person = p;
// 1.2 建立狗
PTLDog *dog = [[PTLDog alloc] init];
// 1.3 将狗赋值给人
[p set Value:dog for KeyPath:@"dog" ];
// 1.4 经过KVC给dog的weight属性赋值
赋值时会自动找到人拥有的dog的weight属性
[p set Value:@10.0 for KeyPath:@"dog.weight" ];
NSLog(@"books = %@" , [p valueForKeyPath:@"dog.weight" ]);
[dog print ];
复制代码
7.二、 KVC取值设计模式
NSMutableArray *tempM = [NSMutableArray array];
// 2.1 kvc取出出数组books中price的值
for (PTLBook *book in [p valueForKeyPath:@"books" ]) {
[tempM addObject:[book valueForKeyPath:@"price" ]];
}
NSLog(@"%@" , tempM);
// 2.2 kvc取出数组中price的最大值
NSLog(@"Max = %@" , [[p valueForKeyPath:@"books" ] valueForKeyPath:@"@max.price" ]);
复制代码
7.三、 原理数组
KVO 是 Objective-C 对观察者设计模式的一种实现,另一种是:通知机制(notification) KVO提供一种机制,指定一个被观察对象(例如A类),当对象某个属性(例如A中的字符串name)发生更改时,对象会得到通知,并做出相应处理 在MVC设计架构下的项目,KVO机制很适合实现mode模型和controller之间的通信。 例如:代码中,在模型类A建立属性数据,在控制器中建立观察者,一旦属性数据发生改变就收到观察者收到通知,经过KVO再在控制器使用回调方法处理实现视图B的更新;(本文中的应用就是这样的例子.)bash
KVO在Apple中的API文档以下: Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class … KVO 的实现依赖于 Objective-C 强大的 Runtime【 ,从以上Apple 的文档能够看出苹果对于KVO机制的实现是一笔带过,而具体的细节没有过多的描述,可是咱们能够经过Runtime的所提供的方法去探索关于KVO机制的底层实现原理.架构
当观察某对象 A 时,KVO 机制动态建立一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变情况。函数
Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态建立一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法以前和以后,通知全部观察对象属性值的更改状况。优化
NSKVONotifying_A类剖析:在这个过程,被观察对象的 isa 指针从指向原来的A类,被KVO机制修改成指向系统新建立的子类 NSKVONotifying_A类,来实现当前类属性值改变的监听; 因此当咱们从应用层面上看来,彻底没有意识到有新的类出现,这是系统“隐瞒”了对KVO的底层实现过程,让咱们误觉得仍是原来的类。可是此时若是咱们建立一个新的名为“NSKVONotifying_A”的类(),就会发现系统运行到注册KVO的那段代码时程序就崩溃,由于系统在注册监听的时候动态建立了名为NSKVONotifying_A的中间类,并指向这个中间类了。 于是在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值通知机制。ui
子类setter方法剖析:KVO的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的先后分别调用2个方法: 被观察属性发生改变以前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变动;当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath 的属性值已经变动; 以后observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的setter 方法这种继承方式的注入是在运行时而不是编译时实现的。 KVO为子类的观察者属性重写调用存取方法的工做原理在代码中至关于:spa
-(void)set Name:(NSString *)newName
{
[self willChangeValueForKey:@"name" ]; //KVO在调用存取方法以前总调用
[super set Value:newName for Key:@"name" ]; //调用父类的存取方法
[self didChangeValueForKey:@"name" ]; //KVO在调用存取方法以后总调用
}
复制代码
八、Category的实现原理?
Category编译以后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
九、Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
十、Category中有load方法吗?load方法是何时调用的?load 方法能继承吗?
有load方法
load方法在runtime加载类、分类的时候调用
load方法能够继承,可是通常状况下不会主动去调用load方法,都是让系统自动调用
十一、+load、+initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
+load
+load方法会在runtime加载类、分类时调用
每一个类、分类的+load,在程序运行过程当中只调用一次
调用顺序:
一、先调用类的+load √ 按照编译前后顺序调用(先编译,先调用) √ 调用子类的+load以前会先调用父类的+load
二、再调用分类的+load √ 按照编译前后顺序调用(先编译,先调用)
+initialize
+initialize方法会在类第一次接收到消息时调用
调用顺序 一、先调用父类的+initialize,再调用子类的+initialize 二、(先初始化父类,再初始化子类,每一个类只会初始化1次)
+initialize和+load的很大区别是,+initialize是经过objc_msgSend进行调用的,因此有如下特色 √ 若是子类没有实现+initialize,会调用父类的+initialize(因此父类的+initialize可能会被调用屡次) √ 若是分类实现了+initialize,就覆盖类自己的+initialize调用
十二、Category可否添加成员变量?若是能够,如何给Category添加成员变量?
默认状况下,由于分类底层结构的限制,不能添加成员变量到分类中。但能够经过关联对象来间接实现
1三、block的原理是怎样的?本质是什么?
block本质上也是一个OC对象,它内部也有个isa指针
封装了函数调用以及调用环境的OC对象
1四、__block的做用是什么?
__block说明符相似static、auto、register同样,只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。 进而在block内部也能够修改外部变量的值。
__block能够用于解决block内部没法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
1五、block的属性修饰词为何是copy?使用block有哪些使用注意?
block一旦没有进行copy操做,就不会在堆上
使用注意:循环引用问题
1六、block在修改NSMutableArray,需不须要添加__block?
不须要
当变量是一个指针的时候,block里只是复制了一份这个指针,两个指针指向同一个地址。因此,在block里面对指针指向内容作的修改,在block外面也同样生效。
1七、说说isa指针?
instance的isa指向class,当调用对象方法时,经过instance的isa找到class,最后找到对象方法的实现进行调用
class的isa指向meta-class,当调用类方法时,经过class的isa找到meta-class,最后找到类方法的实现进行调用
当Student的instance对象要调用Person的对象方法时(Student继承自Person),会先经过isa找到Student的class,而后经过superclass找到Person的class,最后找到对象方法的实现进行调用
当Student的class要调用Person的类方法时(Student继承自Person),会先经过isa找到Student的meta-class,而后经过superclass找到Person的meta-class,最后找到类方法的实现进行调用
在arm64架构以前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
从64bit开始,isa须要进行一次位运算,才能计算出真实地址
1八、isa、superclass总结
instance的isa指向class
class的isa指向meta-class
meta-class的isa指向基类的meta-class
class的superclass指向父类的class 若是没有父类,superclass指针为nil
meta-class的superclass指向父类的meta-class 基类的meta-class的superclass指向基类的class
instance调用对象方法的轨迹 isa找到class,方法不存在,就经过superclass找父类
class调用类方法的轨迹 isa找meta-class,方法不存在,就经过superclass找父类