DISPATCH_QUEUE_CONCURRENT
DISPATCH_QUEUE_SERIAL
dispatch_barrier_async
dispatch_group_notify
queue.maxCOncurrentOperationCount= 1;
[operation2 addDependency:operation1];
[NSBlockOperation blockOperationWithBlock:^{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 添加操作到队列
[queue addOperationWithBlock:
1、对象循环引用
@class ,Strong,weak
2、block循环引用
__weak typeof(self) weakself = self;
3、NSNotification的观察者忘记移除
[[NSNotificationCenter defaultCenter] removeObserver:self];
4、delegate循环引用问题
@property (nonatomic, weak) id delegate;
5、NSTimer循环引用
([NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeRepeat:) userInfo:nil repeats:YES]);
使用GCD
6、非OC对象内存处理
CGImageRef类型变量非OC对象,其需要手动执行释放操作CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃。其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free等都需要注意;
7、使用过多的UIWebView
改为WKWebView
8、大次数循环内存暴涨问题
一、手机百度(搜索业务)1.技术亮点、难点。
快慢指针,直到两个指针相遇或者到达尾部
https://www.jianshu.com/p/119c1ff5ea69
https://www.jianshu.com/p/4d5b6fc33519
要点 :dns缓存 弱网环境优化 包体积大小
域名合并:淘宝、美团等公司公布的解决方案中都有提到,就是将公司原来的很多域名都合并到较少的几个域名。为什么?因为 HTTP 的通道复用就是基于域名划分的。如果域名只有几个,那么多数请求都可以在长连接通道进行,这样就可以降低延迟、增加成功率
预热,尽早建立长连接。这样其他的业务请求就可以复用长连接通道。加快访问速度。因为每次建立连接都需要经过 DNS 域名解析、TCP 三次握手等漫长步骤。建立长连接的时机可以考虑:冷启动、前后台切换、网络切换等
如果情况允许,可以将网络切换到 HTTP 2.0,解决了 HTTP1.1 的 head of blocking ,降低了网络延迟,提供了更强大的多路复用技术。还加入了流量控制、新的二进制格式、Server Push、请求优先级和依赖等待等特性。
建立多通道。比如携程、艺龙、美团等公司都有自己的 TCP、UDP 通道。具有多域名共用通道。
有些超级大厂还自研了协议。比如 QUIC
加入 CDN 加速,动态静态资源分离
对于类似埋点的业务数据请求,可以合并请求,减小流量。另外结合埋点数据压缩上传
App 网络情况诊断
根据网络情况,动态设置超时时间等
https://zhuanlan.zhihu.com/p/115134324
https://blog.csdn.net/xj1009420846/article/details/80313566
image
方法见:http://stackoverflow.com/questions/35233564/how-to-find-unused-code-in-xcode-7
https://developer.Apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/CheckingCodeCoverage.html
main()调用之后的加载时间
在main()被调用之后,App的主要工作就是初始化必要的服务,显示首页内容等。而我们的优化也是围绕如何能够快速展现首页来开展。 App通常在AppDelegate类中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中创建首页需要展示的view,然后在当前runloop的末尾,主动调用CA::Transaction::commit完成视图的渲染。
而视图的渲染主要涉及三个阶段:
准备阶段 这里主要是图片的解码
布局阶段 首页所有UIView的- (void)layoutSubViews()运行
绘制阶段 首页所有UIView的- (void)drawRect:(CGRect)rect运行
再加上启动之后必要服务的启动、必要数据的创建和读取,这些就是我们可以尝试优化的地方
因此,对于main()函数调用之前我们可以优化的点有:
https://www.jianshu.com/p/7096478ccbe7
runloop有个60fps回调,绘制内容交给GPU渲染,包括view拼接,纹理的渲染。
CPU计算好显示内容提交到GPU,GPU渲染完成后将渲染结果放入帧缓冲区
1 当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行
2 离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操
3 重写了drawRect方法,并且使用任何Core Graphics的技术进行了绘制操作,就涉及到了CPU渲染
4 CoreGraphic通常是线程安全的,所以可以进行异步绘制,显示的时候再放回主线程
public static int sum(int n1, int n2) {
if(n1 == n2) {
return n1;
}
if(n1 > n2) {
int temp = n1;
n1 = n2;
n2 = temp;
}
return sum(n1, n2-1) + n2;
}
一、区别
1.修饰变量类型的区别
weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。
assign 可修饰对象,和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是”unsafe_”。
2.是否产生野指针的区别
weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。
assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。
二、相似
都可以修饰对象类型,但是assign修饰对象会存在问题。
三、总结
assign 适用于基本数据类型如int,float,struct等值类型,不适用于引用类型。因为值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。而引用类型会被放入堆中,需要我们自己手动管理内存或通过ARC管理。
weak 适用于delegate和block等引用类型,不会导致野指针问题,也不会循环引用,非常安全。
1、调用objc_release
2、因为对象的引用计数为0,所以执行dealloc
3、在dealloc中,调用了_objc_rootDealloc函数
4、在_objc_rootDealloc中,调用了object_dispose函数
5、调用objc_destructInstance
6、最后调用objc_clear_deallocating。
https://blog.csdn.net/future_one/article/details/81606895
关键字:结构体 parent和child 双向链表
https://www.jianshu.com/p/58dab9c28a12
objc_msgSend汇编部分仅仅完成很少的缓存查找功能,如果找不到就会调用C方法去对象的方法二维数组中找,找不到再查父类的缓存(这也是汇编实现的)和父类的方法数组,一直找到根类,如果此过程中找到对应的方法则调用并添加缓存,如果没有找到,则表明该继承体系都没有直接实现该方法,这时runtime会调用对象的方法决议去尝试解决。如果不行则由CoreFoundation框架提供的forwarding来转发到其他对象处理,若还不能处理则抛出异常。
https://www.jianshu.com/p/75a4737741fd
全局 堆栈 带__block的自动变量 和 静态变量 就是直接地址访问。所以在Block里面可以直接改变变量的值。
剩下的静态全局变量,全局变量,函数参数,也是可以在直接在Block中改变变量值的,但是他们并没有变成Block结构体__main_block_impl_0的成员变量,因为他们的作用域大,所以可以直接更改他们的值。
https://blog.csdn.net/DreamcoffeeZS/article/details/102257488
https://blog.csdn.net/DreamcoffeeZS/article/details/102475351
1、load是根据函数地址直接调用
2、initialize是通过objc_msgSend调用
1、load是runtime加载类、分类的时候调用(只会调用一次)
2、initialize是类第一次接收到消息的时候调用, 每一个类只会initialize一次(如果子类没有实现initialize方法, 会调用父类的initialize方法, 所以父类的initialize方法可能会调用多次)
1、load:父子分
先调用类的load, 在调用分类的load
先编译的类, 优先调用load, 调用子类的load之前, 会先调用父类的load
先编译的分类, 优先调用load
2、initialize 分子父
先初始化分类, 后初始化子类
通过消息机制调用, 当子类没有initialize方法时, 会调用父类的initialize方法, 所以父类的initialize方法会调用多次
https://blog.csdn.net/elricboa/article/details/78847305
开放定址法 拉链法
https://blog.csdn.net/xtzmm1215/article/details/47177701
https://www.jianshu.com/p/40078ed436b4
kvo作为一个中间对象,在当前控制器销毁时任然会存在,所以在销毁时应该移除当前观察释放kvo对象
一个像素是RGB + alpha 一个是16位,所以是100 * 100 * 4 *16 位
https://blog.csdn.net/zhangshichi/article/details/51161245
组队列(dispatch_group) 阻塞任务(dispatch_barrier)
https://www.jianshu.com/p/81576172ad1f
检查对象的类有没有相应的 setter 方法。如果没有抛出异常;
检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类;
检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法;
添加这个观察者
http://www.cocoachina.com/articles/11321
https://www.jianshu.com/p/3ffa8bc19cbe
互斥条件 请求和保持条件 不剥夺条件 环路等待条件
https://www.jianshu.com/p/333cf1c02a0e
算法:https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/solution/mian-shi-ti-67-ba-zi-fu-chuan-zhuan-huan-cheng-z-4/
六、知乎
1.组件化拆分的过程问答的很细致,以及拆完组件化是否还有可以优化的
2.包体积优化
3.启动优化
4.dyld阶段的细节
5.autoreleasepool和线程有什么关系
6.数组中有1万个数字,如果删除某一个元素,得到一个新的数组,怎么判断出删除的是几???
7.手触摸到屏幕,整个事件的响应全过程。
8.用协议实现一个一对多的通知效果
9.判断链表是否有环,且环是多少个节点
10.用栈实现一个数组的增删改查
11.设计上报日志的库
12.抽象工厂和工厂有什么区别
七、腾讯(个人觉得面试官实力有点差)
1.引用计数的理解?系统为什么这么设计
2.runloop和autoreleasepool的关系,子线程创建的时候需要些一个autoreleasepool吗,为什么?
3.webView和WKWebView的区别
4.实现多线程的方式,各有什么优缺点
多线程有多种实现方式,常见的有以下三种:
1、继承Thread类,重写run()方法。
1) 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
2)创建Thread子类的实例即创建了线程对象。
3)调用线程对象的start()方法启动线程。
2、实现Runnable接口,重写run()方法。
1)定义Runnable接口的实现类,并重写该方法的run()方法,该run()方法同样是该线程的执行体。
2)创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
3)调用线程对象的start()方法启动线程。
3、通过实现Callable接口和使用FutureTask包装器来实现线程。
1)创建Callable接口的实现类,并实现call()方法,该call()方法的方法体同样是该线程的执行体。
2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
三种实现方式的优缺点对比:
1、实现Runnable和Callable接口方式:
优点:
1)线程类只是实现了Runnable接口(JDK1.0开始)或Callable接口(JDK1.5开始),还可以继承其他类。
2)多线程可以共享同一个target对象,非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
3)实现Callable接口创建多线程最大的好处是可以有返回值。
缺点:
编程稍显复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2、使用继承Thread类方式:
优点:
编写简单,如果要访问当前线程无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
缺点:
线程类已经继承了Thread类,不能再继承其他类(java的单继承性),因此该方式不够灵活。
补充说明:
1)Callable规定重写call()方法;Runnable重写run()方法。
2)Callable的任务执行结束后可有返回值;Runnable的任务是不能有返回值的。
3)call()方法可以抛出异常;run()方法不可以。
4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检查计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,可以获取执行结果。
5.iOS如何实现多继承,代码书写一下。
代理 消息转发
动态方法解析:向当前类发送resolveInstanceMethod: 信号,检查是否动态向该类添加了方法
快速消息转发:检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法,若该方法返回nil或者非self,则向该返回对象重新发送消息
标准消息转发:runtime发送methodSignatureForSelector:消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出.
6.创建子线程需要创建一个autoreleasepool吗?
https://blog.csdn.net/qq_22389025/article/details/85162240?utm_medium=distribute.pc_relevant.none-task-blog-title-1&spm=1001.2101.3001.4242
不需要,但是他说如果不出创建autoreleasepool对象,如果有autorelease修饰的对象会有警告,简直是胡扯。
7.为什么不能在子线程刷新UI
https://www.jianshu.com/p/5849eb69ec82
8.一个imageView在屏幕中显示的整个过程,那些步骤可以放在子线程,那些要在主线程执行