1、简单的说说UIApplication的作用及继承关系?
(1)UIApplication是继承UIResponder的。UIApplication是整个应用程序的核心。每一个程序在运行期必须有UIApplication(或子类)的一个实例(有且只有一 个),通过[UIApplication shareApplication]可以得到这个单例实例。
(2)UIApplication帮助管理应用程序的生命周期,通过delegate来履行任务。
(3)UIApplication可以接收事件,把所有用户事件都放入队列,逐个处理。它会发送 当前事件给一个合适的目标控件进行处理。他还将部分事件转给delegate对象来处理。 delegate可以处理的事件包括:应用程序的生命周期事件(如程序启动与关闭)、系统事 件(如来电)。
2、UIView和CALayer是什么关系?
(1)UIView是iOS系统中界面元素的继承。所有的界面元素都继承自它,他本身完全是由CoreAnimation来实现的。它真正的绘图部分,是由一个叫CALayer的类来管理。UIView本身更像是一个CALayer的管理器,访问它的跟绘图和跟坐标有关的属性,如frame,bounds等,实际上内部都是在访问它所包含的CALayer的相关属性。
(2)UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示。
(3)UIView的CALayer类似UIView的子View树形结构,也可以向它的layer上添加子layer。
(4)CALayer坐标系统与UIView有点不一样,它多了一个anchorPoint的属性。
(5)UIView的layer树形在系统内部,被系统维护着三份copy。逻辑树,动画树,显示树。
(6)CALayer默认修改属性支持隐式动画
(7)View可以接受并处理事件,Layer不可以。
3、如何优化UITableView?
(1)复用单元格
(2)使用不透明的视图,单元格中少使用动画。
(3)图片使用异步加载,设置图片加载的并发数。
(4)滑动时不加载图片,停止滑动时再加载。
(5)图片和文字可以直接drawRect
(6)如果cell是动态行高,计算缓存单元格高度
(7)尽量少reloadData,只reloadRowsAtIndexPaths
(8)cell高度固定直接用rowHeight属性设置高度。
4、loadView的作用?
- loadView用来自定义view,只要实现了这个方法,其他通过xib或storyboard创建的view都不会被加载
- 看懂控制器view创建的这个图就行
5、如何高性能的给 UIImageView 加个圆角?
不好的解决方案
使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现
self.view.layer.cornerRadius = 5;
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];
6、使用drawRect有什么影响?
- drawRect方法依赖Core Graphics框架来进行自定义的绘制
- 缺点:它处理touch事件时每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例,那就会很糟糕了
- 这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为dirty,但还是会显示原来的内容,直到下一次的视图渲染周期,才会将标记为 dirty 的图层重新建立Core Graphics上下文,然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制
7、描述下SDWebImage里面给UIImageView加载图片的逻辑?
- SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后在替换占位图片 - 加载图片的过程大致如下:
- 首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
- 如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
- 如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
- 下载后的图片会加入缓存中,并写入磁盘中
- 整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
8、触摸事件的传递?
- 触摸事件的传递是从父控件传递到子控件
- 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件
- 不能接受触摸事件的四种情况
- 不接收用户交互,即:userInteractionEnabled = NO
- 隐藏,即:hidden = YES
- 透明,即:alpha <= 0.01
- 未启用,即:enabled = NO
- 提示:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的
- 如何找到最合适处理事件的控件:
- 首先,判断自己能否接收触摸事件
- 可以通过重写hitTest:withEvent:方法验证
- 其次,判断触摸点是否在自己身上
- 对应方法pointInside:withEvent:
- 从后往前(先遍历最后添加的子控件)遍历子控件,重复前面的两个步骤
- 如果没有符合条件的子控件,那么就自己处理
9、事件响应者链
- 如果当前view是控制器的view,那么就传递给控制器
- 如果控制器不存在,则将其传递给它的父控件
- 在视图层次结构的最顶层视图也不能处理接收到的事件或消息,则将事件或消息传递给UIWindow对象进行处理
- 如果UIWindow对象也不处理,则将事件或消息传递给UIApplication对象
- 如果UIApplication也不能处理该事件或消息,则将其丢弃
- 补充:如何判断上一个响应者
- 如果当前这个view是控制器的view,那么控制器就是上一个响应者
- 如果当前这个view不是控制器的view,那么父控件就是上一个响应者
10、UIView的绘制原理
当我们调用UIView的setNeedsDisplay方法以后,实际上并没有立刻发生当前视图的绘制工作,而是在之后的某一时机才会进行当前视图的绘制 。为什么没有立刻发生当前视图的绘制工作?(因为到当前runloop将要结束的时候,才会开始介入到UI视图的绘制流程中)
- 当调用UIView的setNeedsDisplay后
- 系统会立刻调用view的layer的同名方法[view.layer setNeedsDisplay],之后相当于在layer上面打上了一个脏标记
- 然后再当前runloop将要结束的时候,才会调用CALayer的display函数方法,然后才进入到当前视图的真正绘制工作的流程当中
- CALayer的display方法,在内部会首先判断layer的delegate是否响应displayLayer这个方法
- 若不响应,则系统开始绘制流程
- 若响应,则开始异步绘制
系统绘制流程
- 首先CALayer内部会创建一个
CGContextRef,在drawRect方法中,可以通过上下文堆栈当中的取出这个context,拿到的就是当前控件或者说视图的上下文或者说是backing store - 然后layer会判断它是否有代理,若没有,则调用CALayer的
drawInContext。 - 若有则调用代理方法,然后做当前视图的绘制工作(这一步发生在系统内部当中),再在合适的时机,基于drawRect回调方法,
- drawRect默认操作是什么都不做,而之所以有这个接口,就是为了让我们在系统绘制之上,可以做些自定义的绘制工作。
- 最后再由CALayer上传对应的backing store给GPU,这里的backing store我们可以理解为位图。
异步绘制流程
-[layer.delegate displayLayer:]
基于layer的delegate,如果实现了displayLayer方法,就可以进入到异步绘制流程当中
- 在异步绘制过程中, 需要代理去负责生成对应的bitmap
- 设置该bitmap作为
layer.contents属性的值 异步绘制的机制和流程
- 左侧是主队列,右侧是全局并发队列
- 假如在某一时机调用了
setNeedsDiaplay方法后 - 在当前runloop将要结束的时候,会有系统调用视图所对应layer的
display方法 - 如果代理实现了
displayLayer方法,会调用这个代理的displayLayer这个方法 - 然后通过子线程的切换,我们会在子线程中去做位图的绘制,此时主线程可以去做些其他的工作
- 然后再回到主队列中,提交这个位图,设置给CALayer的contents属性 子线程的绘制
- 通过
CGBitmapContextCreat方法,来创建一个位图的上下文 - 通过
CoreGraphic的相关API,可以做当前UI控件的一些绘制工作 - 之后通过
CGBitmapContextCreatImage方法,根据所绘制的上下文,生成一张CGImage图片
11、UIView的frame、bounds跟center属性的区别?
- frame:描述当前视图在其父视图中的位置和大小;
- bounds:描述当前视图在其自身坐标系统中的位置和大小;
- center:描述当前视图的中心点在其父视图中的位置;
// frame 和 bounds 属性都是CGRect类型的,难免认为功能相似
- (CGRect)frame{
return CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width,self.frame.origin.height);
}
- (CGRect)bounds{
return CGRectMake(0,0,self.bounds.size.width,self.bounds.size.height);
}
// bounds属性高于frame属性,任何视图都一定存在bounds属性,但不是所有视图都存在frame属性的,某种程度上frame属性的存在依存于bounds属性。 bounds --> 指定坐标系 --> frame来参照指定的坐标系
优秀参考: