iOS面试
# 一、OC与Swift的区别
1.swift是静态语言,有类型推断,OC是动态语言。
2.swift面向协议编程,OC面向对象编程
3.swift注重值类型,OC注重引用类型。
4.swift支持泛型,OC只支持轻量泛型
5.swift支持静态派发(效率高)、动态派发(函数表派发、消息派发)方式,OC支持动态派发(消息派发)方式。
6.swift支持函数式编程
7.swift的协议不仅可以被类实现,也可以被struct和enum实现
8.swift有元组类型、支持运算符重载
9.swift引入了命名空间,从此不用再import其他文件
10.swift支持默认参数
11.swift比oc代码更加简洁
#二、结构体与类的区别
1.类型不同:结构是一种值类型,而类是引用类型。值类型用于存储数据的值,引用类型用于存储对实际数据的引用。那么结构体就是当成值来使用的,类则通过引用来对实际数据操作。
2.存储不同:结构使用栈存储,而类使用堆存储。栈的空间相对较小.但是存储在栈中的数据访问效率相对较高.堆的空间相对较大.但是存储在堆中的数据的访问效率相对较低。
3.作用不同:类是反映现实事物的一种抽象,而结构体的作用只是一种包含了具体不同类别数据的一种包装,结构体不具备类的继承多态特性。
4.关键字不同:在类中可以使用但是在结构中限制使用的关键字有:abstract、sealed、protected;StaTIc关键字可以用在类名前面用来声明静态类,但是不能用在[struct](https://so.csdn.net/so/search?q=struct&spm=1001.2101.3001.7020)前面,不存在静态结构。
5.初始化不同:类可以在声明的时候初始化,结构不能在申明的时候初始化(不能在结构中初始化字段),否则报错。
类的实质是一种数据类型,类似于int、char等基本类型,不同的是它是一种复杂的数据类型。因为它的本质是类型,而不是数据,所以不存在于内存中,不能被直接操作,只有被实例化为对象时,才会变得可操作。
#三、面向协议编程pop
1.用协议代替基类
2.用值类型(结构体)代替引用类型(类)
好处,比如ABC都有一个公共方法,把方法发到协议D,使之 ABC遵循协议D
oop面向对象 是用基类解决的,从中插一个基类
#四、三次握手的步骤
1.客户端发送sync报文给服务端建立连接
2.服务端收到后发送sync+ack报文给客户端
3.客户端收到发送ack报文给服务端
#五、https为什么更安全
1.HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的TLS(SSL)加密传输协议
2.HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
3.HTTP 的连接很简单,是无状态的;HTTPS协议是由 HTTP+SSL/TLS 协议构建的可进行加密传输、身份认证的网络协议,比 HTTPS 协议安全。
#六、死锁理解
cpu流水调度加了一个标记,等下一个线程访问临界区时,cpu就不会分配cpu资源给这个线程访问临界区,让这个线程等待
两个或多个进程,由于资源的竞争或者彼此间的通信而造成的阻塞现象
线程A持有资源A,要获取资源B
线程B持有资源B,要获取资源A
解决:设置占有锁的超时时间
避免一个线程同时获取多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
#七、并行与并发区别
1.并行是同时执行
2.并发是交替执行
并行同时执行AB任务
并发暂停执行A任务,区执行B任务,执行完B任务再执行A任务
#八、并发底层是怎么实现的
1.至少是双核的cpu或以上
https://zhuanlan.zhihu.com/p/68171984
修改线程直接的优先级
使用孤立队列对资源进行多读单写
资源保护
多线程编程中,最常见的情形是你有一个资源,每次只有一个线程被允许访问这个资源。
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。
线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。
###进程线程的区别
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
#九、KVO的实现机制
1.KVO是基于runtime机制实现的
2.当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
3.如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
4.每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
5.键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
https://blog.csdn.net/qq_19484963/article/details/79454355
a.必须有set方法b.动态生成子类,并创建set方法,关联set方法,将isa指向子类c.进行消息转发的时候,转发到该子类中
#十、通知原理
有张哈希表,以消息名为key,里面存储的数据模型数组
模型包含监听者、方法、回调、
注册消息监听的时候
把模型加入到表里面
发送消息的时候
把模型从表里面去处理去执行对应的任务
移除消息的时候
把模型从表里面移除
#十一、消息转发机制
1. 消息发送阶段:负责从类及父类的缓存列表及方法列表查找方法,先从父类去找,再用元类找,最后从NSObject去做
2. 动态解析阶段:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现
3. 消息转发阶段:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接受者来处理
4. 标准消息转发:runtime发送methodSignatureForSelector消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。
#十二、内存管理的理解
原则:谁创建谁管理,谁retain谁release
ARC 的工作原理大致是这样:当我们编译源码的时候,编译器会分析源码中每个对象的生命周期,然后基于这些对象的生命周期,在合适的地方添加相应的引用计数操作代码retain, release和autorelease。
通过哈希表(通过对象指针两次hash查找)找到对应的弱引用表weak_table_t 和引用记数表RefcountMap
weak自动置为nil:
当一个对象被dealloc后,在dealloc的内部实现中,会去调用弱引用清除的相关函数,这个函数的内部实现中,会根据当前对象指针,通过两次hash查找,第一次找到对应的SideTable, 第二次找到对应的弱引用数组。然后遍历这个数组中的所有弱引用指针,分别置为nil
自动释放池
是以栈为结点通过双向链表的形式组合而成。是和线程一一对应的(AutoreleasePoolPage)
两个哨兵对象之间是一个自动释放池,多层嵌套就是多次插入哨兵对象
AutoreleasePoolPage::pop 根据传入的哨兵对象找到对应位置,给上传push操作之后添加的对象依次发送release消息,回退next指针到正确位置
局部变量array什么时候被释放:在每一次runloop循环中,当他将要结束的时候,会对前一次创建的autoreleasepool进行pop操作,同时会push进来一个新的autoreleasepool。array是在runloop结束autoreleasepool pop的时候释放的
使用场景:在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasePool,来降低内存的峰值,防止一些内存消耗过大导致的问题
二进制重排
一个内存地址时,如果虚拟内存地址对应的物理内存还未分配,CPU 会执行 page fault,将指令从磁盘加载到物理内存中并进行验签操作(App Store 发布情况下)。
在App 启动过程中,会调用各种函数,由于这些函数分布在各个 TEXT 段中且不连续,此时需要执行多次 page fault 创建分页,将代码读取到物理内存中,并且这些分页中的部分代码不会在启动阶段被调用。
把启动阶段需要用到的函数按顺序排放,减少 page fault 执行次数和分页数量,并使 page fault 在相邻页执行
https://juejin.cn/post/6844904121896534024
#十三、设计模式
基本原则
开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
单一职责原则告诉我们实现类要职责单一;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合度;
合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。
简单工厂模式:用个工厂来创建一个要求的相同种类东西~Factory生产 圆、方形、椭圆、菱形 的按钮
单例模式:单一、重复使用、频繁使用~XXManager,XXServer,UserDefault
代理模式:先占坑,异步操作~耗时操作,图片异步加载
观察者模式:主动观察到状态改变,从而发生变化,一对多的场景~KVO、Notification
装饰器模式:动态的给对象增加方法~swift中面向协议编程以及extension都是这种思想,
外观模式:减少不同类之间的交互,提供统一接口~在获取资源类中,需要分别调用本地缓存和网络数据,最后把这个组合结果返回;实现隔离减少不同类之间的交互
工厂方法模式:用个工厂来创建多个要求的相同种类东西~Factory生产 圆、方形、椭圆、菱形 的按钮,同时区分暗黑主题、还是正常模式
抽象工厂模式:用个工厂来创建多个要求的不相同种类东西~Factory生产 圆、方形、椭圆、菱形 的控件,同时区分暗黑主题、还是正常模式;控件包含按钮,UILabel、UIView、UIImageView
适配器模式:把需适配者Adaptee,通过适配器Adapter,适配成可以供目标TargetA、B、C、D使用 ,注意ABCD用的都是Adaptee中的同样的功能,不是组合~
桥接模式:A想用B但是没办法通过继承组合等形式达成时~
策略模式:定义一系列算法,把它们一个个的封装起来,并且使他们可以相互替换。策略模式使得算法可独立于使用它的客户端而变化~X模块中有个分段函数,A、B、C需要X模块支持,但是对X模块中的分段函数有不同要求,这时,你可以在X模块中写if、else语句、或者switch语句,但这样违反开闭原则,所以,更好的方式是把函数算法封装起来,作为参数传进去。
https://juejin.cn/post/6844904200564916237
#十四、组件化
用MVVM模式,用策略模式作差异化实现的
把差异化统一写在vm里面,根据配置的不同会实现不同的效果,当然会有一个默认的配置
MVP是个用代理的形式去处理业务,属于一对一
MVVM通常与ReactiveCocoa一起,进行双向绑定去处理业务,可以一对多,进行响应式编程
#十五、性能优化
启动优化:
冷启动
1.加载Math-O(系统可执行文件)到内存。减少文件数量,如OC的.h.m文件,C++和cpp文件
2.加载 dyld(动态连接器)到内存。减少库的数量,
a.加载动态库:dyld 从主执行文件的 header 获取到需要加载的所依赖的动态库列表,然后找到每个 dylib,而 dylib 也可能依赖其他 dylib,所以这是一个递归加载依赖的过程
b.Rebase 和 Bind:
作指针偏移和符号绑定
c.Objc setup:
1. 注册 Objc 类
2. 把 category 的定义插入方法列表
3. 保证每个 selector 唯一
减少Objc 类和category的数量d.Initializers:1. Objc 的 +load 函数 2. C++ 构造函数属性函数 3. 非基本类型的 C++ 静态全局变量的创建(通常是类或结构体)
尽量用懒加载的方式代替+load 和Initializers理的方法
热启动
减少主线程的阻塞
能延迟的操作尽量延迟,能放到子线程的就放到子线程
比如升级弹框,注册推送
其他
1.减少离屏渲染,用图片代替阴影、圆角等特效
2.tableview缓存高度,减少计算
3.tableview尽量注册多的cell,减少cell的重布局
4.复用机制,减少UI的绘制
5.耗时操作用子线程,大数据包的处理用子线程
6.尽量减少硬件的使用,比如实时用完就关
#十六、做得最好的
解决资源
加锁
队列
UI立即刷新的方法
layout sub views 只能被系统调用
layout if need 能手动调用
setneeds layout 标记刷新,等调用上述方法时刷新