1.动画了解吗?
2.请求序列化?
3.APP启动流程的优化?
一、APP启动过程
什么是镜像?
二进制文件.o、动态链接库dylib、资源文件bundle(指正在运行时使用dlopen()加载)
1.main()前阶段:加载应用的可执行文件.o、动态链接器dyld、动态链接库dylib。
2.main()阶段:
dyld调用main()、
UIApplicationMain()、
applicationWillFinishLaunching、
didFinishLaunchingWithOptions
二、main()的过程和可优化项
1.load dylibs
1.1、尽量不使用内嵌的dylib(我们自己写的framework)和无用的dylib,加载内嵌的dylib性能开销大;
1.2、合并已有的dylib和使用静态库,减少dylib的使用个数
1.3、懒加载dylib,但是要注意dlopen()可能造成一些问题,且实际上懒加载做的工作会更多
检查framework应当设为optional和required,如果该framework在当前APP支持的所有iOS
系统版本都存在,那么就设为required,否则就设为optional
2.rebase-Bind
Rebase在前,Bind在后,rebase做的是将镜像读入内存,修正镜像内部的指针,性能消耗主要在IO上。
Bind做的是查询符号表,设置指向镜像外部的指针,性能消耗主要在CPU计算,所以指针数量越少越好。
2.1、减少OC类(Class)、方法(Selector)、分类(category)的数量
2.2、减少C++虚函数的数量,创建虚函数表有开销
2.3、使用swift structs,内部做了优化,符号数量更少
2.4、压缩资源图片、删除无用的图片(IO操作)
3.ObjC setup
大部分ObjC初始化工作已经在Rebase-Bind阶段做完了,这一步dyld会注册所有声明过的ObjC类,将分类插入到
类的方法列表里,再检查每个Selector的唯一性,这一步没有什么优化可做,Rebase-Bind阶段优化好了,这一
步的耗时也会减少。
4.Initializers
dyld开始运行程序的初始化函数,调用每个Objc类和分类的+load方法,调用C/C++的构造器函数(用attribbute
(constructor)修饰的函数),创建非基本类型的C++静态全局变量(通常是类或结构体),Initializers后dyld
开始调用main().在这一步我们可以做的优化有:
4.1、少在类的load方法里做事情,尽量把这些事情推迟到+ initialize
4.2、减少构造器函数个数,在构造器函数里少做些事情
4.3、减少C++静态全局变量的个数
三、main()阶段的可优化项
这一阶段的优化主要是减少didFinishLaunchingWithOptions方法里的工作,在didFinishLaunchingWithOptions
方法里,我们会创建应用的window,指定其rootViewController,调用window的makeKeyAndVisible方法
让其可见。由于业务需要,我们会初始化各个二、三方库,设置系统UI风格,检查是否需要显示引导页、是否需要登录、
是否有新版本等,由于历史原因,这里的代码容易变得比较庞大,启动耗时难以控制。所以满足业务需要的前提下,
didFinishLaunchingWithOptions在主线程里做的事情越少越好。
1、梳理各个二、三方库,找到可以延迟加载的库做延迟加载处理,比如放到首页控制器或tabBar控制器的viewDidAppear方法里
2、梳理业务逻辑,把可以延迟执行的逻辑做延迟执行处理,比如检查新版本、注册推送通知等逻辑
3、避免复杂、多余的计算
4、采用性能更好的API
5、避免在首页控制器的viewDidLoad和viewWillAppear做太多的事情,这2个方法执行完,首页控制器才能显示,
部分客户延迟创建的视图做延迟创建、懒加载处理。
6、首页控制器、注册登录页面用纯代码来构建
7、延迟持久化数据的读取到内存
四、启动优化总结
main()前阶段的优化
1.1、尽量不使用内嵌的dylib(我们自己写的framework)和无用的dylib,加载内嵌的dylib性能开销大;
1.2、合并已有的dylib和使用静态库,减少dylib的使用个数
1.3、懒加载dylib,但是要注意dlopen()可能造成一些问题,且实际上懒加载做的工作会更多
检查framework应当设为optional和required,如果该framework在当前APP支持的所有iOS
系统版本都存在,那么就设为required,否则就设为optional
2.1、减少OC类(Class)、方法(Selector)、分类(category)的数量
2.2、减少C++虚函数的数量,创建虚函数表有开销
2.3、使用swift structs,内部做了优化,符号数量更少
2.4、压缩资源图片、删除无用的图片(IO操作)
4.1、少在类的load方法里做事情,尽量把这些事情推迟到+ initialize
4.2、减少构造器函数个数,在构造器函数里少做些事情
4.3、减少C++静态全局变量的个数
main()阶段的优化
1、梳理各个二、三方库,找到可以延迟加载的库做延迟加载处理,比如放到首页控制器或tabBar控制器的viewDidAppear方法里
2、梳理业务逻辑,把可以延迟执行的逻辑做延迟执行处理,比如检查新版本、注册推送通知等逻辑
3、避免复杂、多余的计算
4、采用性能更好的API
5、避免在首页控制器的viewDidLoad和viewWillAppear做太多的事情,这2个方法执行完,首页控制器才能显示,
部分客户延迟创建的视图做延迟创建、懒加载处理。
6、首页控制器、注册登录页面用纯代码来构建
7、延迟持久化数据的读取到内存
4.列表的优化?
(1)复用cell
(2)cell高度。定高cell用rowHeight而不是heightForRow,不定高cell缓存cell的高度。
(3)cell渲染。
cell的opaque值设为YES,背景色不要使用clearColor,不要使用阴影渐变;
减少subviews的个数和层级,多用drawRect绘制元素,在rect范围之外的区域我们不需要进行绘制,否则会消耗相当大的资源;
避免calayer的特效阴影颜色、偏移、透明度、圆角;
不要给cell动态添加subview,在初始化cell的时候就将所有需要展示的添加完毕,然后根据需要来设置hide属性显示和隐藏。
(4)离屏渲染。
使用贝塞尔曲线UIBezierPath和Core Graphics画出一个圆角;
使用CAShapeLayer和UIBezierPath设置圆角;
美工、服务端处理圆角。
5.NSArray和NSSet的区别?
内存中的存储方式
NSArray是有序集合在内存中的存储方式是连续的,
NSSet是无序集合在内存中的存储方式是不连续的
元素查找效率
NSArray 遍历查找
NSSet 哈希查找效率高
6.swift和OC有什么区别?
swift OC
语言特性:静态语言,更加安全。 动态语言,不安全
语法:精简 冗长
命名空间:有 无
方法调用:直接调用,函数表调用,消息转发。消息转发。
泛型/元组/高阶函数:有。 无
语言效率:性能更高,速度更快。略低。
文件特性:.swift单文件。 .h/.m包含头文件
编程特性:函数式、响应式编程。 面向对象编程。
7.如何扩大按钮的响应范围?
不改变大小的情况下,扩大按钮点击范围的两种方式
(1)新建一个类继承自UIButton然后重写UIButton的pointInside:WithEvent方法,在里边改变它的内边距
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGRect btnBounds = self.bounds;
btnBounds = CGRectInset(btnBounds, -10, -10)//扩大按钮的点击范围改为负值
return CGRectContainsPoint(btnBounds,point) //若点击的点在新的bounds里,就返回YES
}
(2)给UIButton类添加类别category,然后重写UIButton的pointInside:WithEvent方法
-(void)setEnLargeEdge:(CGFloat)size
{
objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
// 设置可点击范围到按钮上、右、下、左的距离
-(void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left
{
objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(CGRect)enlargedRect
{
NSNumber *topEdge=objc_getAssociatedObject(self, &topNameKey);
NSNumber *rightEdge=objc_getAssociatedObject(self, &rightNameKey);
NSNumber *bottomEdge=objc_getAssociatedObject(self, &bottomNameKey);
NSNumber *leftEdge=objc_getAssociatedObject(self, &leftNameKey);
if(topEdge && rightEdge && bottomEdge && leftEdge){
return CGRectMake(self.bounds.origin.x-leftEdge.floatValue,
self.bounds.origin.y-topEdge.floatValue,
self.bounds.size.width+leftEdge.floatValue+rightEdge.floatValue,
self.bounds.size.height+topEdge.floatValue+bottomEdge.floatValue);
}else{
return self.bounds;
}
}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGRect rect=[self enlargedRect];
if(CGRectEqualToRect(rect, self.bounds))
{
return [super pointInside:point withEvent:event];
}
return CGRectContainsPoint(rect, point)?YES:NO;
}
[self.button2 setEnlargeEdgeWithTop:50 right:50 bottom:200 left:50];
8.控制器的生命周期?
loadView、===viewDidLoad===、viewWillAppear、viewDidAppear、viewWillDisappear、dealloc
9.点击屏幕中一个按钮都发生了什么?
屏幕会产生一个MachPort,基于MachPort最终会转成source1可以把主线程唤醒运行。
10.求二叉树的前序遍历?用递归和迭代两种方式实现?
二叉树的前序遍历访问顺序:根节点,前序遍历左子树,前序遍历右子树。
7
4 9
2 5 8 11
1 3 10 12
访问顺序是:7、 左4、2、1、5、 右9、8、11、10、12
11.16瓶水其中1瓶水有毒如何找出这瓶水?水可以无限分解。分组
12.alloc的底层实现
alloc -> _objc_rootAlloc -> callAlloc -> class_createInstance -> _class_createInstanceFromZone
instanceSize:计算需要的内存空间大小,并会进行内存对齐
calloc:开辟指定大小内存空间
initInstanceIsa:将calloc开辟的内存空间与当前的Class进行绑定
13.init底层实现
init是个工厂模式,什么都没做,交给子类可以自定义去重写
14.new的底层实现
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
15.Swift常用第三方库
Alamofire:类似AFN
Kingfisher:SD
SwiftyJSON
KakaJSON
ReachabilitySwift:Reachability
MonkeyKing:社会化分享
ObjectMapper:字典模型互转
SnapKit:Masonry
16.swift和iOS是如何互相调用?
swift调用OC:将暴露的OC头文件写入TestSwift2-Bridging-Header.h中
OC调用swift:用@objcMembers、@objc、NSObject修饰类,并引入桥接文件 项目名-Swift.h
17.GCD实现原理?
a.GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?) 而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。
b.如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。
c.如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。
d.就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开5~8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3~5条最为合理。
18.FMDB如何保证线程安全?
创建数据库的队列
19.如何保证数组的线程安全?
@synchronized
20.weak为何会被置为nil?
21.iOS的内存时如何管理的?
22.如何加快视频的下载和写入速度?
服务端、移动端优化。
23.耗电优化?
耗电的主要来源:CPU GPU处理、网络、定位、图像
(1)尽可能的让CPU和GPU少做事情
1>少用定时器
2>优化I/O操作:
a.尽量不要频繁写入小数据,最好批量一次性写入。
b.读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API,用dispatch_io系统会优化磁盘访问。
c.数据量比较大时,建议使用数据库。
(2)网络优化
1>减少、压缩网络数据
2>如果多次请求的结果是相同的,尽量使用缓存
3>使用断点续传,否则网络不稳定时可能多次传输相同的内容
4>网络不可用时,不要尝试执行网络请求
5>让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间。
6>批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封不要一封一封地下载。
(3)定位优化
1>如果只是需要快速确定用户位置,最好用CLLocationManager的requestLocation方法,定位完成后会自动让定位硬件断电;
2>如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务;
3>尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest
4>需要后台定位时,尽量设置pauseLocationUpdatesAutomatically = YES,如果用户不太可能移动的时候系统会自动暂停位置更新;
5>尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion
(4)硬件检测优化
用户移动、摇晃、倾斜设备时,会产生动作motion事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测,在不需要检测的场合,应该及时关闭这些硬件。
24.安装包瘦身?
安装包主要由可执行文件、资源组成。
(1)资源:图片、音频、视频等。采取无损压缩,去除没有用到的资源。
(2)可执行文件瘦身:
1>编译器优化:Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES,去掉异常支持Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO、Other C Flags添加-fno-exceptions
2>利用AppCode检测未使用的代码:菜单栏->Code->Inspect Code
3>编写LLVM插件检测出重复代码、未被调用的代码
4>生成LinkMap文件,可以查看可执行文件的具体组成,可借助第三方工具(https://github.com/huanxsd/LinkMap)解析LinkMap文件