面试被虐总结二

年后因为各种原因只能重新找工作,期间进攻了UC,CVTE,酷狗还有其他一些比较大一点的公司,一路过来,被虐的不要不要的。

性能优化实战

nstruments

nstruments有三件套(Time Profiler、Core Animation、GPU Driver),秒把U(GPU、CPU)来搞。 如果想在地铁上用手机也能调BUG,也可以使用HeapInspector,支持OC和Swift,比Beagle更强大。能监测Leak、Retain cycles、dirty memory、对象生命周期(PS:最难调的BUG,往往跟生命周期有关)

解决问题

一:App进入一个界面比较慢,尤其是首次进入:

    聊天界面
    创建Controller及相关类
    读取消息列表
    渲染消息

通过Instrument Profile过后,发现当时App有相当一部分时间花费在了CoreText的渲染上。当时App的文本消息是使用CoreText绘制的,而CoreText整个绘制流程当中有一步占比最重:文本消息的高度宽度计算及超链接检测。

    解决1:以空间换时间,把文字高宽度和超链接的信息都存入database,这样下次启动的时候不用重新计算计算完之后,再启动一个后台任务在子线程当中把计算好的信息(dirty message)存入database。

### App页面卡顿:滑动中出现严重卡顿问题


    用一个倾斜90°的tableview来做,简单,不用自己维护重用队列,每个cell放一个 vc 的view 就可以了。

### 经测试发现严重卡顿。

    用scrollView来写,自己来维护重用队列,具体做法大家可以参考 UIScrollView 实践经验 (3.重用) 。最后“完美”地实现了需求,开始做别的需求去了。

    上线一个多月之后发现。我在使用过程中。在scrollView滚动的时候,明显的感觉到了卡帧,然后就开始优化。

### 解决思路

1.尼玛,该不会是 UIScrollView的重用 没写好?

    断点验证了下,vc只会创建3个,重用没问题呀。

2.因为涉及重用,所有vc里面tableview的内容肯定不是一下子全请求出来的,每滚动一次才会去请求下个页面的数据,以及初始化页面。然后再看nice,忽然发现它滚动的时候,状态栏居然没有网络请求的小菊花!!难不成是一次请求的?应该不会吧,这么多数据呀。为了验证这种猜想,用 Charles 拦截下,结果nice也是每滚动次发次请求的:

github,博客,stackOverFlow,各种google,发现都是一些关于tableView优化规范而已。

### 只能靠自己一点点琢磨了。

先跑下Instruments三件套吧(Time Profiler,Core Animation,GPU Driver)。

    1.排除了GPU的问题:关于渲染,OPenGL ES Driver中检测
    2.排除了CPU的问题:cellForRowCPU使用30%,头像的地方


### 只能从代码了:

    cell的高度没有缓存,这肯定有影响
    发现refreshData的success block回调居然执行2次,这岂不是意味着tableview要reloadData两次,短时间刷新2次,肯定会卡啊

## 三(四)次握手 

### 其实有个问题,为什么连接的时候是三次握手,关闭的时候却是四次挥手?

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手

## 崩溃记录

    1、数组越界导致的崩溃。
    -[__NSArrayI objectAtIndex:]: index 100 beyond bounds [0 .. 99]’
    2、数据集合类型,如字典、数组中插入元素时,插入空指针nil。
    3、调用当前对象类中不存在的方法导致崩溃。
    ‘-[AppDelegate button1]: unrecognized selector sent to instance 0x8c764c0’
    4、数据接收时,服务器返回数据不规范,如字典或数组元素中存在null,且客户端没做处理导致的崩溃。
    5、内存管理不当,向野指针发送消息导致的崩溃。(此类bug最难解决,所以编码时谨慎)
    一般报错为:EXC_BAD_ACCESS


## 疑难杂症

    认定消息已经发送成功:二次握手,非心跳包
    网络请求失败:域名解析错误,使用ip+host
    MOV-MP4->android:使用ijk底层编码H264
    文件上传:32位的MD5结合文件的前8个字节的16位+文件的后8个字节的16位=64位




### 总结

    一、客户端发送的请求数量和服务端接收的数量不一致。原因可能是服务端并发请求数量设置的过小。
    二、利用循环请求数据时小概率的引起程序崩溃。原因可能是两个线程同时对一个数据源进行了操作。
    三、绘制分时、k线时线条模糊,举行时会出现四条边不一样粗细。原因就是IOS绘机制的问题。
    四、利用tableView的headerViewForSection:方法获取headerView时一直是nil。原因应该是设置headerView时利用- (UIView *)tableView: viewForHeaderInSection:的代理方法返回的UIView应该是UITableViewHeaderFooterView类型的,很多时候被他的返回值(UIView *)误导了。
    五、由于项目比较大,页面多而且复杂,有时就需要从当前的responder通过nextResponder(一个甚至多个)找到深层次的VeiwController。
    六、项目中需要用到循环刷新数据,利用NSTimer来实现,但是想在VC销毁时停掉timer(就是在dealloc方法中停掉),结果发现dealloc根本不调用,原本以为是引用计数没有减到0,可是问题不在此,而就在NSTimer这。结果在viewDidDisappear:停掉timer后就调用dealloc方法了。
    七、利用viewWithTag:寻找子View时,出现绝对性的错误,对象类型都不对。问题出现在设置的tag有重复,要注意的是子View在包括子View的子View的tag都不可以重复,所以建议另外创建一个文件专门设定tag,就像android中的R.java文件一样来确保tag的唯一。


### 准确机制

    二次握手:而心跳机制,是为了确保客户端跟服务器链接没问题,客户端定时的给服务器发送空字符串或额定格式的消息(一般而言,不会太多内容,节省流量),然后客户端根据返回的结果判断(超时、正常、断线)而做重连与否的操作
    非心跳包:二次握手是指小心接收发送都得遵循的,是指一个消息的传递,如A给B发一个消息,A发送到服务器,服务器发给B  ,B根据消息告诉服务器已经收到,服务器再传递给A(最后一步可要可不要);这样一个流程下来才叫一个消息传递成功



## 关于tableView性能优化


    正确使用reuseIdentifier来重用cells
    尽量使所有的view opaque,包括cell自身
    避免渐变,图片缩放,后台选人
    缓存行高
    如果cell内现实的内容来自web,使用异步加载,缓存请求结果
    使用shadowPath来画阴影
    减少subviews的数量
    尽量不适用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果
    使用正确的数据结构来存储数据
    使用rowHeight, sectionFooterHeight 和 sectionHeaderHeight来设定固定的高,不要请求delegate



### 只定义一种Cell。
``` bash
提前计算并缓存每个Cell的高度。
提前创建真正显示的、需要加工的数据并缓存。
缓存View!
在UITableView的Delegate、DataSource方法中,减少任何不必要的操作

最常用的就是cell的重用, 注册重用标识符

如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell
如果有很多数据的时候,就会堆积很多cell。如果重用cell,为cell创建一个ID
每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell
设置正确的reuseIdentifer以重用cell

避免cell的重新布局

cell的布局填充等操作 比较耗时,一般创建时就布局好
如可以将cell单独放到一个自定义类,初始化时就布局好

提前计算并缓存cell的属性及内容

在cellForRowAtIndexPath:中尽量做更少的操作。如果需要做一些处理,那么最好做过一次之后,就将结果缓存起来。
当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度
而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell

减少cell中控件的数量

尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件,
不适用的可以先隐藏

不要使用ClearColor,无背景色,透明度也不要设置为0

渲染耗时比较长
尽量将view设置为不透明,包括cell本身。

使用局部更新

如果只是更新某组的话,使用reloadSection进行局部更新

加载网络数据,下载图片,使用异步加载,并缓存

如果cell显示的内容来此网络,那么确保这些内容是通过异步来获取的

少使用addView 给cell动态添加view

按需加载cell,cell滚动很快时,只加载范围内的cell

注意正确使用懒加载

不要实现无用的代理方法,tableView只遵守两个协议

非必要的代理或者数据源方法可以省略,比如numberofsention

缓存行高:

如果row的高度不相同,那么将其缓存下来
estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可

避免渐变,图像缩放以及离屏绘制

使用shadowPath来设置阴影。

使用适当的数据结构来保存需要的信息。不同的结构会带来不同的操作代价。

使用rowHeight, sectionFooterHeight 和 sectionHeaderHeight 来设置一个恒定 高度,而不要从delegate中获取。

使用富文本标签代价是很昂贵的

tableView性能优化总结

入门级(这是些你一定会经常用在你app开发中的建议)

1. 用ARC管理内存
2. 在正确的地方使用reuseIdentifier
3. 尽可能使Views不透明
4. 避免庞大的XIB
5. 不要block主线程
6. 在Image Views中调整图片大小
7. 选择正确的Collection
8. 打开gzip压缩

中级(这些是你可能在一些相对复杂情况下可能用到的)

9. 重用和延迟加载Views
10. Cache, Cache, 还是Cache!
11. 权衡渲染方法
12. 处理内存警告
13. 重用大开销的对象
14. 使用Sprite Sheets
15. 避免反复处理数据
16. 选择正确的数据格式
17. 正确地设定Background Images
18. 减少使用Web特性
19. 设定Shadow Path
20. 优化你的Table View
21. 选择正确的数据存储选项

进阶级(这些建议只应该在你确信他们可以解决问题和得心应手的情况下采用)

22. 加速启动时间
23. 使用Autorelease Pool
24. 选择是否缓存图片
25. 尽量避免日期格式转换

坚持原创技术分享,您的支持将鼓励我继续创作!