2017年iOS面试题整理

2017年iOS面试题整理_第1张图片
78f87bfced337de9b9147a38ea7afead.jpg

声明:本篇面试题整理的是来自于https://www.jianshu.com/p/56e40ea56813的题目。答案参照的是https://www.jianshu.com/p/dfc4ca0fdf47,在整理期间觉得自己的答案和参照的答案差不多的我就直接拷贝了过来,在此感谢出题者和解答者。这篇答案有自己的理解也有摘自网上其他人理解。再次感谢所有的知识分享者。对答案有不同见解的同学,留言即可,咱一起研究。现整理如下:

2017年iOS面试题整理_第2张图片
aa21f50b7385b7f1fd668f38448329cf.jpg

基础部分

1、为什么说OC是一门动态语言?

答:动态语言是指程序在运行时可以改变其结构,新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如Ruby,Python等就是动态语言,而c,c++等语言则不属于动态语言。
Obejct-C 提供了 Objc Runtime 机制,将很多静态语言在编译和链接时期做的事放到了运行时来处理。
三个动态特征:
1.动态类型:如id类型。实际上静态类型因为其
固定性和可预知性而使用的特别广泛。静态类型是强类型,动态类型是弱类型,运行时决定接收者。
2.动态绑定:让代码在运行时判断需要
调用什么方法,而不是在编译时。与其他面向对象语言一样,方法调用和代码并没有在编译时连接在一起,而是在消息发送时才进行连接。运行时决定调用哪个方法。
3.动态载入。让程序在运行时添加代码模块以及其他资源。用户可以根据需要执行一些可执行代码和资源,而不是在启动时就加载所有
组件。可执行代码中可以含有和程序运行时整合的新类。

2、讲一下MVC和MVVM,MVP?

答:这三种架构模式都由以下三个实体组成:
Model:负责主要的数据或者操作数据的数据访问层。数据层
View:负责展示层
Controller/Presenter/ViewModel:负责协调 Model 和 View,通常根据用户在View上的动作在Model上作出对应的更改,同时将更改的信息返回到View上。
MVVM是Model-View-ViewModel的简写,和MVC模式一样主要目的就是分离view和model。
MVC回顾
MVC存在的问题:
模型代码很少;控制器代码会很多;不好测试。
MVVM:
1、View和Controller视为一个组件,view和controller都不能直接引用model,而是引用视图模型(V iewModel).
2、viewModel 封装业务逻辑处理、封装网络处理,封装数据缓存等
使用注意:
1、view引用viewModel,但反过来不行。
2、vviewModel引用model,但反过来不行。
优点:
1、低耦合。view可以独立于model的变化和修改,一个viewModel可以绑定到不同的view上。
2、可重用性。可以把一个视图逻辑放到viewmodel里,让很多view重用这段逻辑
3、独立开发。开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面的设计。
4、可测性。通常界面是比较南测试的,而MVVM可以针对ViewModel来进行测试。

3、为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?

答:weak修饰词表示对象对改属性指向的对象是一种非拥有关系,delegate的销毁由外部控制。 防止循环引用。
首先Delegate是委托的意思,在oc中则是一个类委托另一个类实现某个方法。当一个对象接受到某个事件或者通知的时候, 会向它的Delegate对象查询它是否能够响应这个事件或者通知,如果可以这个对象就会给它的Delegate对象发送一个消息。
Datasource字面是数据源,一般和Delegate伴生,这时数据源处理的数据就是Delegate中发送委托的类中的数据,并通过Datasource发送给接受委托的类。
区别:block 和 delegate 都可以通知外面。block 更轻型,使用更简单,能够直接访问上下文,这样类中不需要存储临时数据,使用 block 的代码通常会在同一个地方,这样读代码也连贯。delegate 更重一些,需要实现接口,它的方法分离开来,很多时候需要存储一些临时数据,另外相关的代码会被分离到各处,没有 block 好读。
通知:则多用于一对多的情况下在不需要拿到某些对象的时候的数据传递。
代理:通常一对一的数据传递,代理更注重过程的传递,比如网络请求中回调的是否开始、进度、成功、失败等。
Block:也是一对一的数据传递,相比代理写法简单,更注重结果的传输。

4、属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?

答:@property = ivar + getter + setter; 实例变量+get方法+set方法,也就是说使用@property 系统会自动生成setter和getter方法;对基本数据类型来说属性的默认关键字是(atomic,readwrite,assign),对对象来说默认值(atomic,readwrite,strong),MRC中strong为retain。
@dynamic告诉编译器不要自动生成属性的setter & getter方法,由我们手动实现存取方法。
@synthesize (Xcode6以后省略这个了, 默认在 @implementation .m中添加这个@synthesize xxx = _xxx; 编译器期间,让编译器自动生成getter/setter方法。
当有自定义的存或取方法时,自定义会屏蔽自动生成该方法。

5、NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)。

答:因为NSString有对应的子类可变类型NSMutableString,且父类指针可以指向子类对象,如果不用copy而用strong,当传入的字符串是可变类型时, 并且这个字符串有改动时,就会导致属性的值也会改变。当然如果传入的字符串确定为不可变的,那么属性可以用strong修饰。 一些可变的类型如NSMutableString、NSMutabeArray、NSMutableDictionary一定得需要strong修饰,因为【object copy】返回的对象是不可变的,这时对可变对象进行修改就会崩溃。

6、如何令自己所写的对象具有拷贝功能?

答:首先要让自己写的类遵守NSCoping或者NSMutableCoping协议,并且实现

7、可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?

答:可变集合类(NSMutableArray、NSMutableDictionary)的copy返回的是不可变对象,但是是深拷贝;mutablecopy返回的是可变对象,而且是单层深拷贝。
不可变集合类(NSArray/NSDictionary)的copy返回的不可变对象,都是浅拷贝; mutablecopy返回的可变对象,都是深拷贝。
集合的内容赋值虽说是深复制,但其实只是集合对象本身的深复制,集合内的元素还是浅拷贝,这种现象(one-level-copy)单层深拷贝,要是完成集合的完全拷贝,可以把集合归档成data,再解档返回一个新集合,这时候才是完全深拷贝。

8、为什么IBOutlet修饰的UIView也适用weak关键字?

答:因为父控件的subviews数组已经对他有一个强引用,不需要再强引用一次。

9、nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?

答:使用atomic会给属性的setter & getter方法加锁,防止读写的时候被另外一个线程读取造成数据错乱,atomic只保证属性的存取方法是线程安全的,并不保证整个对象都是线程安全。不过atomic可并不能完全保证线程安全。如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。

10、UICollectionView自定义layout如何实现?

答:1、重写prepareLayout方法,在这个方法中做一些初始化操作,要调用【super prepareLayout】2、重写layoutAttributesForElementsInRect:方法,这个方法返回的是一个数组,元素时每个cell的布局属性。

11、用StoryBoard开发界面有什么弊端?如何避免?

答:优点:1、节省时间,效率高。2、各个界面间的切换关系一目了然, 逻辑清晰。3、可以使用静态cell,对于开发一些Cell不多,但每个Cell都不一样的列表类设置界面会比较方便。
缺点:1、xib 对版本管理是灾难。storyboard 实际上的多个 xib 的集合,所以更容易让多人编辑产生冲突。而虽然它们是 xml 格式,但是冲突解决起来还是不如代码那么容易。2、苹果对 xib, storyboard 的设计中带有当前电脑的操作系统版本和 Xcode 版本。所以如果两个协作的开发者电脑操作系统或 Xcode 有不一样的话,每次打开必定会修改这个文件。另外即使操作系统版本和 Xcode 版本一样,有些时候打开看也会造成一些自动的修改。

12、线程间通信?

答:
1、GCD,更新UI时,在子线程回到主线程刷新UI,dispatch_async(dispatch_get_main_queue(), ^{//数据执行完毕回调到主线程操作UI更新数据});
2、perfermselecter选择器实现线程通信, //数据请求完毕回调到主线程,更新UI资源信息 waitUntilDone 设置YES ,代表等待当前线程执行完毕,[self performSelectorOnMainThread:@selector(dothing:) withObject:@[@"1"] waitUntilDone:YES];//将当前的逻辑转到后台线程去执行[self performSelectorInBackground:@selector(dothing:) withObject:@[@"2"]];
3、自己定义线程,将当前数据转移到指定的线程内去通信操作,[self performSelector:@selector(dothing:) onThread:thread withObject:nil waitUntilDone:YES];

13、GCD的一些常用的函数?(group,barrier,信号量,线程同步)

答:
1、dispath_get_main_queue()主队列 串行;
2、dispath_get_global_queue()全局队列 并行;
3、dispatch_queue_create(<#const char * _Nullable label#>,<#dispatch_queue_attr_t _Nullable attr#>)自定义队列 可自定义并行DISPATH_QUEUE_CONCURRENT串行DISPATH_QUEUE_SERIAL;
4、dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>) 同步添加任务到队列;
5、dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>) 异步添加任务到队列;
6、dispatch_suspend(<#dispatch_object_t _Nonnull object#>)挂起队列;
7、dispatch_resume(<#dispatch_object_t _Nonnull object#>)恢复队列;
8、dispatch_semaphore_create(<#long value#>)创建信号量;
9、dispatch_semaphore_wait(<#dispatch_semaphore_t _Nonnull dsema#>, <#dispatch_time_t timeout#>)等待信号量
10、dispatch_semaphore_signal(<#dispatch_semaphore_t _Nonnull dsema#>)发出信号量;
11、dispatch_group_create() 创建队列组;
12、dispatch_group_async(<#dispatch_group_t _Nonnull group#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)异步添加队列到组中
13、dispatch_group_notify(<#dispatch_group_t _Nonnull group#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)队列组中任务完成时回调
14、dispatch_barrier_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)和dispatch_barrier_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)等待所有位于barrier前的函数执行完毕后执行,并在barrier函数执行完毕后执行后面的函数,只在并发自定义队列中可用(区别:dispatch_barrier_sync将自己的任务插入后需要等待自己的任务完成才会执行后面的函数,dispatch_barrier_async不需要等待自己的任务完成,插入后就可以执行后面的函数)。

15、如何使用队列来避免资源抢夺?

答:可以使用信号量。具体见GCD XCode 工程。

16、数据持久化的几个方案?

答:plist,NSUserDefault(本质也是plist),归档解档,sqlite(FMDB),CoreData。

17、说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?

答:第一次启动调用的方法是didFinishLaunching(程序启动)之后是DidBecomeActive(活跃状态);从前台到后台调用的方法是WillResignActive(将要结束活跃状态)之后是DidEnterBackground(已经进入后台);后台进入前台调用的方法是WillEnterForeground(即将进入前台)之后是DidBecomeActive(进入活跃状态)

18、NSCache优于NSDictionary的几点?

答:1、NSCache是线程安全的,NSMutableDictionary线程不安全,NSCache线程是安全的,Mutable开发的类一般都是线程不安全的。2、当内存不足时NSCache会自动释放内存(所以从缓存中取数据的时候总要判断是否为空)3、NSCache可以指定缓存的限额,当缓存超出限额自动释放内存缓存限额。4、苹果给NSCache封装了更多的方法和属性,比NSMutableDictionary的功能要强大很多。

19、重写description的有什么好处?

答:NSLog中(或lldb中使用po obj)输出一个对象本质就是输出[obj description]的返回值,重写该方法可以改变输出内容便于调试。

20、objc使用什么机制管理对象内存?

答:引用计数。分为MRC和ARC 。

中级

1、block的实质是什么?一共有几种block?都是什么情况下生成的?

答:OC中的block可以看作一个对象,因为block中含有isa指针,这个isa指针被初始化_NSConcreateStackBlock或者NSConcreateGlobalBlock类的地址;block根据在内存中的位置分为三种:NSGlobalBlock,NSStackBlock,NSMallocBlock。block中没有用到局部变量会初始化为NSConcreateGlobalBlock,如果用到局部变量,在MRC中会初始化为NSConcreateStackBlock,ARC中会初始化为NSConcreateMallocBlock。block作为属性时使用copy修饰以保证MRC下将block拷贝到堆中,ARC下不使用copy修饰也会自动拷贝到堆中。使用__block修饰的变量可以在block中修改、重新赋值,使用__block修饰的对象在block内不会被强引用一次,从而不会出现循环引用的问题。 某个类使用block作为属性,然后再block内使用了self;delegate使用strong或retain修饰(一般来讲,只要出现self->成员变量->block->self的闭环就会导致循环引用)。A页面进入B页面,在B中声明一个block属性,当B页面要消失的时候调用block传值给A,A在创建B页面实例时,实现b.block接收传入的参数进行处理。

2、runtime如何实现weak变量的自动置nil?

答:Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

3、runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?

答:进程是一家工厂,线程是一个流水线,Run Loop就是流水线上的主管;当工厂接到商家的订单分配给这个流水线时,Run Loop就启动这个流水线,让流水线动起来,生产产品;当产品生产完毕时,Run Loop就会暂时停下流水线,节约资源。
RunLoop管理流水线,流水线才不会因为无所事事被工厂销毁;而不需要流水线时,就会辞退RunLoop这个主管,即退出线程,把所有资源释放。
RunLoop并不是iOS平台的专属概念,在任何平台的多线程编程中,为控制线程的生命周期,接收处理异步消息都需要类似RunLoop的循环机制实现,Android的Looper就是类似的机制.
特性
•主线程的RunLoop在应用启动的时候就会自动创建
•其他线程则需要在该线程下自己启动
•不能自己创建RunLoop
•RunLoop并不是线程安全的,所以需要避免在其他线程上调用当前线程的RunLoop
•RunLoop负责管理autorelease pools
•RunLoop负责处理消息事件,即输入源事件和计时器事件
Run Loop Modes
Default:NSDefaultRunLoopMode,默认模式,在Run Loop没有指定Mode的时候,默认就跑在Default Mode下
Connection:NSConnectionReplyMode,用来监听处理网络请求NSConnection的事件
Modal:NSModalPanelRunLoopMode,OS X的Modal面板事件
Event tracking:UITrackingRunLoopMode,拖动事件
Common mode:NSRunLoopCommonModes,是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式.
RunLoop可以通过[acceptInputForMode:beforeDate:]和[runMode:beforeDate:]来指定在一段时间内的运行模式。如果不指定的话,RunLoop默认会运行在Default下(不断重复调用runMode:NSDefaultRunLoopMode beforDate:).
在主线程启动一个计时器Timer,然后拖动UITableView或者UIScrollView,计时器不执行。这是因为,为了更好的用户体验,在主线程中Event tracking模式的优先级最高。在用户拖动控件时,主线程的Run Loop是运行在Event tracking Mode下,而创建的Timer是默认关联为Default Mode,因此系统不会立即执行Default Mode下接收的事件.

高级

1、UITableview的优化方法(缓存高度,异步绘制,减少层级,hide,避免离屏渲染)。

答:主要在以下三个方面分析:
一、基础的优化准则(高度缓存, cell 重用…)。
二、使用调试工具分析问题。
三、异步绘制。
一、基础的优化:
1、正确地使用UITableViewCell的重用机制。UITableViewCell的核心思想是cell的重用机制,UITableView只会创建一屏幕或者多一点的cell,当cell滑出屏幕后就会放到cell的复用池里,要显示新的cell的时候回会在复用池里寻找相同标识符的cell使用,若没有可用的才会再创建,这样极大的减少内存的开销,所以给每种不通过布局方式的cell设置不同的标识符。
2、缓存cell高度。UITableView有两个很重要的回调方法,- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath和- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;回调顺序会先多次调用heightForRowAtIndexPath:方法来确定cell的contentSize和cell位置,然后才会调用cellForRowAtIndexPath:进而显示到屏幕上。在滑动的时候heightForRowAtIndexPath:的方法也会调用所以在这个方法里一定不能重复着进行大量的计算,一个提前把高度计算好存储起来在回调时直接把高度返回。做法就是在请求到数据之后和刷新tableView之前就把每个cell的高度算好存到相对的Model中。
3、不要阻塞主线程。请求网络图片,通常使用的是SDWebImage,这个正常使用没有问题,并且也能够缓存图片。但是要是准求性能,我推荐使用YYWebImage,因为SD对动图的处理不好, 难免会拖慢速度。特别是 gif 的内存暴增问题,SD 一直没有一个较好的解决方案还有在快速滑动时,对性能要求比较高的情况下,YY 可以直接以 layer 作为图片的载体而不是以imageView这样减少了相当一部分资源消耗。题外话:UIImageView 显示 UIImage 时,内部处理逻辑更为复杂,它需要正确处理 UIImage 的 imageOrientation、scale、capInsets、resizingMode 等属性,如果有 images 或 CIImage 的话,它还要额外处理图片动画或滤镜。如果在某个场景下没有用到这些属性,那用 UIView.layer.contents 来显示 CGImage 是更高效的。所以如果没有性能问题,还是用 UIImageView 更好一些。两种方式最终都是用 layer.contents 来显示具体内容的,但 UIImageView 封装了更完善的逻辑。
4、重用开销大的对象。如NSDateFormatter 和 NSCalendar等对象初始化非常慢,我们可以把它加入类的属性当中,或者创建单例来使用。
5、尽量减少计算的复杂度。在计算cell中控件的宽高或者返回cell的高度时尽量用ceil()或者floor()函数取整,
6、不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。

二、使用调试工具分析问题。
常见的调试选项:1、Color Blended Layers 混合图层。
Blended Layer是因为控件的Layer是透明的(Transparent),系统在渲染这些view时需要将该view和下层view混合(Blend)后才能计算出该像素点的实际颜色。解决办法就是检查该控件的opaque属性,记得设置成YES;检查backgroundColor属性是不是[UIColor clearColor]
2、Color Misaligned Images图片的缩放。这个选项检查了图片是否被放缩,像素是否对齐。被放缩的图片会被标记为黄色,像素不对齐则会标注为紫色。 如果不对齐此时系统需要对相邻的像素点做anti-aliasing反锯齿计算,会增加图形负担。当图片的size和显示图片View的size不同 或 图片的scale和屏幕的scale不同,就会发生像素不对齐的问题。要想像素对齐,必须保证image.size和显示图片view.size相等 且 image.scale和 [UIScreen mainScreen].scale相等。
解决办法:可以给UIImage写个分类专门处理这类图片尺寸和目标尺寸不符的情况,如://创建上下文 参数:目标尺寸大小,设置opaque=YES,设置新图片的分辨率和屏幕分辨率相同
UIGraphicsBeginImageContextWithOptions(size, YES, [UIScreen mainScreen].scale);
//绘图
[oldimage drawInRect:CGRectMake(0, 0, size.width, size.height)];
//获取新图片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();

3、Color Offscreen-Rendered 离屏渲染。
下面有关丽萍渲染的介绍摘自大神YYModel的作者的iOS 保持界面流畅的技巧 这是一位大神,开发了YYModel、YYKit、YYImage、YYWebImage、YYAsyncLayer等。这是他的博客地址
屏幕渲染有以下两种方式:On-Screen Rendering 当前屏幕渲染,指的是在当前用于显示的屏幕缓冲区中进行渲染操作。Off-Screen Rendering。离屏渲染,指的是 GPU 或 CPU 在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。过程中需要切换 contexts (上下文环境),先从当前屏幕切换到离屏的contexts,渲染结束后,又要将 contexts 切换回来,所以这种Offscreen-Rendering会导致app的图形性能下降。大部分Offscreen-Rendering都是和视图Layer的Shadow和Mask相关。
下列情况会导致视图的Offscreen-Rendering:
使用Core Graphics (CG开头的类);使用drawRect()方法,即使为空;将CALayer的属性shouldRasterize设置为YES;使用了CALayer的setMasksToBounds(masks)和setShadow*(shadow)方法。

2017年iOS面试题整理_第3张图片
电子枪扫描屏幕示意图

屏幕显示图像的原理:CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称 VSync。显示器通常以固定频率进行刷新,这个刷新率就是 VSync 信号产生的频率。尽管现在的设备大都是液晶显示屏了,但原理仍然没有变。

2017年iOS面试题整理_第4张图片
屏幕显示原理图

计算机系统中 CPU、GPU、显示器是以上面这种方式协同工作的。CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。

2017年iOS面试题整理_第5张图片
掉帧示意图

在 VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。随后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
解决方案:1、shadows(阴影) 。
设置阴影后,设置CALayer的 shadowPath。view.layer.shadowPath = [UIBezierPath pathWithCGRect:view.bounds].CGPath;
2、圆角。
cornerRadius 的文档中明确说明对 cornerRadius 的设置只对 CALayer 的backgroundColor 和 borderWidth&borderColor 起作用,如果 contents 有内容或者内容的背景不是透明的话,只有设置 masksToBounds 为 true 才能起作用,此时两个属性相结合,产生离屏渲染。此时的解决方案是后台绘制圆角图片,前台进行设置;
对于 contents 无内容或者内容的背景透明(无涉及到圆角以外的区域)的layer,直接设置layer的 backgroundColor 和 cornerRadius 属性来绘制圆角,比如label.layer.backgroundColor = aColor,label.layer.cornerRadius = 5;
使用混合图层,在layer上方叠加相应mask形状的半透明layer,sublayer.contents = (id)[UIImage imageNamed:@"xxx"].CGImage;
[view.layer addSublayer:sublayer];
3、光栅化shouldRasterize(光栅化)为YES。
将图转化为一个个栅格组成的图象。 光栅化特点:每个元素对应帧缓冲区中的一像素。shouldRasterize = YES在其它属性触发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的layer或者 sublayers没有发生改变,在下一帧的时候可以直接复用,从而减少渲染的频率。
三、异步绘制
待续。。。

2、SDWebImage的缓存策略?

答:SDWebImage用到了内存和磁盘双缓存,sd_setImageWithURL方法调用时会先从内存中查询图片缓存,如果找不到就会从磁盘中查找缓存,如果找到了会把图片再次设置到内存缓存中以提升效率,缓存查询成功就直接返回缓存数据,查询失败则发起网络请求,请求成功后会返回图片数据并写入缓存

3、AFN为什么添加一条常驻线程?

答:AFNetworking内部创建了一个单例线程。这个线程将会常驻内存,用来处理AFN发起的所有请求任务。当然,线程也跟随着一个runloop,AFN将这个 runloop的模式设置为NSDefaultRunLoopMode,不会在这个线程处理connection完成后的UI刷新等工作,而是会将数据抛给主线程,让主线程去完成UI的刷新

4、KVO的使用?实现原理?(为什么要创建子类来实现)?

答:当观察某对象A时,KVO机制动态创建一个对象A当前类的子类NSKVONotifying_A,并为这个新的子类重写了被观察属性keyPath的setter 方法。setter 方法随后负责通知观察对象属性的改变状况。NSKVONotifying_A类剖析:在这个过程,被观察对象的 isa 指针从指向原来的A类,被KVO机制修改为指向系统新创建的子类 NSKVONotifying_A类,来实现当前类属性值改变的监听;子类setter方法剖析:KVO的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的前后分别调用2个方法:
被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath 的属性值已经变更;
之后observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的setter 方法这种继承方式的注入是在运行时而不是编译时实现的。KVC(键值编码),即 Key-Value Coding,一个非正式的 Protocol,使用字符串(键)访问一个对象实例变量的机制。而不是通过调用 Setter、Getter 方法等显式的存取方式去访问。KVO(键值监听),即 Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,对象就会接受到通知,前提是执行了 setter 方法、或者使用了 KVC 赋值。notification 比 KVO 多了发送通知的一步。
两者都是一对多,但是对象之间直接的交互,notification 明显得多,需要notificationCenter 来做为中间交互。而 KVO 如我们介绍的,设置观察者->处理属性变化,至于中间通知这一环,则隐秘多了,只留一句“交由系统通知”,具体的可参照以上实现过程的剖析。notification 的优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,例如键盘、前后台等系统通知的使用也更显灵活方便。

5、KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar?

答:KVC(Key-value coding)键值编码,顾名思义。额,简单来说,是可以通过对象属性名称(Key)直接给属性值(value)编码(coding)“编码”可以理解为“赋值”。这样可以免去我们调用getter和setter方法,从而简化我们的代码,也可以用来修改系统控件内部属性(这个黑魔法且用且珍惜)。也可以字典转模型:setValuesForKeysWithDictionary。
赋值原理:

  • (1)去模型中查找有没有setIcon方法,就直接调用这个set方法,给模型这个属性赋值[self setIcon:dict[@"icon"]];
  • (2)如果找不到set方法,接着又会去寻找_icon成员变量,如果有,直接_icon = dict[@"icon”];
  • (3)如果找不到_icon成员变量,接着就会去寻找有没有icon属性,如果有,就直接访问模型中icon = dict[@"icon”];
  • (4)如果都找不到就会报错[ setValue:forUndefinedKey:

集合操作符:
简单集合操作符
@count: 返回一个值为集合中对象总数的NSNumber对象。
@sum: 首先把集合中的每个对象都转换为double类型,然后计算其总,最后返回一个值为这个总和的NSNumber对象。
@avg: 首先把集合中的每个对象都转换为double类型,然后计算其均分,最后返回一个值为这个总和的NSNumber对象。
@max: 使用compare:方法来确定最大值。所以为了让其正常工作,集合中所有的对象都必须支持和另一个对象的比较。
@min: 和@max一样,但是返回的是集合中的最小值。

你可能感兴趣的:(2017年iOS面试题整理)