iOS底层原理
Objective-C底层实现是C/C++代码。OC对象基于C/C++的结构体,Class为指向了objc_class类型的结构体的指针。
OC对象分为三种:实例对象(instance对象) ,类对象(class对象),元类对象(meta-class对象)
系统会为一个NSObject对象分配最少16个字节的内存空间。一个指针变量所占用的大小(64bit占8个字节,32bit占4个字节)class_getInstanceSize和malloc_size
对象的isa指针指向哪里?instance对象的isa指针指向class对象,class对象的isa指针指向meta-class对象,meta-class对象的isa指针指向基类的meta-class对象,基类自己的isa指针指向自己。
OC的类信息存放在哪里?成员变量的具体值存放在实例对象(instance对象);对象方法,协议,属性,成员变量信息存放在类对象(class对象);类方法信息存放在元类对象(meta-class对象)。
指针与内存的一些相关面试
浅拷贝就是拷贝后,并没有进行真正的复制,而是复制的对象和原对象都指向同一个地址
深拷贝是真正的复制了一份,复制的对象指向了新的地址
copy: 对于可变对象为深拷贝,对于不可变对象为浅拷贝
mutableCopy:始终是深拷贝
bss段( bss segment )、数据段(data segment)、代码段(code segment/text segment)、堆(heap)、栈 (stack heap)
堆是用于存放进程运行中被动态分配的内存段
栈又称堆栈, 是用户存放程序临时创建的局部变量
Weak弱引用
assign适用于基本数据类型如int,float,struct等值类型,不适用于引用类型。因为值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。而引用类型会被放入堆中,需要我们自己手动管理内存或通过ARC管理。
weak适用于delegate和block等引用类型,不会导致野指针问题,也不会循环引用,非常安全。
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
block本质: block本质是一个oc对象,它内部也有个isa指针,block内部封了函数调用及函数调用的oc对象.
修饰类型:__weak修饰对象不能基本数据类型
__weak 需添加__strong 解决外部释放内部还要持有问题。__block需要手动赋值nil,非arc
__block对象可以在block中被重新赋值,__weak不可以;__block可以让block修改局部变量,__weak不能
如何解决循环引用
__weak block持有一个weak 对象在释放的时候可以打破彼此强引用
__Blcok 将当前对象通过__block来改变存储范围,将self的引用地址存放在堆中,在block最后设置为nil
传值 obj是一个参数,参数存放在栈中,block执行后就给释放掉。
block的修饰词为什么是copy?使用block有哪些使用注意?
复制到堆上,存活时间更长久,更好的使用它,注意循环引用等.
block在修改NSMutableArray,需不需要添加_block?
不需要,因为是使用它,不是修改它,能不用_block就不要用_block,因为会生成更多东西.
autoreleasePool应用,autoreleasePool 在何时被释放?
RunLoop 的释放池会等待Runloop即将进入睡眠或者即将退出的时候释放一次、手动添加的大括号外会被释放。
for循环中线程一直在做事情,Runloop不会进入睡眠对象无法及时释放,造成瞬时内存占用过大,如处理较大图片等。解决办法,每次循环时都手动创建一个局部释放池,及时创建,及时释放。
Runloop其实就是一个do-while循环,每次循环一圈,都会判断一次retVal,决定是否结束循环,继续执行循环外的代码。 处理事件:点击、定时器 ,mach_msg()线程休眠:内核操作
Runloop 应用,定时器不受滑动事件影响,添加至runloop的comomodel中, tracking,2种模式,
Runloop 和线程的关系:Runloop和线程是一一对应保存在全局的字典里面。线程(非主线程)刚创建时并没有Runloop,当你需要时创建,线程结束时Runloop销毁。
苹果不允许直接创建 RunLoop,你只能在线程内部获取,它提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。
Runtime原理
消息转发原理
一个用C、汇编写的运行时系统来动态得创建类和对象、进行消息传递和转发。
动态方法解析 resolveInstanceMethod 返回实现新方法
备用接收者 forwardingTargetForSelector 指定某个类接收
完整消息转发 methodSignatureForSelector
runtime应用场景
关联对象(Objective-C Associated Objects)给分类增加属性
(Method Swizzling)方法添加和替换和KVO实现
消息转发(热更新)解决Bug(JSPatch)
实现NSCoding的自动归档和自动解档
实现字典和模型的自动转换(MJExtension)
url-block、protocol-class(和 url-controller 类似)、target-action
缺点.增加代码的冗余,组件化颗粒度越细,中间代码越多、增加项目复杂度
exp打印值、修改值
image list命令用来查看工程中使用的库
load是根据函数地址直接调用、initialize是通过objc_msgSend调用
load是runtime加载类、分类的时候调用(只会调用一次)
initialize是类第一次接收到消息的时候调用, 每一个类只会initialize一次(如果子类没有实现initialize方法, 会调用父类的initialize方法, 所以父类的initialize方法可能会调用多次)
性能优化以及架构
①性能优化方面:
runloop如何优化tableView
cell重用
提前计算高度
因为苹果使用双缓冲区,根据上图,当垂直信号过来之后,但是GPU还没有渲染完成,就会出现掉帧(卡顿)显现
预排版 缓存tableView cell 的高度
预解码 & 预渲染 预解码 SDWebImage 或YYImage 中有是先用
异步渲染 使用core Graphic合成位图
app 性能分析->
线程使用、 I/O操作 、 CPU使用分析、
内存
日常如何检查内存泄露
启动
SDK注册较为耗时的可以使用异步并发加载,部分二级页才用到的SDK可以采用懒加载的形式。
防止启动时有过多的串行接口操作,尽量精简。
避免启动后出现过多的耗性能操作,例如频繁读写IO,数据解码等耗时方法的调用。
3. APP启动时间应从哪些方面优化?
原生页面
编译打包
4. 如何降低APP包的大小
APP稳定性
项目优化
②架构方面:
设计模式:
iOS有哪些常见的设计模式
单例会有什么弊端?
1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
MVVM, 针对vue为 div, model 和vue示例, 把具体视图逻辑与view拆分,适用性更强。
MVP设计模式的优点是:减低耦合,实现了Model与View 的真正分离,修改View而不影响Model。模块职责分明,层次分明,便于维护,多人开发首选。Presenter可复用,一个Presenter可以用于多个View,不用去改Presenter利于单元测试。模块分明,方便单元测试,而不用特意搭建平台,人工模擬用户操作等等耗时耗力的事情。
缺点是:架构的引入在逻辑上清晰了,但代码量增加了很多,不利于小工程项目的开发和使用。
由于Presenter层负责给View层提供数据,在View层需要数据较多的情况下,会导致Presenter层和View层之间的交互较为频繁,在一定程度上增加了他们的耦合度。
单一职责原则、开闭、接口隔离、依赖倒置、里氏替换、迪米特法则
多线程、网络
什么是多线程? 多线程就是指一个进程中可以开启多条线程,可以同时执行不同的任务。多线程可以提高程序的执行效率。多线程是一个进程中并发多个线程同时执行各自的任务。就是由单核CPU通过时间片不断的切换执行程序。
分时操作系统会把CPU的时间划分为长短基本相同的时间区间(时间片),在一个时间片内,CPU只能处理一个线程中的一个任务,对于一个单核CPU来说,在不同的时间片来执行不同线程中的任务,就形成了多个任务在同时执行的“假象”。
线程池:提供一组线程资源用来复用线程资源的一个池子
发生线程死锁的条件是,在队列里面的任务没有执行完毕的时候,在同一个队列里面添加了同步执行的任务.
造成阻塞条件
可能造成阻塞的条件: 锁(lock),循环引用,sleep()函数,循环执行
多线程的优缺点
1.优点
*减少应用程序的堵塞,增加程序的执行效率
*适当提高CPU和内存的利用率
*线程上的任务执行完成后,线程自动销毁(部分API可实现)
3.缺点
*线程的开启需要占用一定的内存空间,默认是512KB/线程
*线程开启的越多内存占用越大,会降低程序的性能
*线程越多CPU在调用线程上的开销就越大
*程序设计更加复杂,需要考虑线程间通信,多线程的数据共享等问题
并行是对于cpu多个核心来说的,在同一时刻有多个任务一起执行
并发是对于cpu单个核心来说的。对每个线程分个不同片段来执行
GCD
全称Grand Central Dispatch,由C语言实现,是苹果为多核的并行运算提出的解决方案,GCD会自动利用更多的CPU内核,自动管理线程的生命周期,程序员只需要告诉GCD需要执行的任务,无需编写任何管理线程的代码。GCD也是iOS使用频率最高的多线程技术。
NSOperation:基于GCD封装的面向对象的多线程类,相较于GCD提供了很多方便的API,使用频率较高。
GCD执行原理
串行与并行
Dispatch Queue。然而,存在于两种Dispatch Queue,一种是要等待上一个执行完,再执行下一个的Serial Dispatch Queue,这叫做串行队列;另一种,则是不需要上一个执行完,就能执行下一个的Concurrent Dispatch Queue,遵循FIFO(先进先出)。
同步与异步
串行与并行针对的是队列,而同步与异步,针对的则是线程。最大的区别在于,同步线程要阻塞当前线程,必须要等待同步线程中的任务执行完,返回以后,才能继续执行下一任务,整个过程是不会创建新线程的;而异步线程则是不用等待,会在新开启的线程中执行任务(执行主队列的任务除外,主队列的任务在主线程中执行)。
锁相关原理
1.自旋锁和互斥锁的特点
自旋锁会忙等,所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环访问是否已经解锁,直到被锁资源释放锁。
互斥锁会休眠,所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作,直到被锁资源释放锁。此时会唤醒休眠线程。
自旋锁优缺点
优点:因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,CPU时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁。
缺点:自旋锁一直占用CPU,他在未获得锁的情况下,一直运行自旋,所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。自旋锁不能实现递归调用。
2.原子属性和非原子属性
OC在定义属性时有nonatomic和atomic两种选择,默认为atomic属性
atomic:原子属性,为setter方法加自旋锁(即为单写多读)
nonatomic:非原子属性,不会为setter方法加锁
nonatomic和atomic的对比
atomic:线程安全,需要消耗大量的资源;
nonatomic:非线程安全,适合内存小的移动设备。
注意:
如非需抢占资源的属性(如购票,充值),所有属性都声明为nonatomic。
尽量避免多线程抢夺同一块资源。
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力。
网络设计
网络框架的理解
进程和线程的区别
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程是进程的一个实体, 是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
开发过程中一些需求结合多线程或者网络设计!
7. 网络的七层协议? 8. Http 和 Https 的区别?Https为什么更加安全? 9. 解释一下 三次握手 和 四次挥手? 10. Cookie和Session
数据结构算法
排序
数据结构设计
复杂度讨论
一些脑筋急转弯的算法题
1. 链表和数组的区别是什么?插入和查询的时间复杂度分别是多少? 2. 哈希表是如何实现的?如何解决地址冲突? 3. 排序题:冒泡排序,选择排序,插入排序,快速排序(二路,三路)能写出哪些? 4. 链表题:如何检测链表中是否有环?如何删除链表中等于某个值的所有节点? 5. 数组题:如何在有序数组中找出和等于给定值的两个元素?如何合并两个有序的数组之后保持有序? 6. 二叉树题:如何反转二叉树?如何验证两个二叉树是完全相等的? 7. 数据结构的存储一般常用的有几种?各有什么特点?
https://github.com/LGBamboo/iOS-Advanced