1. 如何对iOS设备进行性能测试?
Xcode->Open Developer Tool -> Instruments ->Time Profiler
测试介绍:App耗时方法检测
2. 开发项目时你是怎么检查内存泄露?
1). 静态分析 analyze。
2). instruments工具里面有个leak可以动态分析。
3. UITableView 如何进行优化?
参考资料:tableView懒加载图片, tableview的优化,异步绘制cell,UITableViewCell 性能优化
4. 什么是懒加载?
答:懒加载就是只在用到的时候才去初始化。也可以理解成延时加载。
我觉得最好也最简单的一个例子就是tableView中图片的加载显示了, 一个延时加载, 避免内存过高,一个异步加载,避免线程堵塞提高用户体验。
5. UITableView重用机制?
UITableView 通过重用单元格来达到节省内存的目的: 通过为每个单元格指定一个重用标识符,即指定了单元格的种类,当屏幕上的单元格滑出屏幕时,系统会把这个单元格添加到重用队列中,等待被重用,当有新单元格从屏幕外滑入屏幕内时,从重用队列中找看有没有可以重用的单元格,如果有,就拿过来用,如果没有就创建一个来使用。
6. 如何高性能的给 UIImageView 加个圆角?
不好的解决方案:使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现。
self.view.layer.cornerRadius = 5.0f;
self.view.layer.masksToBounds = YES;
正确的解决方案:使用绘图技术
- (UIImage *)circleImage {
// NO代表透明
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
// 获得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 添加一个圆
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
// 裁剪
CGContextClip(ctx);
// 将图片画上去
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭上下文
UIGraphicsEndImageContext();
return image;
}
还有一种方案:使用了贝塞尔曲线"切割"个这个图片, 给UIImageView 添加了的圆角,其实也是通过绘图技术来实现的。
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
7. 谈谈 UITableView 的优化
1). 正确的复用cell。
2). 设计统一规格的Cell
3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
4). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
5). 减少子视图的层级关系
6). 尽量使所有的视图不透明化以及做切圆操作。
7). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。
8). 使用调试工具分析问题。
8. 如何实行cell的动态的行高
如果希望每条数据显示自身的行高,必须设置两个属性,1.预估行高,2.自定义行高。
设置预估行高 tableView.estimatedRowHeight = 200。
设置定义行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。
如果要让自定义行高有效,必须让容器视图有一个自下而上的约束。
9. 内存管理有哪几种?
Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。
1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。
2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。
3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。
有一个很经典的面试题,考察自动释放池的如下:
for (int i = 0; i < MAXFLOAT; i++) {
NSString *string = @"stdy";
string = [string lowercaseString];
string = [string stringByAppendingString:@"123"];
NSLog(@"--%@", string);
}
上述的这种写法,会使内存慢慢增加,如何解决呢,面试官想要的答案就是用自动释放池,你也可以改成其他的,但不是面试官要的,你懂的,修改如下:
for (int i = 0; i < MAXFLOAT; i++) {
@autoreleasepool {
NSString *string = @"stdy";
string = [string lowercaseString];
string = [string stringByAppendingString:@"123"];
NSLog(@"--%@", string);
}
}
9.1 什么时间会创建自动释放池?
从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件,运行循环检测到事件并启动后,就会创建自动释放池。
子线程的 runloop 默认是不工作,无法主动创建,必须手动创建。自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如:自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。
但对于 blockOperation
和invocationOperation
这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。
@autoreleasepool 当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。
如果在一个vc的viewDidLoad中创建一个 Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了。
10. 什么会造成离屏渲染?
GPU屏幕渲染有两种方式:
(1)On-Screen Rendering (当前屏幕渲染)
指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行。
(2)Off-Screen Rendering (离屏渲染)
指的是在GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作。
下面的情况或操作会引发离屏渲染:
- 为图层设置遮罩(layer.mask)
- 将图层的layer.masksToBounds / view.clipsToBounds属性设置为true
- 将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0
- 为图层设置阴影(layer.shadow *)。
- 为图层设置layer.shouldRasterize=true
- 具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的图层
- 文本(任何种类,包括UILabel,CATextLayer,Core Text等)。
- 使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现。
优化方案
1). 圆角优化
方案1 :使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角
方案2 :使用CAShapeLayer和UIBezierPath设置圆角
2). shadow优化
对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优化性能,能大幅提高性能
其他优化
当我们需要圆角效果时,可以使用一张中间透明图片蒙上去使用ShadowPath指定layer阴影效果路径;
使用异步进行layer渲染(Facebook开源的异步绘制框架AsyncDisplayKit)设置layer的opaque值为YES,减少复杂图层合成尽量使用不包含透明(alpha)通道的图片资源;
尽量设置layer的大小值为整形值;
直接让美工把图片切成圆角进行显示,这是效率最高的一种方案很多情况下用户上传图片进行显示,可以让服务端处理圆角使用代码手动生成圆角Image设置到要显示的View上;
利用UIBezierPath(CoreGraphics框架)画出来圆角图片
11. 说一下NSTimer在使用的时候内存泄漏的分析?
NSTimer必须与RunLoop搭配使用,因为其定时任务的触发基于RunLoop。
NSTimer使用常见的Target-Action模式。由于RunLoop会强引用timer,timer会强引用Target,容易造成循环引用、内存泄露等问题。
12. loop 强引用timer, timer 强引用 target,如果不能释放,会造成内存泄漏,有一个面试官问如果在target中传入weak的self,那么可以解决循环引用问题吗?
答案:否
Target强引用or弱引用Timer并不是问题的关键,问题的关键是:一定要在Timer使用完毕调用invalidate使之失效(手动调用or系统自动调用),Timer从RunLoop中被移除并清除强引用,这个操作可打破引用1、2,而引用3是强弱引用已经不重要了。
NSTimer一共有三种初始化方案:init开头的普通创建方法、timer开头的类工厂方法、scheduled开头的类工厂方法。前两者需要手动加入RunLoop中,后者会自动加入当前RunLoop的DefaultMode中。
13. 对于NSTimer,面试官还会问,它是否是时间准确呢?
大家可能都知道是时间不准确的,因为受RunLoop的影响,那么GCD中也有延时,如果用GCD来做延时,那时间准确吗?
GCD的time是准确的,GCD 的线程管理是通过系统来直接管理的。
GCD Timer 是通过 dispatch port 给 RunLoop 发送消息,来使 RunLoop 执行相应的 block。
如果所在线程没有 RunLoop,那么 GCD 会临时创建一个线程去执行 block,执行完之后再销毁掉,因此 GCD 的 Timer 是不依赖 RunLoop 的。
14. 大次数循环内存暴涨问题
for (int i = 0; i < 100000; i++) {
NSString *string = @“Abc”;
string = [string lowercaseString];
string = [string stringByAppendingString:@“xyz”];
NSLog(@"%@", string);
}
改:
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *string = @“Abc”;
string = [string lowercaseString];
string = [string stringByAppendingString:@“xyz”];
NSLog(@"%@", string);
}
}
15. 内存泄漏可能会出现的几种原因,聊聊你的看法?
15.1 非OC对象如何处理?
15.2 地图类内存若泄漏,如何处理?
15.3 若常用框架出现内存泄漏如何处理?
iOS 出现内存泄漏的几种原因
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``