1、面向对象
1.1、一个NSObject对象占用多少内存?
答:
系统分配了16个字节给NSObject对象(通过malloc_size函数获得)但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
参考:OC对象的本质
1.2、对象的isa指针指向哪里?
答:
instance对象的isa指向class对象
class对象的isa指向meta-class对象
meta-class对象的isa指向基类的meta-class对象
参考:isa、superclass指针
1.3、OC的类信息存放在哪里?
答:
对象方法、属性、成员变量、协议信息,存放在class对象中
类方法,存放在meta-class对象中
成员变量的具体值,存放在instance对象中
参考:OC对象的分类
2、KVO
2.1、iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
答:
利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
参考:KVO
2.2、如何手动触发KVO?
答:
手动调用willChangeValueForKey:和didChangeValueForKey:
参考:KVO
2.3、直接修改成员变量会触发KVO么?
答:
不会触发KVO,因为没有调用set方法。
参考:KVO
3、KVC
3.1、通过KVC修改属性会触发KVO么?
答:
会触发KVO,因为会调用对象的willChangeForKey:和didChangeForKey:方法。
参考:KVC
3.2、KVC的赋值和取值过程是怎样的?原理是什么?
参考:KVC
4、Category
4.1、Category的使用场合是什么?
答:
一个类有不同的功能,可以创建不同的分类进行区分。
参考:Category
4.2、Category的实现原理?
答:
category在编译之后底层结构是struct categor_t,里面存储这分类的对象方法、类方法、属性、协议信息。
在程序运行的时候,runtime会将category的数据合并到类的信息中(类对象、元类对象中)
参考:Category
4.3、Category和Class Extension的区别是什么?
答:
Class Extension在编译的时候,它的数据就已经包含在类信息中。
Category是在运行时,才会将数据合并到类信息中。
参考:Category
4.4、Category中有+load方法吗?+load方法是什么时候调用的?+load方法能继承吗?
答:
有。
是在runtime加载类和分类的时候调用的
可以继承,假设子类中没有实现+load方法,但是父类中实现了,调用子类中的+load方法时,会触发父类中的+load方法,所以可以继承。
参考:+load方法
4.5、+load、+initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
答:
+load、+initialize方法的区别区别:
调用方式:
+load根据函数地址直接调用
+initialize通过objc_msgSend调用
调用时刻:
+load在runtime加载类、分类时调用,只会调用一次
+initialize是类第一次接收到消息时调用,每一个类只会调用+initialize一次(父类的+initialize方法可能会被调用多次)
在category中和继承时的调用顺序:
1、+load
1.1先调用类的+load
a>先编译的类,优先调用+load
b>调用子类+load之前,会先调用父类的+load
1.2再调用分类
a>先编译的分类,优先调用+load
2、+initialize
1、先初始化父类
2、再初始化子类(可能最终调用的是父类的+initialize方法)
3、如果分类实现了+initialize会覆盖类本身的+initialize方法
参考:+load方法、 +initialize方法
4.6、Category能否添加成员变量?如果可以,如何给Category添加成员变量?
答:
不能直接给Category添加成员变量。可以关联对象实现给Category添加成员变量的效果。
参考:关联对象
5、block
5.1、block的原理是怎样的?本质是什么?
答:
封装了函数调用以及调用环境
本质是一个OC对象
参考:block本质
5.2、__block的作用是什么?有什么使用注意点?
答:
为了可以在block内部修改捕获的auto变量
注意捕获对象的内存管理
参考:__block修饰符
5.3、block的属性修饰词为什么是copy?使用block有哪些使用注意?
答:
如果block不使用copy就不会在堆上,保证block的生命周期由开发人员管理。
注意循环引用
参考:block循环引用
5.4、在block内部修改NSMutableArray,需不需要添加__block?
答:
如果是添加、删除数组内元素则不需要,因为不是修改数组的指针。
如果数组是auto局部变量,并且修改的是数组的指针,则需要添加block。
参考:[block修饰符](https://www.yuque.com/mengxianliang/uuxpmg/wg6op0)
6、运行时
6.1、讲一下 OC 的消息机制
答:
OC中的方法调用其实都是转成了objc_msgSend函数调用,给receiver发送一条消息(方法名)
发送消息机制有三个阶段:发送消息、动态方法解析、消息转发
参考:消息机制
6.1、消息转发机制流程
答:
阶段一:转发接收者
阶段二:转发调用
参考:消息机制
6.2、什么是Runtime?平时项目中有用过么?
答:
Runtime是什么:
OC是一门动态性比较强的编程语言,允许很多操作推迟到运行时再进行,OC的动态性就是由Runtime来实现和支撑的,Runtime是一套C语言的API,封装了很多动态性相关的函数,平时编写的OC代码,底层都是转换成了Runtime API进行调用。
具体以应用:
利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textField的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题
参考:Runtime应用
7、RunLoop
7.1、讲讲 RunLoop,项目中有用到吗?
答:
1、解决Timer在滚动tableView时不执行问题
2、线程保活
参考:RunLoop应用
7.2、runloop内部实现逻辑?
参考:RunLoop的运行逻辑
7.3、runloop和线程的关系?
答:
每条线程都有唯一的一个与之对应的RunLoop对象。
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value。
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它的时候创建。
RunLoop会在线程结束时销毁。
主线程的RunLoop已经自动获取(创建),自线程默认没有开启RunLoop。
参考:RunLoop基础知识
7.4、timer 与 runloop 的关系?
答:
timer运行是依赖于RunLoop的,RunLoop控制Timer何时运行。
参考:
7.5、程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
答:
原因是NSTimer被添加到RunLoop中后,默认在Default模式下工作,当拖动tableView时,RunLoop切换到UITracking模式下工作,Timer会自动暂停。
解决办法时在NSTimer添加到RunLoop时,标记为CommonModes,这样NSTimer就可以在Default和UITracking模式下工作。
参考:RunLoop应用
7.6、runloop 是怎么响应用户操作的, 具体流程是什么样的?
答:
当用户点击屏幕时,有Source1捕捉系统事件,包装成EventQueue,再有Source0处理触摸事件。
参考:RunLoop的运行逻辑
7.7、说说runLoop的几种状态?
答:
kCFRunLoopEntry // 进入RunLoop
kCFRunLoopBeforeTimers // 即将处理Timer
kCFRunLoopBeforeSources // 即将处理Source
kCFRunLoopBeforeWaiting // 即将进入休眠
kCFRunLoopAfterWaiting // 即将结束休眠
kCFRunLoopExit // 退出Loop
参考:RunLoop的运行逻辑
7.8、runloop的mode作用是什么?
答:
常见有两种模式
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App默认的Mode、
UITrackingRunLoopModel:界面跟踪Mode。
Mode的作用是让RunLoop只在一个模式下执行,可以使不同组的Source0、Source1、Timer、Observer能分隔开来,互不影响。
参考:RunLoop对象
8、多线程
8.1、你理解的多线程?
答:可以让CPU同时做不同的事情。
参考:百度
8.2、iOS的多线程方案有哪几种?你更倾向于哪一种?
答:
pthread:跨平台的多线程方案,使用难度较大,程序员控制生命周期。
NSThread:基于pthread的OC封装,使用简单,程序员控制生命周期。
GCD:C语言形式的多线程管理,系统自动管理线程生命周期。
NSOperation:基于GCD的OC封装,更加面向对象,系统自动管理线程生命周期。
倾向于NSOperation,因为使用起来更方便。
参考:多线程基本概念
8.3、你在项目中用过 GCD 吗?
答:用过,获取主队列、获取全局队列
参考:百度
8.4、GCD 的队列类型
答:并发队列、串行队列
参考:多线程基本概念
8.5、说一下 OperationQueue 和 GCD 的区别,以及各自的优势
答:NSOperation是基于GCD的OC封装,使用更方便。
参考:多线程基本概念
8.6、线程安全的处理手段有哪些?
答:
一、线程同步
spinlock、unfairlock、mutex、NSLock、NSCondition、dispatch_queue_serial、dispatch_semaphore、@synchronized
二、读写安全
pthread_rwlock_t、dispatch_barrier_async
参考:iOS多线程同步方案、读写安全
8.7、OC你了解的锁有哪些?在你回答基础上进行二次提问
追问一:自旋和互斥对比?
追问二:使用以上锁需要注意哪些?
追问三:用C/OC/C++,任选其一,实现自旋或互斥?口述即可!
答:
OC锁有这些:
spinlock、unfairlock、mutex、NSLock、NSCondition、dispatch_queue_serial、dispatch_semaphore、@synchronized
参考:iOS多线程同步方案
追问
一、
自旋锁是利用while循环阻塞线程加锁,iOS10.0后已经弃用,互斥锁是利用休眠加锁。
二、
有加锁就要有解锁操作,避免造成死锁。
加锁避免递归调用,会造成死锁。
三、
方案:
自旋锁OSSpinLock,互斥锁pthread_mutex
实现:
自旋锁使用while循环,互斥锁休眠
参考:iOS多线程同步方案
9、内存管理
9.1、使用CADisplayLink、NSTimer有什么注意点?
答:
可能造成循环引用:
timer的target会强引用viewController,viewController会强引用timer造成循环引用,可以使用block、Proxy、GCDTimer解决。
时间可能不准:
timer和displayLink都是依赖于RunLoop的,RunLoop每次循环处理timer调用,循环时间不能保证和timer调用时间完全一致。
参考:定时器循环引用
9.2、介绍下内存的几大区域
答:
代码段:编译之后的代码,比如main.m中的代码,编译后会放在数据段。
数据段:字符串常量、全局变量、静态变量等
栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小。
堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大。
参考:iOS程序的内存布局
9.3、讲一下你对 iOS 内存管理的理解
答:
在iOS中,使用引用计数来管理OC对象的内存。
一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。
调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1。
当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它。
想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
参考:MRC
9.4、ARC 都帮我们做了什么?
答:
ARC时LLVM编译器和Runtime系统相互协作的结果
LLVM自动添加release、retain、autorelease代码。
Runtime会监控dealloc方法,销毁弱引用指针。
参考:weak指针实现原理
9.5、weak指针的实现原理
答:
weak指针会被保存到SideTable中的weak_table哈希表中,当对象被销毁时,从哈希表中取出指向对象的弱引用指针并销毁。
参考:weak指针实现原理
9.6、autorelease对象在什么时机会被调用release
答:
在RunLoop进入休眠前,或者RunLoop退出循环前。
参考:自动释放池
9.7、方法里有局部对象,出了方法后会立即释放吗
答:
不一定,ARC会自动给对象添加retain、release、autorelease,如果添加的是release会马上释放,如果添加的是autorelease会在RunLoop进入休眠前,或者RunLoop退出循环前休眠。
参考:自动释放池
10、性能优化
10.1、你在项目中是怎么优化内存的?
答:
参考:百度
10.2、优化你是从哪几方面着手?
答:卡顿优化、耗电优化、启动优化、安装包瘦身
参考:卡顿优化
10.3、列表卡顿的原因可能有哪些?你平时是怎么优化的?
答:
参考:卡顿优化
10.4、遇到tableView卡顿嘛?会造成卡顿的原因大致有哪些?
答:
参考:卡顿优化
11、架构与设计模式
11.1、讲讲 MVC、MVVM、MVP,以及你在项目里具体是怎么写的?
答:
参考:架构与设计模式
11.2、你自己用过哪些设计模式?
答:
参考:架构与设计模式
11.3、一般开始做一个项目,你的架构是如何思考的?
答:
参考:架构与设计模式