iOS面试题-PDD面试题及答案
iOS面试--拼多多最新iOS开发面试题
基础
http请求的过程?
- 对请求网址进行DNS域名解析,得到对应的IP地址
- 根据这个IP,找到对应的服务器,发起TCP的三次握手
- 建立TCP连接后发起HTTP请求
- 服务器响应HTTP请求,浏览器得到html代码,或者请求内容
- 解析html代码,或者请求内容,如果是浏览器发起请求,浏览器会请求html代码中的资源
- 浏览器对页面进行渲染呈现给用户
为什么要在主线程更新UI?
首先,从UIKit中来看,UIKit中大部分类属性都是线程不安全的,很多类是不具备在多线程环境下工作的,如果把UIKit改成线程安全的,会因为加锁解锁而耗费大量的时间,势必降低UI性能,造成卡顿,且极有可能造成不安全事件,比如,两个线程中分别删除,修改了同一个cell,使得程序Crash。
如何保证OC容器在多线程下的数据安全性?
oc容器有,数组,字典,集合,可操作容器类都不是线程安全,可以通过加锁,或者使用GCD的栅栏块实现数据同步的线程安全,但是以上方法都会损失部分性能。
SDWebImage的内存怎么设计的,更新原则是什么?
SDWebImage更新图片优先级是,内存缓存>磁盘缓存>直接下载。 SDWebImage使用了NSCache实现内存缓存,NSCache设计类似于NSMutableDictionary,但是是线程安全的设计,并且使用了 lru淘汰算法, lru一般采用HashTable和双向链表来实现,HashTable用来满足获取缓存的时候,时间复杂度为O(1), 双向链表用来满足淘汰机制和更新机制的时间复杂度O(1)。
简单说一下,LRU,首先将Cache都放到HashTable和双向链表中,当需要获取缓存时,就从Hashtable中读取cache node,同时把双向链表中的同一个cache node移动到链表表头,那么链表尾则表示最近最少使用的Cache,当需要淘汰时就丢弃到表尾部分即可。
+load
和+initialize
怎么理解的,什么时候会被调用,分别讨论父类重写而子类没重写的情况
load方法: 当类被导入到项目的时候就会执行+load
,且+load
在main函数开始执行之前的无论该类是否被用到。每个类的load函数只会自动调用一次,不会沿用父类的+load,如果父类重写,子类没有重写,那么父类也会收到调用,且本类的方法优先于分类的方法,分类的方法在本类的方法之后。
+initialize:类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用,并且只会调用一次,子类不实现initialize方法,父类也会被调用,会沿用父类的方法,分类会覆盖本类的调用,只会执行分类的方法。
怎么理解OC的动态性
首先动态是指将程序编译好的代码装载进内存开始运行的阶段,oc的动态性主要体现在
- 动态类型:运行时确定对象的类型
- 动态绑定:运行时确定对象的调用方法
- 动态加载:运行时加载需要的资源或者可执行代码。
其中动态绑定机制主要是因为oc的函数派发机制是消息机制派发。这里注意纯swift不是消息机制派发,而是函数表派发,所以纯swift失去了oc的部分动态特性,只有使用
dynamic
,@objc
修饰的时候会通过oc的运行时进行消息机制派发。
介绍下消息转发机制,说一下这些过程中用到的系统api
介绍消息转发机制和另一个面试问题“一个对象收到一个它无法响应的方法到崩溃之间发生了什么”属于同一个问题,这个机制就是抛出异常之前会经历的步骤,分别是
- 动态方法解析
- 快速转发
- 完整消息转发
动态方法解析时当调用方法为实现的时候会调用
+(BOOL)resolveInstanceMethod:
或者+ (BOOL)resolveClassMethod:
快速消息转发时在- (id)forwardingTargetForSelector:(SEL)aSelector
方法里面返回一个新对象,相当于替换了消息的接收者,进而去新的接收者那里去寻找对应的实现。 完整消息转发时,methodSignatureForSelector
中实现方法签名,forwardInvocation
中根据methodSignatureForSelector
返回的方法签名进行消息的转发,(void)forwardInvocation:(NSInvocation *)invocation
(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
怎么理解Block的
oc的block实际就是封装了函数调用以及调用环境的OC对象,它内部也有个isa指针。 而switf的闭包本质是代码块,它是函数的升级版本,函数是有名称、可复用的代码块,闭包则是比函数更加灵活的匿名代码块,两者略微有点不同。 定义Block,就相当于定义了一个函数,只要在调用Block的时候才会执行Block体内的代码
Block的变量捕获机制
对于block外部变量的捕获,如果外部变量是局部变量,那么捕获的局部变量相当于捕获了变量的值,如果是静态变量那么,block捕获了静态变量的地址。 回顾我们上一个问题,block实际就是封装了函数调用,那么当外部变量是局部变量的时候,变量的作用域就是在外部函数,所以当block获取外部变量的时候,只能获取变量的副本,不能够修改外部变量,当变量是,静态全局,或者变量在堆区的时候,block就能直接获取地址从而修改变量。
__block的实现原理
对于__block修饰的局部变量,在block里面使用的时候,也是对该变量指针的捕获,但是因为该变量从栈上被拷贝到了堆上,并且生成了一个新的结构体对象,所以该变量的地址发生了变化。
内存管理机制
内存管理要从内存模型开始说起,程序内存分为桟区、堆区、全局区、常量区、代码区,全局区,平时创建对象,变量,字符串等等都需要保存在内存空间中,如果不管理内存,那么一个app都会消耗完系统中所有可用的内存,造成系统崩溃。 垃圾回收有两种方法:标记清除、引用计数。
- 标记清除:垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
- 引用计数:运行环境中,有一张计数表,保存了内存里面所有的资源的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。 在iOS中,oc和swift都使用引用计数作为垃圾回收算法,被称为自动引用计数(ARC),编译器会分析源码中每个对象的生命周期,然后基于这些对象的生命周期,来添加相应的引用计数操作代码。
用户点击屏幕,系统是怎么找到一个view并决定由它来响应事件的
- 触摸屏幕产生触摸事件后,触摸事件会被添加到由UIApplication管理的事件队列中。
- UIApplication会从事件队列中取出最前面的事件,把事件传递给应用程序的主窗口
- 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。
- 最合适的view会调用自己的touches方法处理事件
- touches默认做法是把事件顺着响应者链条向上抛,首先看initial view能否处理这个事件,如果不能则会将事件传递给其上级视图(inital view的superView);如果上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller,首先判断视图控制器的根视图view是否能处理此事件;如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传递;对于第二个图视图控制器本身还在另一个视图控制器中,则继续交给父视图控制器的根视图,如果根视图不能处理则交给父视图控制器处理;一直到 window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃
算法
{}判断括号匹配性
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
char[] chars = s.toCharArray();
for (char aChar : chars) {
if (stack.size() == 0) {
stack.push(aChar);
} else if (isSym(stack.peek(), aChar)) {
stack.pop();
} else {
stack.push(aChar);
}
}
return stack.size() == 0;
}
private boolean isSym(char c1, char c2) {
return (c1 == '(' && c2 == ')') || (c1 == '[' && c2 == ']') || (c1 == '{' && c2 == '}');
}
}
只有一种括号,计算最少加多少个括号,使其满足匹配
class Solution {
public:
int minAddToMakeValid(string S) {
stack<char> s;
for (int i = 0; i < S.length(); i++) {
if (s.empty()) {
s.push(S[i]);
continue;
}
if (s.top() == '(' && S[i] == ')') {
s.pop();
}else {
s.push(S[i]);
}
}
return (int)s.size();
}
};
设计
JSON转模型如何实现?
略
夜间模式如何实现?
略
如何设计播放器架构设计?
这个后面再写
转载自:https://juejin.cn/post/6986653706557063205