-
一个NSObject对象占用多少内存?
- 系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
- 但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得),NSObject对象只有一个isa指针,这8个字节就是用来存放isa这个成员变量
-
对象的isa指针指向哪里?
- instance对象的isa指向class对象
- class对象的isa指向meta-class对象
- meta-class对象的isa指向基类的meta-class对象,基类的meta-class对象的isa指向自身
-
OC的类信息存放在哪里?
- 实例方法、属性信息、成员变量信息、协议信息都存放在class对象中
- 类方法存放在meta-class对象中
- 成员变量的具体值存放在instance对象中
-
iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么)
- 使用RuntimeAPI动态生成一个子类(NSKVONotifying_XXX),并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的这个属性时(调用这个属性的setter方法),会调用Foundation的_NSSetXXXValueAndNotify函数
- -willChangeValueForKey:
- 记录旧值,触发之后的didChangeValueForKey
- 原本父类的setter方法
- 赋值
- -didChangeValueForKey:
- 内部触发监听器Observer的监听方法(-observeValueForKeyPath:ofObject:change:context:)
- -willChangeValueForKey:
- PS:直接监听成员变量不起效,除非有成员变量的setXXX:方法,因为生成的子类要重写这个方法来进行监听 // 直接修改不会触发KVO
- self.per1->_height += 1;
- // 这样才会触发KVO,说明NSKVONotifying_XXX内部重写的是setXXX:方法
- [self.per1 setHeight:10];
-
如何手动触发KVO
- 手动调用-willChangeValueForKey:方法和-didChangeValueForKey:方法
- 必须先-willChangeValueForKey:后-didChangeValueForKey:,且缺一不可
-
直接修改成员变量会触发KVO吗?
- 不会触发KVO,调用setter方法才会触发KVO,因为KVO生成的子类内部重写的是这个属性的setter方法
-
Category的实现原理。
- Category编译之后的底层结构是struct category_t,里面存储着分类的实例方法、类方法、属性、协议信息
- 在程序运行的时候,Runtime会将Category的数据,合并附加到类信息中(class对象、meta-class对象)
- PS1:Runtime将分类的方法放在原来的方法的前面,因此分类的方法优先级更高,所以如果有重名的方法,会优先调用分类的方法
- PS2:如果多个分类有重名的方法,【后】编译的分类的方法优先级更高,因为Runtime里面的方法列表附加操作的顺序是下标从大到小进行的,越往后越靠前 · PS3:在 Tatgets -> Build Phases -> Compile Sources 中可以控制编译顺序,编译是从上到下的顺序,想调用的优先级高的就往下放
-
Category和Class Extension的区别是什么?
- Class Extension在编译的时候,它的数据就已经包含在类信息中
- Category是在运行时,才会将数据合并到类信息中
-
Category中有load方法吗?load方法是什么时候调用的?load方法能继承吗?
- 有。
- load方法在Runtime加载类、分类的时候调用
- load方法可以继承,在子类没有重写load方法,主动去调用load方法(消息发送机制)时会调用父类的load方法,说明是有继承关系的,但是一般情况下不会主动去调动load方法,都是让系统自动调用(在Runtime加载时是直接拿到load方法的地址去调用,之后手动调用时其实就是利用消息发送机制:子类没有load方法就会去父类的方法列表里面找)
-
load、initialize方法的区别是什么?它们在Category中的调用的顺序?以及出现继承时它们之间的调用过程?
- 区别:
- 调用方式
- load是根据函数地址直接调用
- initialize是通过objc_msgSend调用
- 调用时刻
- load是Runtime加载(所有参与编译的)类、分类的时候调用,只会调用1次
- initialize是类第一次接收到消息的时候调用,每一个类只会调用initialize一次,但父类的initialize方法可能会被调用多次(例如:子类和子类的分类都没有实现initialize方法时就会去调用父类的initialize方法)
- 调用方式
- 调用顺序
- load:
- 先调用类的load
- 先编译的类,优先调用load
- 调用子类的load之前,会先调用父类的load
- 再调用分类的load
- 先编译的分类,优先调用load
- 先调用类的load
- initialize:
- 调用父类的initialize(初始化)
- 再调用子类的initialize(初始化)
- 可能最终调用的是父类的initialize(子类没有实现initialize)
- load:
- 区别:
-
Category能否添加成员变量?如果可以,如何给Category添加成员变量?
- 不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果(关联对象)。
-
通过KVC修改属性会触发KVO么?
- 会触发
- 通过-_isKVOA方法判定是否有监听器(_isKVOA为KVO生成的NSKVONotifying_XXX的方法)
- 内部实现:
- [per willChangeValueForKey:@"age"]; // 保存旧值,标识等会调用didChangeValueForKey
- 执行setter方法,没有则直接对成员变量赋值:per->_age = 20;
- [per didChangeValueForKey:@"age"]; // 通知监听器,XX属性值发送了改变
- 会触发
-
KVC的赋值和取值过程是怎样的?原理是什么?
- 赋值 -setValue:forKey: 的过程:
- 按照优先级为 -setKey: 、-_setKey: 的顺序查找方法
- 找到:传递参数,调用方法
- 找不到,查看 +accessInstanceVariablesDirectly 方法是否为YES
- NO,不允许:抛出NSUnknownKeyException异常
- YES,允许,按照优先级为 _key、_isKey、key、isKey 的顺序查找成员变量
- 找到:直接赋值
- 找不到:抛出NSUnknownKeyException异常
- 按照优先级为 -setKey: 、-_setKey: 的顺序查找方法
- 取值 -valueForKey: 的过程:
- 按照优先级为 -getKey 、-key、-isKey、-_key 的顺序查找方法
- 找到:调用方法,取值
- 找不到,查看 +accessInstanceVariablesDirectly 方法是否为YES
- NO,不允许:抛出NSUnknownKeyException异常
- YES,允许,按照优先级为 _key、_isKey、key、isKey 的顺序查找成员变量
- 找到:直接取值
- 找不到:抛出NSUnknownKeyException异常
- 按照优先级为 -getKey 、-key、-isKey、-_key 的顺序查找方法
- PS:+accessInstanceVariablesDirectly:是否允许访问成员变量,默认为YES
- 赋值 -setValue:forKey: 的过程:
-
block的原理是怎样的?本质是什么?
- 本质就是一个封装了函数调用(impl.FuncPtr)以及函数调用环境(函数需要的参数)的OC对象(impl.isa)
-
__block的作用是什么?有什么使用注意点?
- 被__block修饰的变量会被包装成另一种对象,可以用于解决block内部无法修改auto变量值的问题,先通过这个对象再通过__forwarding指针就可以访问到那个变量进行修改
- 注意内存管理的问题,如果修饰的是对象,block拷贝到堆上时,在ARC环境下会对对象做retain操作,而MRC环境下则不会。
-
block的属性修饰词为什么是copy?使用block有哪些使用注意?
- block一旦没有进行copy操作就不会在堆上,拷贝在堆上是为了控制block的生命周期,进行内存管理
- 使用注意:循环引用
-
block在修改NSMutableArray,需不需要添加__block?
- 不需要,NSMutableArray有相应的api来修改数组内容,这是直接使用这个变量,而不是修改这个变量,所以不需要__block修饰,
- __block能不加就尽量不加,加了数据结构就变得复杂,会生成一个额外的结构体
-
讲一下OC的消息机制
- OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送一条消息(selector方法名)
- objc_msgSend底层有3大阶段:
- 消息发送(当前类、父类中查找)
- 动态方法解析(resolveInstanceMethod、resolveClassMethod)
- 消息转发
-
消息转发机制流程
- 调用forwardingTargetForSelector:方法,返回转发对象
- 返回值不为nil,将消息转发给别人
- objc_msgSend(转发对象, SEL);
- 返回值为nil
- 调用methodSignatureForSelector:方法,返回方法签名(返回值和参数信息)
- 返回值不为nil
- 调用forwardInvocation:方法 --> 根据方法签名包装成一个NSInvocation给到方法中让我们使用,自定义任何逻辑
- 返回值为nil
- 调用doseNotRecognizeSelector:方法 --> 报错
- 返回值不为nil
- 调用methodSignatureForSelector:方法,返回方法签名(返回值和参数信息)
- 返回值不为nil,将消息转发给别人
- 调用forwardingTargetForSelector:方法,返回转发对象
-
什么是Runtime?平时项目中有用过么?
- Runtime:
- OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
- OC的动态性就是由Runtime来支撑和实现,Runtime是一套C语言的API,封装了很多动态性相关的函数。
- 平时编写的OC代码,底层都是转换成了Runtime API进行调用
- 平时项目中用到的地方:
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(可以访问私有成员变量):修改私有成员变量、字典转模型、自动归档解档
- 交换方法实现:交换系统的方法
- 利用消息转发机制解决方法找不到的异常问题
- 可以在程序运行的过程中动态生成新的类(KVO)
- Runtime:
-
你理解的多线程?
- 多条线程同时工作,充分利用设备的多核,提高运行效率
-
iOS的多线程方案有哪几种?你更倾向于哪一种
- pthread
- NSThread
- GCD(倾向)
- NSOperation(倾向)
-
你在项目中用过GCD吗?
- 有,用于开启多线程处理复杂任务、线程同步、读写安全、延时任务、一次性任务、快速迭代等
-
说一下NSOperationQueue和GCD的区别,以及各自的优势
- NSOperationQueue
- 是OC对象,使用起来更加面向对象,底层是用GCD实现,其实就是对GCD的封装
- 加入队列的任务可以随时取消执行(已经开始的任务无法停止)
- 即使是在并发队列当中,同个队列里的不同任务之间可以设置依赖关系
- 可以简单设置任务的优先级,使得在并发队列中的任务也能区分先后地执行
- 可以监听任务的执行状态
- 可继承,定制自由度高
- GCD
- 底层的C语言构成的API,轻量级的数据结构,性能比NSOperationQueue高
- 加入队列的任务可以无法停止执行
- 同个队列的不同的任务之间不可以设置依赖关系
- 只能区分队列的优先级,区分任务的优先级需要复杂代码
- 只能手动写代码监听执行状态
- 无法定制别的功能
- NSOperationQueue
-
你对 iOS 内存管理的理解
- 使用引用计数来管理OC对象的内存
- 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
- 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
- 内存管理的经验总结:
- 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
- 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
-
ARC 都帮我们做了什么? · ARC是【LLVM编译器】和【Runtime系统】相互协助的一个结果 - LLVM:例如在某个作用域的{}即将结束的时候,自动对里面的对象调用release方法 - Runtime:例如weak指针的实现,在程序运行的过程中,监控到对象要销毁时会去清空对象的弱引用
-
autorelease对象在什么时机会被调用release
- 如果是自定义的autoreleasepool,会在自动释放池的{}结束前一刻调用release
- 如果不是在自定义的autoreleasepool,而是在main函数的autoreleasepool,是由RunLoop控制的,可能是在某次RunLoop循环中,在RunLoop休眠之前调用了release(kCFRunLoopBeforeWaiting)
-
方法里有局部对象, 出了方法后会立即释放吗
- 【MRC】环境下不会,会等RunLoop那次循环的休眠之前才释放
- 【ARC】环境下会,LLVM会在方法的{}即将结束的时候,自动对里面的对象调用release方法
-
你在项目中是怎么优化内存的?
- 减少类的创建,多复用
- 尽量用轻量级的对象
- 图片不能过大,尽可能少用图片
- 少用定时器
- 控制线程的最大并发数量
- 减少、压缩网络数据
-
列表卡顿的原因可能有哪些?你平时是怎么优化的?
- 使用MVVM模式进行开发,ViewModel在子线程获取Model的同时也将视图的UI属性都提前计算好,再回到主线程刷新,尽可能减少在滚动的时候的计算操作
- 图片圆角不使用layer.masksToBounds = YES、layer.cornerRadius大于0的方式设置,通过CoreGraphics绘制裁剪圆角,或者叫美工提供圆角图片
- 要使用阴影就必须也设置layer.shadowPath
-
遇到tableView卡顿嘛?会造成卡顿的原因大致有哪些?
- 高度并没有提前算好、离屏渲染、视图结构过于复杂、视图的UI属性修改频繁、透明的视图过多、图片过大
-
讲讲 MVC、MVVM、MVP,以及你在项目里具体是怎么写的?
- MVC:将View内部的细节封装起来了,View依赖Model,展示的内容由Model决定,Controller负责控制View展示哪个Model的内容
- MVVM:View和Model互不依赖,都经由ViewModel进行联动,ViewModel负责Model的加载和View需要的其他数据的处理,并将其内容展示到View上
- MVP:跟MVC类似,只是把Controller的工作交付给Presenter,并且View不依赖Model,由Presenter控制Model的内容数据展示在哪个View上
网友评论