1、一个NSObject对象占用多少内存?
系统会分配16个字节(通过
malloc_size
函数获得),但实际只占用8个字节(通过class_getInstanceSize
获得)
补充1:结构体的内存大小,会根据内存对齐来定(是结构体最大成员的大小的倍数);对象的大小是以分配内存块的方式来决定的,内存块的大小都是16字节的倍数。
补充2:sizeof
是一个运算符,传入一个类型,返回类型所占的内存大小,它在编译过程中会当做一个常量;class_getInstanceSize
是一个函数,传入的参数是类对象。
2、对象的isa指针指向哪里?
- 实例对象(instance)的isa指向类对象(class)
- 类对象(class)的isa指向元类对象(meta-class)
- 元类对象(meta-class)的isa指向元类的基类
- 基类meta-class的isa指向自己
3、OC的类信息存放在哪里?
- 成员变量的值存放在实例对象中
- 实例方法、成员变量、属性、协议存放在类对象
- 类方法存放在元类对象
4、KVO的本质是什么?
当添加监听方法以后,被监听对象的isa指针会指向一个由Runtime动态生成的类:NSKVONotifying_ClassName,它是被监听类的子类;当被监听属性发生变化时,新生成子类的属性
setter
方法中会调用Foundation框架里的函数_NSSet*ValueAndNotify
,在该函数中会依次调用-willChangeValueForKey:
,父类的属性setter
方法,-didChangeValueForKey:
,在-didChangeValueForKey:
中会调用-observeValueForKeyPath:ofObject:change:context:
,完成对属性的监听。
5、如何手动触发KVO?
手动调用
-willChangeValueForKey:
和-didChangeValueForKey:
6、直接修改成员变量会触发KVO吗?
不会。因为不会调用
setter
方法。
7、通过KVC修改属性会触发KVO吗?
会。因为KVC修改属性会主动调用
-willChangeValueForKey:
和-didChangeValueForKey:
。
8、KVC的赋值和取值过程是怎样的?原理是什么?
- setValue:forKey原理
- valueForKey:原理
9、category的实现原理?
- category在编译之后的底层结构是
struct category_t
,里边存储着分类的对象方法、类方法、属性、协议信息- 在程序运行的时候,runtime会将category的数据,合并到类信息中(类对象、元类对象)
10、category和Class Extension的区别?
- Class Extension在编译的时候,它的数据就已经包含在类信息中了
- category是在运行时,才会将数据合并到类信息中
11、category中有load方法吗?load方法什么时候调用?load方法可以继承吗?
- 有
- 在runtime加载类、分类的时候调用
- 可以继承。一般情况下不会主动去调用load方法,都是让系统自动调用
12、load、initialize方法的区别是什么?
- 调用方式
- load是函数指针的方式;initialize是objc_msgSend方式
- 调用时机
- load是runtime加载类和分类数据时调用;initialize是类在第一次接受到消息时调用
- 调用顺序
- load是先调用类,再调用分类。调用类时,按照编译的先后顺序进行调用,如果有父类,则先调用父类load。调用分类时,也是按照编译的先后顺序进行调用
- initialize在调用时,如果子类没有实现,则会调用父类方法,因为是objc_msgSend调用方式,分类的initialize会覆盖类本身的initialize方法
13、category能否添加成员变量?
因为category底层结构的限制,不能直接添加成员变量。
可以通过关联对象的方式间接实现添加成员变量的效果。
14、block的本质是什么?
封装了函数调用以及函数调用环境的OC对象
15、__block的作用是什么?有什么注意点?
它修饰的变量可以在block内部进行修改,只能修饰auto变量,不能修饰static和全局变量
注意点:要合理使用,因为他会将修饰的变量包装到一个对象中
16、block的属性修饰词为什么是copy?使用block有哪些注意点?
不进行copy操作,它就不会在堆上
注意循环引用问题
17、block在修改NSMutableArray,需不需要使用__block?
不需要。因为它不是修改array指针,是使用它,类似对它进行打印输出
18、讲一下OC的消息发送机制?
- OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
- objc_msgSend底层有三大阶段
- 消息发送(当前类,父类中查找)、动态方法解析、消息转发
19、讲一下消息转发机制的流程?
当消息发送的前两个阶段(消息发送和动态方法解析)都没有实现的时候,就会来到消息转发阶段。首先会调用
forwardingTargetForSelector
,如果该方法返回值不为空,则转发流程结束,重新调用objc_msgSend(返回值,SEL)
;如果返回nil,则会进入消息签名方法(methodSignatureForSelector
),如果返回值为nil,整个消息发送流程结束,抛出异常;如果返回值不为空,则进入forwardInvocation
方法,在这里可以写任何方法实现代码。
20、说出下列代码的打印值?
void test()
{
// 方法调用者的isa指向的对象,或者指向对象的父类,如果有等于传入参数的类,返回YES
BOOL res1 = [[NSObject class] isKindOfClass:[NSObject class]];
// 方法调用者的isa指向的对象,如果等于传入参数的类,返回YES
BOOL res2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [[MJPerson class] isKindOfClass:[MJPerson class]];
// 这句代码的调用者不管是哪个类(只要是NSObject体系下的类),都会返回YES
BOOL res4 = [[MJPerson class] isKindOfClass:[NSObject class]]; // 基类的元类对象的父类指向基类的类对象
BOOL res5 = [[MJPerson class] isMemberOfClass:[MJPerson class]];
NSLog(@"res1 - %d,res2 - %d,res3 - %d,res4 - %d,res5 - %d",res1,res2,res3,res4,res5);
// res1 - 1,res2 - 0,res3 - 0,res4 - 1,res5 - 0
}
- 相关源码
+ (id)self {
return (id)self;
}
- (id)self {
return self;
}
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); 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;
}
+ (BOOL)isSubclassOfClass:(Class)cls {
for (Class tcls = self; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
21、以下代码能不能执行成功?如果可以,执行结果是什么?
可以执行。涉及的知识点:Class结构、消息发送机制、栈内存布局、super调用等。
22、什么是runtime?项目中有哪些用处?
- OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
- OC的动态性就是由runtime来支撑和实现,runtime是一套C语言的API,封装了很多动态性相关的函数
- 平时编写的OC代码,底层都是转换成了runtime API进行调用
- 具体应用
- 使用关联对象给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动解档归档等)
- 交换方法实现(一般是交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题
- ......
--Edited from Rpc