iOS 面试题整理
2022-05-12 本文已影响0人
AmumuHandsome
常见面试题链接: https://www.jianshu.com/p/25324d04797d
高级iOS面试题:https://www.jianshu.com/p/c10a4701ac28
进阶版:
1.介绍下App启动的完成过程?
1. App启动过程
▪ 解析Info.plist
▪ 加载相关信息,例如如闪屏
▪ 沙箱建立、权限检查
Mach-O加载
▪ 如果是胖二进制文件,寻找合适当前CPU类别的部分
▪ 加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
▪ 定位内部、外部指针引用,例如字符串、函数等
▪ 执行声明为__attribute__((constructor))的C函数
▪ 加载类扩展(Category)中的方法
▪ C++静态对象加载、调用ObjC的 +load 函数
程序执行
· 1.main函数
· 2.执行UIApplicationMain函数
· 1.创建UIApplication对象
· 2.创建UIApplicationDelegate对象并复制
· 3.读取配置文件info.plist,设置程序启动的一些属性,(关于info.plist的内容可网上搜索下)
· 4.创建应用程序的Main Runloop循环
· 3.UIApplicationDelegate对象开始处理监听到的事件
· 1.程序启动成功之后,首先调用application:didFinishLaunchingWithOptions:方法,
· 如果info.plist文件中配置了启动storyboard文件名,则加载storyboard文件。
· 如果没有配置,则根据代码来创建UIWindow--->UIWindow的rootViewController-->显示
2.比如App启动过慢,你可能想到的因素有哪些?
利用DYLD_PRINT_STATISTICS分析main()函数之前的耗时
▪ 重新梳理架构,减少动态库、ObjC类的数目,减少Category的数目
▪ 定期扫描不再使用的动态库、类、函数,例如每两个迭代一次
▪ 用dispatchonce()代替所有的__attribute__((constructor))函数、C++静态对象初始化、ObjC的+load
▪ 在设计师可接受的范围内压缩图片的大小,会有意外收获
• 利用锚点分析applicationWillFinishLaunching的耗时
▪ 将不需要马上在applicationWillFinishLaunching执行的代码延后执行
▪ rootViewController的加载,适当将某一级的childViewController或subviews延后加载
▪ 如果你的App可能会被后台拉起并冷启动,可考虑不加载rootViewController
• 不应放过的一些小细节
▪ 异步操作并不影响指标,但有可能影响交互体验,例如大量网络请求导致数据拥堵
▪ 有时候一些交互上的优化比技术手段效果更明显,视觉上的快决不是冰冷的数据可以解释的,好好和你们的设计师谈谈动画
3.多线程有哪几种?你更倾向于哪一种?
image.png NSOperation相对于GCD:
1,NSOperation拥有更多的函数可用,具体查看api。NSOperationQueue 是在GCD基础上实现的,只不过是GCD更高一层的抽象。
2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
3,NSOperationQueue支持KVO。可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
4,GCD 只支持FIFO 的队列,而NSOperationQueue可以调整队列的执行顺序(通过调整权重)。NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
使用NSOperation的情况:各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等。
使用GCD的情况:一般的需求很简单的多线程操作,用GCD都可以了,简单高效。
从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。
4.TCP UDP区别?
1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证
TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)。
UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。
5.哈希表是如何实现的?如何解决地址冲突?
哈希表是也是通过数组来实现的,首先对key值进行哈希化得到一个整数,然后对整数进行计算,得到一个数组中的下标,
然后进行存取,解决地址冲突常用方法有开放定址法和链表法。
runtime源码的存放weak指针哈希表使用的就是开放定址法,Java里的HashMap使用的是链表法。
6 Runloop
▪为什么只有主线程的runloop是开启的
▪为什么只在主线程刷新UI
▪PerformSelector和runloop的关系
▪如何使线程保活
1.为什么只有主线程的runloop是开启的
mian()函数中调用UIApplicationMain,这里会创建一个主线程,用于UI处理,为了让程序可以
一直运行并接收事件,所以在主线程中开启一个runloop,让主线程常驻.
2.为什么只在主线程刷新UI
我们所有用到的UI都是来自于UIKit这个基础库.因为objc不是一门线程安全的语言所以存在多
线程读写不同步的问题,如果使用加锁的方式操作系统开销很大,会耗费大量的系统资源(内存、
时间片轮转、cpu处理速度…),加上上面讲到的系统事件的接收处理都在主线程,如果UI异步线
程的话 还会存在 同步处理事件的问题,所以多点触摸手势等一些事件要保持和UI在同一个线程
相对是最优解.
另一方面是 屏幕的渲染是 60帧(60Hz/秒), 也就是1秒钟回调60次的频率,(iPad Pro 是120Hz/
秒),我们的runloop 理想状态下也会按照时钟周期 回调60次(iPad Pro 120次), 这么高频率的调用
是为了 屏幕图像显示能够垂直同步 不卡顿.在异步线程的话是很难保证这个处理过程的同步更
新. 即便能保证的话 相对主线程而言 系统资源开销 线程调度等等将会占据大部分资源和在同一
个线程只专门干一件事有点得不偿失.
3.PerformSelector和runloop的关系
当调用NSObect的 performSelector:相关的时候,内部会创建一个timer定时器添加到当前线程的
runloop中,如果当前线程没有启动runloop,则该方法不会被调用.
开发中遇到最多的问题就是这个performSelector: 导致对象的延迟释放,这里开发过程中注意一
下,可以用单次的NSTimer替代.
4.如何使线程保活?
想要线程保活的话就开启该线程的runloop即可,注意:在NSThread执行的方法中添加while(true)
{},这样是模拟runloop的运行原理,结合GCD的信号量,在{}代码块中处理任务.
但是注意 开启runloop的方法要正确
如下代码
//测试开启线程
- (void)memoryTest {
for (int i = 0; i < 100000; ++i) {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
[self performSelector:@selector(stopThread) onThread:thread withObject:nil waitUntilDone:YES];
}
}
//线程停止
- (void)stopThread {
CFRunLoopStop(CFRunLoopGetCurrent());
NSThread *thread = [NSThread currentThread];
[thread cancel];
}
//运行线程的runloop 注意 意添加的那个空port,否则会出现内存泄露
- (void)run {
@autoreleasepool {
NSLog(@"current thread = %@", [NSThread currentThread]);
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
if (!self.emptyPort) {
self.emptyPort = [NSMachPort port];
}
[runLoop addPort:self.emptyPort forMode:NSDefaultRunLoopMode];
[runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]];
}
}
//下列代码用于模拟线程内部做的一些耗时任务
- (void)printSomething {
NSLog(@"current thread = %@", [NSThread currentThread]);
[self performSelector:@selector(printSomething) withObject:nil afterDelay:1];
}
//模拟手动点击按钮 让 runloop停掉
- (void)stopButtonDidClicked:(id)sender {
[self performSelector:@selector(stopRunloop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)stopRunloop {
CFRunLoopStop(CFRunLoopGetCurrent());
}