likes
comments
collection
share

2021 iOS 面试复习资料

作者站长头像
站长
· 阅读数 30

基础题

启动过程(main函数之前) 和 优化

设置运行环境
    启动dyld
    读取mach-o文件的头部信息
    mach-o 初始化
    运行C++的初始化器
加载共享缓存
实例化主程序
加载插入的库(动态库、代码注入的库)
链接主程序和需要插入的库
初始化主程序
    objc
        注册Objc类 (class registration)
        把category的定义插入方法列表 (category registration)
        保证每一个selector唯一 (selector uniquing)
    Initializers
        Objc的+load()函数
        C++的构造函数属性函数
        非基本类型的C++静态全局变量的创建(通常是类或结构体)
    main()

优化:
    冷启动:
        代码瘦身
            动态库(官方建议小于6个)、静态库、废弃代码
            通过mach-o可以获得所有没有被使用的代码(__TEXT,__DATA__objc_selrefs)
        load方法优化
        Time Profiler查找
    热启动:
        代码逻辑优化(数据处理、本地化、耗时任务)

RunLoop

概念
    1. 循环,对象,与线程一一对应,key-value保存在一个全局字典中,主线程默认开启,子线程默认不开启,线程销毁同步销毁
    2. 包含一个线程,若干个Mode,若干个commonMode,还有一个当前运行的Mode
    3. 每次运行必须指定Mode,一般为default和tracking,切换时先退出当前模式,再切换其他模式,保证互不影响
    4. 被标记为commonMode的mode和其他mode本质上没有区别,都是以对象形式被存放在runloop的modes里边。
       只不过被标记为commonMode的mode在创建的时候会打上标记,把modelName存放在runloop的_commonModes里边,不是什么伪model。
       commonMode在创建的时候会把source事件存放到RunLoop的_commonModeItems中,并把该事件同步到其他commonModel里边,保证一个事件只会被创建一次。
    5. runloop运行的时候会同步执行_commonModeItems里边的事件
结构体(大概内容):
    线程

    _commonModes
    _commonModeItems
    _currentMode
    _modes
    _runTime
    _sleepTime
    表头指针
    表尾指针
运行过程:
    启动
    找到mode,没找到或者没事件就退出
    通知即将进入 
    进入 loop
    通知 即将触发 timer
    通知 即将触发 Source0
    触发 Source0
    如果有 Source1,处理 Source1,处理完毕之后 重新进入 loop
    通知 即将进入 休眠
    等待 事件唤醒 (唤醒条件)
        1. 一个基于 port 的Source 的事件。
        2. 一个 Timer 到时间了
        3. RunLoop 自身的超时时间到了
        4. 被其他什么调用者手动唤醒
    通知 loop 被唤醒
    处理 唤醒loop的 事件
    通知 即将退出 loop
应用:
    定时器
    event交互
    AutoreleasePool
    手势识别
    界面刷新
    网络请求

消息查找

objc_msgSend
    lookUpImpOrForward 方法查找准备,例如初始化、加锁等
        cache_getImp 查找类缓存
            找到
                返回
            没找到
                getMethodNoSuper_nolock 查找类方法列表,二分查找
                    找到
                        将方法缓存到当前类
                        返回
                    没找到
                        cache_getImp 查找父类缓存
                            找到
                                返回
                            没找到
                                getMethodNoSuper_nolock 查找父类方法列表,二分查找
                                    找到
                                        将方法缓存到当前类
                                        返回
                                    没找到
                                        进入动态决议   

消息转发

消息查找无果后进入消息转发流程_class_resolveMethod
    动态方法决议
        实例方法
            _class_resolveInstanceMethod 添加方法
        类方法
            _class_resolveClassMethod 添加方法
            没有实现
                _class_resolveInstanceMethod 添加方法
    快速方法转发
        forwardingTargetForSelector 返回对象,如果返回对象没有实现,走此对象动态方法决议
    正常方法转发
        methodSignatureForSelector 方法签名,会调用class_respondsToSelector,因此会多走一次动态方法决议
        forwardInvocation 转向其他类
    doesNotRecognizeSelector 抛出

KVO

键值监听,根据isa-swizzing技术实现,运行时创建一个子类,修改监听对象的isa指针,重写set方法
需要注意
    创建-释放
    多次释放报错
    _a = a; 不会调用
扩展KVC:
    键值对,根据key查找属性

多线程

pthread
    知道有这玩意就行
NSThread
    基于thread封装,添加面向对象概念,性能较差,偏向底层
    优点
        相对于GCD和NSOperation来说是较轻量级的线程开发
    缺点
        使用比较简单,但是需要手动管理创建线程的生命周期、同步、异步、加锁等问题
GCD(常用)
    优点
        相对于NSOperation效率更高
        偏向底层
        高并发功能优秀
    缺点
        多线程管理麻烦(不用记也行)
NSOperation(常用)
    优势
        面向对象
        多线程管理更加方便、灵活、好控制
    缺点
        相对于GCD效率慢一些(也不需要记)

UIview和Layer

UIView,继承自UIReponder,响应事件,本身不能显示可视内容
CALayer,继承自 NSObject,管理可视内容,不强依赖与UIview,可独立显示,管理可视内容坐标系

循环引用

多个对象互相强持有,导致内存无法释放,叫做循环引用
常见:
    delegate
    block
    timer
解除方式:
    weak strong
    __block
    传参

alloc 流程

概念
    开辟空间
流程
    获取对象实际大小
    绑定data数据(方法列表、属性列表等)
    字节对齐,小于16字节分配16字节(最小16字节)

webview加载一个url到渲染结束,中间的过程

启动浏览器内核->建立网络链接->下载/解析JS/CSS代码->后台请求数据->渲染页面
优化
    创建全局浏览器内核
        缺点是需要一部分常驻内存保存浏览器内核,页面跳转时手动管理浏览记录
    提前进行H5数据请求
    DNS预热
    JS/CSS/HTML代码提前下载到本地
        版本号控制
        缓存管理
        下载数据量控制(分段)

静态库/动态库 符号表

静态库
    .a 代码会复制到可执行文件里边
动态库
    .tbd/.dylib 共享库
    动态库不会参与app编译,但是会被包含打app的包里
    dyld会把共享库载入运行进程的地址空间,载入的共享库也会有未定义的符号,这样会触发更多的共享库被载入。
    动态库两种加载方式
        1、程序启动加载时绑定
        2、符号第一次被用到时绑定
符号表
    .dSYM 映射文件(文件名、函数名、行数)

一般应用于多线程,为了线程安全
自旋锁
    没咋用,不太熟
互斥锁
    pthread_mutex、NSLock
信号量
    dispatch_semaphore
递归锁
    @synchronized
iOS用互斥锁比较多
死锁四个条件
    互斥:一个资源每次只能被一个进程使用
    请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放
    不可抢夺:进程已获得的资源,在末使用完之前,不能强行剥夺
    循环等待:若干进程之间形成一种头尾相接的循环等待资源关系。

崩溃的原理,crashlog解析

原理:
    某种原因导致程序无法继续正常运行,系统就会自行把程序关闭,这样的情况叫做崩溃
解析:
    获取crash日志
    获取dSYM文件
    APP文件
    利用symbolicatecrash工具进行解析

响应者(Responder)

从点击屏幕到事件响应
    屏幕点击
        1.系统受到消息
        2.把事件封装成UIEvent,加队事件队列
        3.Application从事件队列取出事件,处理事件
    找到交互视图的Responder
        1.寻找事件的最佳响应视图是通过对视图调用hitTest和pointInside完成的
        2.hitTest的调用顺序是从UIWindow开始,对视图的每个子视图依次调用,子视图的调用顺序是从后面往前面,也可以说是从显示最上面到最下面
        3.遍历直到找到响应视图,然后逐级返回最终到UIWindow返回此视图
    事件处理
        1.找到最适合的响应视图后事件会从此视图开始沿着响应链nextResponder传递,直到找到处理事件的视图,如果没有处理的事件会被丢弃。
        2.如果视图有父视图则nextResponder指向父视图,如果是根视图则指向控制器,最终指向AppDelegate,他们都是通过重写nextResponder来实现。

Autorelease pool

由page构建的双向链表,page里边保存对象指针,一个page保存多个对象指针,有数量限制,满了会自动开辟新的page
构造push,销毁pop
每个runloop都有对应的autoreleasepool

分类(Category)

结构体,编译期生成,保存在category_t *L_OBJC_LABELCATEGORY里边
dyld调用_objc_init, 初始化runtime,runtime链接分类和对应类,插入方法到对应类方法列表
执行顺序根据编译顺序决定
分类添加的属性(objc_setAssociatedObject)不加入到对应类上,由AssociationsManager管理
为什么不能添加实例变量?
    编译型语言,内存布局在编译期已经确定。添加实例变量会修改内存结构
为什么能添加方法?
    方法methodLists是一个二级指针,方法并不是新增,只是修改了指针的指向地址

优化

UITableView 性能优化

数据优化:
    并行请求
    线程安全
    异步数据处理
布局优化:
    布局预加载
    布局缓存
加载速度优化:
    重用机制
    数据、布局提前加载、加入缓存队列
    绘制优化
内存优化:
    数据压缩(图片、视频)
    活跃度分级,分级释放内存

减包优化

删除无用资源(图片、音视频、代码),通过mach-o可以获得所有没有被使用的代码(__TEXT,__DATA__objc_selrefs)
保留资源压缩(图片、音视频、JSS/HTML)
静态库减少
Optimization Level
方法名、属性名替换
[滴滴这篇文章讲的很详细](https://ming1016.github.io/2017/06/12/gmtc-ios-slimming-practice/)

性能测试

性能测试:
    资源消耗
        内存使用率
        CPU使用率
    内存泄漏
    流量消耗
    耗电功率
    渲染效果
    加载时间
数据对比问题:
    Instrument-UI Test
其他自定义统计问题:
    hook

方案设计

无侵入无痕埋点方案

方法hook,例如app生命周期、controller生命周期、touch事件等
需要注意的是如何标识页面元素的唯一性,我的解决方案是:
    在元素创建的时候,生成一个唯一的字符串,例如“类名+元素名+自定义索引(递增的数字)”。维护一张元素对应表,根据对应表查找对应元素。需要根据版本号做区分。
    如果有后台,可以在后台返回数据里边增加页面所有元素的唯一标识,这样可以动态的调整唯一标识。不用区分版本号。  

数据加密

用的不多,大概了解
    AES
        对称加密
    RSA
        公私钥的生成、公钥加密、私钥解密、私钥签名、公钥验签功能,证书信息的读取,以及密钥在KeyChain中存储,查找,删除等功能
    MD5
        按照固定格式进行加密的算法

源码/静态库切换方案

方法一:podspec 里边 if-else
方法二:用 image lookup 获取源文件地址,python查找本地对应文件

组件化通信

URL Router
Target-Action
我司方案:
    维护路由表,但不进行注册,交互的时候使NSClassFromString(name)获取类的实例进行跳转













算法

链表环入点

A+1,B+2 找到 A=B 的点 C
A+1,C+1 找到 A=C 的点就是入环点

打家劫舍2

- (void)temp:(NSArray *)array {
    int index_a = 0;
    int index_b = 1;
    int max_a = 0;
    int max_b = 0;
    for (int i = 0; i < array.count; i ++) {
        if (i == index_a && i != array.count - 1) {
            int temp = [array[i] intValue];
            max_a += temp;
            index_a += 2;
        } else if (i == index_b) {
            int temp = [array[i] intValue];
            max_b += temp;
            index_b += 2;
        }
    }
    NSLog(@"%d",max_a > max_b ? max_a : max_b);
}

两个数组里面各取一个,差值最小的

其他

LinkMap文件可以统计到动态库可执行文件吗

可以统计到

工程使用MRC和ARC各自实现,哪个运行速度和效率高?原因是什么?

相对于MRC来说,ARC的开发速度和运行速度都要高一些。
    开发速度:开发者不需要关心retain和release了
    运行速度:ARC的引用计数管理代码是在runtime里

多个开发环境的配置方式

target
config
cocoapods 没用过
转载自:https://juejin.cn/post/7021830700140036103
评论
请登录