面向对象
-
1.一个NSObject对象占用多少内存?
- 系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
- 但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
-
2.对象的isa指针指向哪里?
- instance对象的isa指向class对象
- class对象的isa指向meta-class对象
- meta-class对象的isa指向基类的meta-class对象
-
3.OC的类信息存放在哪里?
- 对象方法、属性、成员变量、协议信息,存放在class对象中
- 类方法,存放在meta-class对象中
- 成员变量的具体值,存放在instance对象
KVO
- 1.iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
- 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
- willChangeValueForKey:
- 父类原来的setter
- didChangeValueForKey:
- 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
- 2.如何手动触发KVO?
- 手动调用willChangeValueForKey:和didChangeValueForKey:
- 3.直接修改成员变量会触发KVO么?
- KVO的本质就是动态生成了一个隐形的子类,修改了set方法的调用如果直接通过指针修改是不会触发KVO的。
- 不会触发KVO
KVC
- 1.通过KVC修改属性会触发KVO么?
- 会触发KVO
- 2.KVC的赋值和取值过程是怎样的?原理是什么?
- 赋值
- 1.查找调用setKey:方法,没有则查找调用_setKey:找到了就传值调用方法
- 2.二个方法都没有就查找 accessInstanceVariableDirecity 看返回值是Yes还是No
- 3.Yes就按顺序查找是否有 _key _iskey key isKey 这些成员变量 有就给他们赋值
- 4.No 或者 没有对应的成员变量就报错说没有这样的成员变量
- 取值
- 1.查找调用getKey:、key:、isKey、_key、找到了就传值调用方法
- 2.四个方法都没有就查找 accessInstanceVariableDirecity 看返回值是Yes还是No
- 3.Yes就按顺序查找是否有 _key _iskey key isKey 这些成员变量 有就给取出值
- 4.No 或者 没有对应的成员变量就报错说没有这样的成员变量
- 赋值
Category
-
1.Category的使用场合是什么?
-
2.Category的实现原理
- Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
- 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
-
3.Category和Class Extension的区别是什么?
- Class Extension在编译的时候,它的数据就已经包含在类信息中
- Category是在运行时,才会将数据合并到类信息中
-
4.Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
- 有load方法
- load方法在runtime加载类、分类的时候调用 注册类的协议完成类之间的通信
- load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用
-
5.load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
- 1.调用方式区别
- load 是根据函数地址直接调用
- initialize 是通过onjc_msgSend调用
- 2.调用时刻区别
- load 是runtime加载类、分类的时候调用只会调用一次
- initialize 是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法会被调用多次)
- 1.load
- 1.先调用类的load
- 2.先编译的类,优先调用load
- 3.调用子类的load之前,会先调用父类的load
- 2.在调用父类的load
- 1.先编译的分类,优先调用load
- 3.initialize
- 先初始化父类
- 再初始化子类(可能最终调用父类的initlialize方法)
- 1.调用方式区别
-
6.Category能否添加成员变量?如果可以,如何给Category添加成员变量?
- 不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
Block
-
1.block的原理是怎样的?本质是什么?
- 封装了函数调用以及调用环境的OC对象,函数调用地址和函数调用的外部变量或外部变量地址
-
2.__block的作用是什么?有什么使用注意点?
- __block可以解决block内部无法修改外部auto变量值得问题,
- __block会把变量包装成一个结构体,
- 注意点:
- 不能修饰全局变量、静态变量
-
3.block的属性修饰词为什么是copy?使用block有哪些使用注意?
- block一旦没有进行copy操作,就不会在堆上。只有在堆上我们才能控制block的生命周期。
- 使用注意:循环引用问题
- 在ARC环境下 copy 和 strong没有区别。
- MRC下 copy会拷贝到堆上 strong不会
-
4.block在修改NSMutableArray,需不需要添加__block?
- 不需要 因为这个只是对 数组地址指针的使用,并没有修改。
Runtime
-
1.讲一下 OC 的消息机制
- OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
- objc_msgSend底层有3大阶段
- 消息发送(当前类、父类中查找)、动态方法解析、消息转发
-
2.消息转发机制流程
-
3.什么是Runtime?平时项目中有用过么?
-
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
-
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
-
平时编写的OC代码,底层都是转换成了Runtime API进行调用
-
具体应用
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题
- ......
-
-
4.打印结果分别是什么
- class 还是return object_getClass(self) 谁是消息接收者就返回谁
- superClass == return class_getSuperclass(object_getClass(self));
- MJStudent
- MJPerson
- MJStudent
- MJPerson
- -isMemberOfClass 判断实例对象的当前类是不是与传入的类的类型一致
- -isKindOfClass 判断实例对象的当前类是不是传入类一致或是子类
- +isMemberOfClass 判断类对象的当前元类是不是与传入的类的类型一致
- +isKindOfClass 判断类对象的当前元类是不是传入类一致或是子类
- 这样才是一致的
- 结果是1 这个坑点要注意一下
- 左边实例对象,右边就要是类对象
- 左边是类对象,右边就要是元类对象 -> 多一个注意点
[NSObject class]
-
5.以下代码能不能执行成功?如果可以,打印结果是什么?
- 能执行结果是 ViewController
- 内存结构示意图
RunLoop
-
1.讲讲 RunLoop,项目中有用到吗?
- 应用范畴
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
-
2.runloop内部实现逻辑?
- 01、通知Observers:进入Loop
- 02、通知Observers:即将处理Timers
- 03、通知Observers:即将处理Sources
- 04、处理Blocks
- 05、处理Source0(可能会再次处理Blocks)
- 06、如果存在Source1,就跳转到第8步
- 07、通知Observers:开始休眠(等待消息唤醒)
- 08、通知Observers:结束休眠(被某个消息唤醒)
- 01> 处理Timer
- 02> 处理GCD Async To Main Queue
- 03> 处理Source1
- 09、处理Blocks
- 10、根据前面的执行结果,决定如何操作
- 01> 回到第02步
- 02> 退出Loop
- 11、通知Observers:退出Loop
-
3.runloop和线程的关系?
-
每条线程都有唯一的一个与之对应的RunLoop对象
-
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
-
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
-
RunLoop会在线程结束时销毁
-
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
-
UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));这个我们可以理解为一开始创建主线程的时候是没有runloop的,但是他同时调用了[NSRunLoop currentRunLoop]主动获取(创建)了主线程的runloop。 -
-
4.timer 与 runloop 的关系?
-
5.程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
- 程序开启主线程中的runloop有两个默认模式 kCFRunLoopDefaultModel和UITrackingRunLoopMode
- Runloop的特性是模式互相隔离且只能在一种模式下运行
- 计时器加入运行是默认加入defaultModel的
- 当滑动tableView的时候runloop切换到了trackingModel了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
-
6.runloop 是怎么响应用户操作的, 具体流程是什么样的?
- source1捕捉系统事件,包装成事件队列EventQueue,放到source0中处理
-
7.说说runLoop的几种状态
- case kCFRunLoopEntry: 即将进入Loop
- case kCFRunLoopBeforeTimers: 即将处理Timer
- case kCFRunLoopBeforeSources: 即将处理Source
- case kCFRunLoopBeforeWaiting: 即将进入休眠
- case kCFRunLoopAfterWaiting: 刚从休眠中唤醒
- case kCFRunLoopExit: 即将推出loop
-
8.runloop的mode作用是什么?
- kCFRunLoopDefaultMode 默认模式
- UITrackingRunLoopMode:滑动模式
- 模式的作用是可以将不同的source0/source1/timer/observer隔离开互相不影响。保证不同模式之间互相不影响,保证运行的流畅度。比如滚动模式下,就不会运行默认模式下的事件,保证了滚动的流畅性。
多线程
-
1.你理解的多线程?
- 多条线程同时执行任务,类似多个人,依次取出队列中的任务去执行。
-
2.iOS的多线程方案有哪几种?你更倾向于哪一种?
-
3。你在项目中用过 GCD 吗?
-
4.GCD 的队列类型
-
5.说一下 OperationQueue 和 GCD 的区别,以及各自的优势
-
6.线程安全的处理手段有哪些?
-
7.OC你了解的锁有哪些?在你回答基础上进行二次提问;
- 追问一:自旋和互斥对比?
- 自旋锁不推荐使用
- 预计线程等待锁的时间很短
- 追问二:使用以上锁需要注意哪些?
- 追问三:用C/OC/C++,任选其一,实现自旋或互斥?口述即可!
- 什么情况使用自旋锁比较划算?临界区就是上锁与开锁之间的代码区域。
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
- CPU资源不紧张
- 多核处理器
- 什么情况使用互斥锁比较划算?
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操作 文件写入读取操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
- 追问一:自旋和互斥对比?