面试总结六部曲
- 1、iOS基础系列(一):底层分析
- 2、iOS基础系列(二):零碎知识点
- 3、iOS基础系列(三):各种优化
- 4、iOS基础系列(四):扩展问题
- 5、iOS基础系列(五):Swift总结
- 6、iOS基础系列(六):Flutter总结
1、iOS 说说NSUserDefaults存储
NSUserDefault是一个非常实用的存储单元,但是他是不安全的,实质是plist文件存储,如果让你设计一个这样一个功能,你会如何设计
回答1:
NSUserDefaults 其实是一个 plist 文件,即使只是修改一个 key 都会 load 整个文件,不适合存储大量数据。
回答2:
NSUserDefaults你每次同步都要全写,打开要全读。小数据连同大数据一起操作了,IO以及内存都不划算,况且还有解析、序列化的成本。大数据建议弄出去,别跟NSUserDefaults这种经常存取的弄一起。
回答3:
数据量一大读取成本就高,敏感数据容易被破解,因为实际上是明文存在 plist 中的,所以建议用户安全相关的(如 token)尽量不要放在 UserDefault 中,即使要存也最好做一次加密再存,最好的方式是存在 Keychain 中。UserDefault 更适合存一些轻量的数据,如当前版本(可用来做检测更新以及展示欢迎页或其他逻辑)。建议使用 CoreData 或其他数据存储方式。
回答4:如何自己高效实现NSUserdefault
从使用和原理两方面讲起,使用方面,首先进行调用封装和key键管理,封装是为了使用效率,key键管理是为了方便修改和管理。
从原理方面讲,NSUserdefault 支持的数据类型有NSString、 NSNumber、NSDate、 NSArray、NSDictionary、BOOL、NSInteger、NSFloat等系统定义的数据类型。如果要进行频繁的大数据存储建议使用归档和数据库等底层更优化的方式存储。
回答5:
参考yycache设计思路吧,小数据用sqlite,大数据存文件,数据存取和删除基于lru算法,设计加锁保证存取安全等等,自由发挥下
2、iOS设计一个IM框架
使用第三方IM服务
对于短平快的公司,完全可以采用第三方SDK来实现。国内IM的第三方服务商有很多,类似云信、环信、融云、LeanCloud,当然还有其它的很多,这里就不一一举例了,感兴趣的小伙伴可以自行查阅下。
- 第三方服务商IM底层协议基本上都是TCP。他们的IM方案很成熟,有了它们,我们甚至不需要自己去搭建IM后台,什么都不需要去考虑。如果你足够懒,甚至连UI都不需要自己做,这些第三方有各自一套IM的UI,拿来就可以直接用。真可谓3分钟集成…
- 但是缺点也很明显,定制化程度太高,很多东西我们不可控。**当然还有一个最最重要的一点,就是太贵了…**作为真正社交为主打的APP,仅此一点,就足以让我们望而却步。当然,如果IM对于APP只是一个辅助功能,那么用第三方服务也无可厚非。
我们自己去实现
我们自己去实现也有很多选择:
- 1、首先面临的就是传输协议的选择,TCP还是UDP?
- 2、其次是我们需要去选择使用哪种聊天协议:
- 基于Scoket或者WebScoket或者其他的私有协议、
- MQTT
- 还是广为人诟病的XMPP?
- 3、我们是自己去基于OS底层Socket进行封装还是在第三方框架的基础上进行封装?
- 4、传输数据的格式,我们是用Json、还是XML、还是谷歌推出的ProtocolBuffer?
- 5、我们还有一些细节问题需要考虑,例如TCP的长连接如何保持,心跳机制,Qos机制,重连机制等等…当然,除此之外,我们还有一些安全问题需要考虑。
传输协议的选择
首先从传输层协议来说,我们有两种选择:TCPorUDP?
这里我们直接说结论吧:对于小公司或者技术不那么成熟的公司,IM一定要用TCP来实现,因为如果你要用UDP的话,需要做的事太多。当然QQ就是用的UDP协议,当然不仅仅是UDP,腾讯还用了自己的私有协议,来保证了传输的可靠性,杜绝了UDP下各种数据丢包,乱序等等一系列问题。
各种聊天协议
首先我们以实现方式来切入,基本上有以下四种实现方式:
- 1、基于Scoket原生:代表框架CocoaAsyncSocket。
- 2、基于WebScoket:代表框架SocketRocket。
- 3、基于MQTT:代表框架MQTTKit。
- 4、基于XMPP:代表框架XMPPFramework。
首先需要搞清楚的是,其中MQTT和XMPP为聊天协议,它们是最上层的协议,而WebScoket是传输通讯协议,它是基于Socket封装的一个协议。而通常我们所说的腾讯IM的私有协议,就是基于WebScoket或者Scoket原生进行封装的一个聊天协议。
所以说到底,iOS要做一个真正的IM产品,一般都是基于Scoket或者WebScoket等,再之上加上一些私有协议来保证的。
3、缓存策略
**缓存算法:**缓存法通过设计良好的数据分块、预取、顺序预取、缓存替换等算法来提高对缓存内容的命中率。缓存算法可以分为基于访问时间的策略、基于访问频率的策略、访问时间与频率兼顾策略、时间距离分布策略等类型。
4、产品希望实现一个功能,将10~100张图片直接拼成1个视频,你会如何实现
1、技术选型,从业务的角度出发,细化产品的要求,比如使用场景、用户需求等,再决定用哪些技术解决问题;(Tips:待选方案有AVFoudation、GPUImage等)
2、实现细节,如何保证使用过程中内存不占用过大、CPU使用不过高;
5、进程和线程的区别 为什么切换线程代价小
进程和线程的区别 为什么切换线程代价小
首先要明白进程是什么:
关于进程的定义有很多,一个经典的定义是一个执行中程序的实例,进程是程序的动态表现。 一个程序进行起来后,会使用很多资源,比如使用寄存器,内存,文件等。每当切换进程时,必须要考虑保存当前进程的状态。状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开的文件描述符的集合,这个状态叫做上下文(Context)。可见,想要切换进程,保存的状态还不少。
其次就要了解线程是什么:
线程存在于进程中,一个进程可以有一个或多个线程。线程是运行在进程上下文中的逻辑流,简单说,线程可以理解为一个方法(Java)或函数(C),这个线程可以独立完成一项任务。同样线程有自己的上下文,包括唯一的整数线程ID, 栈、栈指针、程序计数器、通用目的寄存器和条件码。可以理解为线程上下文是进程上下文的子集。
由于保存线程的上下文明显比进程的上下文小,因此系统切换线程时,必然开销更小。
如何实现进程间的通信
进程间通信的目的
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源
通知事件:一个进程需要向另一个进程发送消息,通知其发生了某种事情(比如进程终止父进程告诉子进程)
进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,能够及时知道它的状态改变。
进程间通信的分类
- 管道:匿名管道,命名管道
- System V IPC:消息队列,共享内存,信号量
- POSIX IPC:消息队列,共享内存,信号量,互斥量,读写锁,条件变量
6、iOS是否支持重载
- **重载(overload):**函数名相同,函数的参数列表不同(包括参数个数和参数类型),至于返回类型可同可不同。重载既可以发生在同一个类的不同函数之间,也可发生在父类子类的继承关系之间,其中发生在父类子类之间时要注意与重写区分开。
- **重写(override):**发生于父类和子类之间,指的是子类不想继承使用父类的方法,通过重写同一个函数的实现实现对父类中同一个函数的覆盖,因此又叫函数覆盖。注意重写的函数必须和父类一模一样,包括函数名、参数个数和类型以及返回值,只是重写了函数的实现,这也是和重载区分开的关键。
Swift中有重载,但Objective-C中基本不支持重载。
7、coacoPod相关问题
CocoaPods是专门为iOS工程提供第三方依赖库的管理工具,通过CocoaPods,我们可以更方便地管理每个第三方库的版本,而且不需要我们做太多的配置,就可以直观、集中和自动化地管理我们项目的第三方库。
CocoaPods将所有依赖的库都放在一个名为Pods的项目下,然后让主项目依赖Pods项目。然后,我们编码工作都从主项目转移到Pods项目。Pods项目最终会编译为一个libPod-项目名.a静态库,主项目依赖于这个静态库。
对于资源文件,CocoaPods 提供了一个名为 Pods-resources.sh 的 bash 脚本,该脚本在每次项目编译的时候都会执行,将第三方库的各种资源文件复制到目标目录中。
CocoaPods 通过一个名为 Pods.xcconfig 的文件来在编译时设置所有的依赖和参数。
pod install & pod update区别
- 用pod install命令来安装新的pod,每次在Podfile中新增和删除pod都使用pod install命令。
- 在Podfile中添加新的pod后应该用pod install命令,而不是pod update命令。通过pod install命令安装新的pod而不用担心在同一进程中修改已有的pod。
- pod update命令仅用在更新指定pod到指定版本或者更新所有pod。
8、Json和xml相比,优劣势
XML和json的优缺点
xml的优点
(1)格式统一
(2)容易与其他系统进行远程交互,数据共享比较方便
xml的缺点
(1)xml文件庞大,文件格式复杂,传输占带宽
(2)服务器和客户端都需要花费大量代码来解析xml,导致服务器和客户端代码变得异常复杂且不易维护
(3)客户端和服务端解析xml花费较多的资源和时间
json的优点
(1)数据格式比较简单,易于读写,格式是压缩的,占用带宽小
(2)易于解析,包括JavaScript可以通过简单的通过eval_r()进行json数据的读取
json的缺点
(1)没有xml那么通用
(2)json格式目前还在推广阶段
9、后台返回的数据是多少K的,大文件传输怎么办
大文件下载、断点续传、后台下载
10、苹果为什么要把控件设计为继承树的形式?
因为整体的UI搭建用的是组合模式,如果不这样做手势没法传递。
11、iOS scheme跳转机制
scheme跳转机制
我们都知道苹果手机中的APP都有一个沙盒,APP就是一个信息孤岛,相互是不可以进行通信的。但是iOS的APP可以注册自己的URL Scheme,URL Scheme是为方便app之间互相调用而设计的。我们可以通过系统的OpenURL来打开该app,并可以传递一些参数URL Scheme必须能唯一标识一个APP,如果你设置的URL Scheme与别的APP的URL Scheme冲突时,你的APP不一定会被启动起来。因为当你的APP在安装的时候,系统里面已经注册了你的URL Scheme。 一般情况下,是会调用先安装的app。但是iOS的系统app的URL Scheme肯定是最高的。所以我们定义URL Scheme的时候,尽量避开系统app已经定义过的URL Scheme。
Universal Link的基本运作流程
- APP第一次启动 or APP更新版本后第一次启动
- APP向工程里配置的域名发起Get请求拉取apple-app-association Json File
- APP将apple-app-association注册给系统
- 由任意webview发起跳转的url,如果命中了apple-app-association注册过的通用链接
- 打开App,触发Universal Link delegate
- 没命中,webview继续跳转url
在你进行apple-app-association 以及 App工程的配置之后,整个Universal Link的运作流程完全由系统控制了
配置apple-app-association
究竟哪些的url会被识别为Universal Link,全看这个apple-app-association文件Apple Document UniversalLinks.html
- 你的域名必须支持Https
- 域名根目录下放这个文件apple-app-association,不带任何后缀
- 文件为json保存为文本即可
- json按着官网要求填写即可
Universal Link(通用链接):看起来就是一条普通的https链接,当然是我们在该链接域名根目录配置过的一个链接,也可以在该链接中放置对应的H5页面。当用户的点击该链接,只要手机中安装了支持该链接的APP就会直接进入到APP中。如果没有安装APP则会跳转到Safari浏览器中,展示H5页面。对用户来说则是一个无缝跳转的过程。
使用Universal Link(通用链接)跳转的好处
- 唯一性: 不像自定义的URL Scheme,因为它使用标准的HTTPS协议链接到你的web站点,所以一般不会被其它的APP所声明。另外,URL scheme因为是自定义的协议,所以在没有安装 app 的情况下是无法直接打开的(在Safari中还会出现一个不可打开的弹窗),而Universal Link(通用链接)本身是一个HTTPS链接,所以有更好的兼容性;
- 安全:当用户的手机上安装了你的APP,那么系统会去你配置的网站上去下载你上传上去的说明文件(这个说明文件声明了当前该HTTPS链接可以打开那些APP)。因为只有你自己才能上传文件到你网站的根目录,所以你的网站和你的APP之间的关联是安全的;
- 可变:当用户手机上没有安装你的APP的时候,Universal Link(通用链接)也能够工作。如果你愿意,在没有安装你的app的时候,用户点击链接,会在safari中展示你网站的内容;
- 简单:一个HTTPS的链接,可以同时作用于网站和APP;
- 私有: 其它APP可以在不需要知道你的APP是否安装了的情况下和你的APP相互通信。
12、为什么只有主线程的runloop是开启的
程序不会马上退出,而是保持运行状态,RunLoop的基本作用:
- 1、保持程序持续的运行
- 2、处理app中的各种事件(比如触摸事件,定时器事件)
- 3、节省CPU资源,提高程序性能,该做事的时候做事,改休息的时候休息
13、为什么只在主线程刷新UI
- 1、UIKit并不是一个 线程安全 的类,UI操作涉及到渲染访问各种View对象的属性,如果异步操作下会存在读写问题,而为其加锁则会耗费大量资源并拖慢运行速度。
- 2、另一方面因为整个程序的起点UIApplication是在主线程进行初始化,所有的用户事件都是在主线程上进行传递(如点击、拖动),所以view只能在主线程上才能对事件进行响应。而在渲染方面由于图像的渲染需要以60帧的刷新率在屏幕上 同时 更新,在非主线程异步化的情况下无法确定这个处理过程能够实现同步更新。
Runloop 与绘图循环
UIApplication在主线程所初始化的Runloop我们称为Main Runloop,它负责处理app存活期间的大部分事件,如用户交互等,它一直处于不断处理事件和休眠的循环之中,以确保能尽快的将用户事件传递给GPU进行渲染,使用户行为能够得到响应,画面之所以能够得到不断刷新也是因为Main Runloop在驱动着
而每一个view的变化的修改并不是立刻变化,相反的会在当前run loop的结束的时候统一进行重绘,这样设计的目的是为了能够在一个runloop里面处理好所有需要变化的view,包括resize、hide、reposition等等,所有view的改变都能在同一时间生效,这样能够更高效的处理绘制,这个机制被称为绘图循环(View Drawing Cycle)。
假设这个时候我们应用了我们的魔法UIKit,并愉快的在一条后台线程操作UI,但当我们需要对设备进行旋转并重新布局的时候,问题来了,因为各个线程之间不同步,这时候各个view修改的请求时机是零碎的,所以所有的旋转变化并不能在Main Runloop的一个runloop里面处理完,这就导致设备旋转之后还有一些view迟迟没有旋转。