Description
请你出一套iOS面试题
底层
- 1.1 在一个对象的方法里面: self.name = "object"; 和 name = "object"有什么不同吗?
参考解答: self.name ="object":会调用对象的setName()方法; name =“object":会直接把object赋值给当前对象的name属性。
- 1.2 对于语句
NSString*obj = [[NSData alloc] init];
obj在编译时和运行时分别时什么类型的对象?
参考解答: 编译时是NSString的类型, 运行时是NSData类型的对象
- 1.3 想到一个关于
Runloop
的面试题:
在程序通过main()
函数启动后,在主线程开启UIApplicationMain
的Runloop
后,在下面的
main()
函数里创建了一个autoreleasepool
,请问如果程序不退出,main()
函数永远不会返回,那么程序中(比如一个VC中)创建的对象是否就永远不会被释放?
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
参考解答: 在程序创建的对象,不是被main()
函数中的autoreleasepool
管理的,
而是由主线程的Runloop
管理,在某次Runloop
循环中,Runloop
休眠之前调用了对象的release
方法释放。
在每次Runloop
循环中,Runloop
休眠之前会调用了对象的release
方法释放AutoreleasePoolPage
中边界内的对象。
- KVO · Issue #5 · FrizzleFur/DailyLearning
- Runtime · Issue #3 · FrizzleFur/DailyLearning
- Block · Issue #7 · FrizzleFur/DailyLearning
Cocoa
- 2.1 这个写法会出什么问题:@Property (copy) NSMutableArray array
参考解答: 两个问题: 1、添加,删除,修改数组内的元素的时候程序会因为找不到对应的方法而崩溃.因为copy就是复制一个不可变NSArray的对象,使用了atomic属性会严重影响性能。
- 2.2 copy 和strong的使用?
参考解答: 我们在声明一个NSString属性时,对于其内存相关特性,通常有两种选择(基于ARC环境): strong与copy。那这两者有什么区别呢?什么时候该用strong,什么时候该用copy呢?
-
由于NSMutableString是NSString的子类,所以一个NSString指针 可以指向NSMutableString对象,让我们的strongString指针指向一 个可变字符串是OK的。
-
而上面的例子可以看出,当源字符串是NSString时,由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。当源字符串是NSMutableString时,strong属 性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。
-
这里还有一个性能问题,即在源字符串是NSMutableString, strong是单纯的增加对象的用计数,而copy操作是执行了一次深拷贝,所以性能上会有所差异。而如果源字符串是NSString时,则没有这个问题。
-
所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。
-
2.3 __strong的使用场景?
- 2.4 谈谈AutoLayout?
参考解答: AutoLayout
,是苹果公司提供的一个基于约束布局,动态计算视图大小和位置的库,并且已经集成到了Xcode开发环境里。
-
AutoLayout的实现原理
- AutoLayout是基于Cassowary算法,
- Cassowary 算法让视图位置可以按照一种简单的布局思路来写,这些简单的相对位置描述可以在运行时动态地计算出视图具体的位置。视图位置的写法简化了,界面相关代码也就更易于维护。
- Cassowary 能够有效解析线性等式系统和线性不等式系统,用来表示用户界面中那些相等关系和不等关系。基于此,Cassowary 开发了一种规则系统,通过约束来描述视图间的关系。约束就是规则,这个规则能够表示出一个视图相对于另一个视图的位置。
- Auto Layout 不只有布局算法 Cassoway,还包含了布局在运行时的生命周期等一整套布局引擎系统,用来统一管理布局的创建、更新和销毁。了解 Auto Layout 的生命周期,这一整套布局引擎系统叫作 Layout Engine ,是 Auto Layout 的核心,主导着整个界面布局。
- 每个视图在得到自己的布局之前,Layout Engine 会将视图、约束、优先级、固定大小通过计算转换成最终的大小和位置。在 Layout Engine 里,每当约束发生变化,就会触发 Deffered Layout Pass,完成后进入监听约束变化的状态。当再次监听到约束变化,即进入下一轮循环中。整个过程如下图所示:
-
AutoLayout性能
- 兄弟视图之间没有关系时,是不会出现性能呈指数增加问题的。这就表示 Cassowary 算法在添加时是高效的。但如果兄弟视图间有关系的话,在视图遍历时会不断处理和兄弟视图间的关系,这时会有修改更新计算。
- iOS 12 之前,Auto Layout 并没有用上 Cassowary 高效修改更新的特性。很多约束变化时都会重新创建一个计算引擎 NSISEnginer 将约束关系重新加进来,然后重新计算。结果就是,涉及到的约束关系变多时,新的计算引擎需要重新计算,最终导致计算量呈指数级增加。
- iOS12 的 Auto Layout 更多地利用了 Cassowary 算法的界面更新策略,使其真正完成了高效的界面线性策略计算。
- UIStackView: iOS9后,在 Auto Layout 基础上模仿前端 Flexbox 布局思路的 UIStackView;可以提高布局效率。
-
AutoLayout关于更新的几个方法的区别
- setNeedsLayout:告知页面需要更新,但是不会立刻开始更新。执行后会立刻调用layoutSubviews。
- layoutIfNeeded:告知页面布局立刻更新。所以一般都会和setNeedsLayout一起使用。如果希望立刻生成新的frame需要调用此方法,利用这点一般布局动画可以在更新布局后直接使用这个方法让动画生效。
- layoutSubviews:系统重写布局
- setNeedsUpdateConstraints:告知需要更新约束,但是不会立刻开始
- updateConstraintsIfNeeded:告知立刻更新约束
- updateConstraints:系统更新约束
参考:Masonry · ming1016/study Wiki
性能优化
TableView性能优化
- 行高一定要缓存!
- 不要动态创建子视图所有的子视图都预先创建,如果不需要显示可以设置hidden。
- 所有的子视图都应该添加到contentView上
- 所有的子视图都必须指定背景颜色,且所有的颜色都不要使用alpha
- cell栅格化
- 异步绘制
UITableView核心思想
- UITableView最核心的思想就是UITableViewCelI的重用机制。简单的理解就是: UITableView只会创建一屏幕(或一屏幕多一点)的UITableViewCell,其他都是从中取出来重用的。每当Cell滑出屏幕时,就会放入到一个集合(或数组) 中(这里就相当于一个重用池),当要显示某一位置的Cel时,会先去集合(或数组)中取,如果有,就直接拿来显示;如果没有,才会创建。这样做的好处可想而知,极大的减少了内存的开销。
tableView:cellForRowAtIndexPath:
和tableView:heightForRowAtIndexPath:
.- UITableView是继承自UIScrolView的,需要先确定它contentSize及每个Cell的位置,然后才会把重用的Cell放置到对应的位置。所以事实上,UITableView的回调顺序是先多次调用tableView:heightForRowAtIndexPath:以确定contentSize及Cel的位置,然后才会调用tableView:cellForRowAtIndexPath;,从而来显示在当前屏幕的Cell。
- 思路是把赋值和计算布局分离。这样让tableView:cellForRowAtIndexPath:方法只负责赋值,
- tableView:heightForRowAtIndexPath:方法只负责计算高度。
- 可以在获得数据后,直接先根据数据源计算出对应的布局,并缓存到数据源中,这样在tableView:heightForRowAtlndexPath:方法中就直接返回高度,而不需要每次都计算了。
UlTableView的优化总结
- 提前计算并缓存好高度(布局) , 因heightForRowAtIndexPath:是调用最频繁的方法;
- 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
- 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用! (SDWeblmage已经实现异步加载,配合这条性能杠杠的)。正确使用reuseldentifier来重用Cells
- 尽量使所有的view opaque, 包括Cell自身
- 尽量少用或不用透明图层如果Cell内现实的内容来自web,使用异步加载,缓存请求结果减少subviews的数量
- 在heightForRowAtIndexPath:中尽量不使用cellForRowAtIndexPath:, 如果你需要用到它,只用一次然后缓存结果
- 尽量少用addView给Cell动态添加View,可以初始化时就添加,然后通过hide来控制是否显示在使用UITableView的时候,有的时候你会碰到Cell卡顿,图片加载慢,使得滑动cel时变得不那么流畅,这些都会影响用户体验,拉低整体app的效果。
- 使用cell重用机制,尽可能快地返回重用cell实例这点大家都应该比较清楚,使用reuse机制能大幅降低创建cell所带来的损耗,这就要各位在UITableView的dataSource中实现的
tableView:cellForRowAtIndexPath:
方法。只是有一点,尽量不要在此时绑定数据,因为目前在屏幕上还没有cell,可以在UITableView的delegate方法tableView:willDisplayCell:forRowAtIndexPath:
中进行数据的填充。需要说明的是,你可能会动态计算cel高度,但最好是不要选择Autolayout。使用Autolayout后,会根据cel的子视图使得求解的约束也越多,从而降低计算速度,影响滑动时FPS。所以,为了使tableview平滑滚动,请使用动态计算高度,不要选择Autolayout。 - cell的subViews的各级opaque值要设成YES,opaque用于辅助绘图系统,表示UIView是否透明。 在不透明的情况下,渲染视图时需要快速地渲染,以提高性能。渲染最慢的操作之一是混合(blending)。提高性能的方法是减少混合操作的次数,其实就是GPU的不合理使用,这是硬件来完成的(混合操作由GPU来执行,因为这个硬件就是用来做混合操作的,当然不只是混合)。优化混合操作的关键点是在平衡CPU和GPU的负载。还有就是cell的layer的
shouldRasterize
要设成YES。 - 尽量少用addView给Cell动态添加View,可以初始化时就添加,然后通过hide来控制是否显示在使用UITableView的时候,有的时候你会碰到Cell卡顿,图片加载慢,使得滑动cel时变得不那么流畅,这些都会影响用户体验,拉低整体app的效果。
- 在绘制字符串时,尽可能使用drawAtPoint:withFont: , 在绘制图片,尽量使用
drawAtPoint
- 不要使用更复杂的
drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withFont:(UIFont*)font
- lineBreakMode:(UIL ineBreakModel)lineBreakMode;如果要绘制过长的字符串,建议先截断,然后使用drawAtPoint:withFont:方法绘制。
- 不要使用drawInRect,因为它在绘制过程中对图片放缩大小,消耗CPU。
- 其实,最快的绘制就是你不要做任何绘制。有时,通过UIGraphicsBeginlmageContextWithOptions()或者CGBitmapContextCeate()创建位图会显得更有意义,从位图上面抓取图像,并设置为CALayer的内容。如果你必须实现-drawRect;,并且你必须绘制大量的东西,这将占用时间。
- 不要使用更复杂的
- cell异步加载图片以及缓存
- 对于cell里的图片采用异步的方式,加载好后缓存。当图片还没有请求加载时,你可以使用默认图片。
- 一旦你缓存好图片,使用cell的重用机制时就可以从关联好的视图源里以相应的url来找到对应的缓存图片,缓存大大节省重复请求图片的耗损。只是你要考虑内存级别的缓存还是磁盘级别的缓存,记得使用完毕清缓存哦! (记得减少内存级别的拷贝)
- 为了防止图片多次下载,我们需要对图片做缓存,缓存分为内存缓存于沙盒缓存,我们当然两种都要实现。
- 一般情况下在我们会在
cellForRow
方法里面设置cell的图片数据源,也就是说如果一个cell的imageview对象开启了一个下载任务,这个时候该cell对象发生了重用,新的image数据源会开启。 - 另外的一个下载任务,由于他们关联的imageview对象实际。上是同一个cell实例的
imageview
对象,就会发生2个下载任务回调给同一个imageview对象。这个时候就有必要做一些处理,避免回调发生时,错误的image数据源刷新了UI。 - 在我们向下滑动
tableview
的时候我们需要手动去取消掉下载操作,当用户停止滑动,再去执行下载操作 - 如果快速滑下去,然后又滑回来的话,图片是过了一会才显示出来,这是因为快速滑动的时候,旧数据源的下载任务被取消掉了。
- 异步下载图片我们用的是NSOperation,并且创建一个全局的queue来管理下载图片的操作。在把图片显示到Cell上之前先判断内存中(images字典中)有没有图片
- 如果有,则取出ur对应的图片来显示,如果没有,再去沙盒缓存中查看,当然存到沙盒中都是NSData。如果沙盒缓存中有,我们取出对应的数据给Cell去显示
- 如果沙盒中也没有图片,我们先显示占位图片。再创建operation去执行下载操作了。当然在创建operation之前,我们要判断这个operation操作是否存在。
- 如果没有下载操作,我们才需要真正的去创建operation执行下载。创建好下载操作之后应该把该操作存放到全局队列中去异步执行,同时吧操作放入operations字典中记录下来。
- 下载完成之后:
- 把下载好的图片放到内存中、同时存到沙盒缓存中,执行完上面的操作之后回到主线程刷新表格,
- 从operations字典中移除下载操作(防止operations越来越大,同时保证下载失败后,能重新下载)
网络请求
网络Http请求 · Issue #9 · FrizzleFur/DailyLearning
架构设计
设计模式
- ViewModel+RAC · Issue #2 · FrizzleFur/DailyLearning
- 看懂UML类图和时序图 · Issue #18 · FrizzleFur/DailyLearning
组件化
知名框架原理
- Alamofire 的设计之道 · Issue #17 · FrizzleFur/DailyLearning
- SDWebImg框架学习 · Issue #22 · FrizzleFur/DailyLearning
CS基础
职业
- 你认为你的最大优势点在哪?有什么不足或者需要提高的地方?