潭州ios面试题6 必看

1 NSOperationQueue和GCD的区别是什么

GCD(Grand Central Dispatch)是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;NSOperation是一个抽象类,它封装了线程的细节实现,我们可以通过子类化该对象,加上NSQueue来同面向对象的思维,管理多线程程序。而Operation为我们提供了更多的选择;

2.在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);

3.NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;

4.我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;

5.在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;

6.我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。

7.GCD 是严格的队列,先进先出FIFO;NSOperation可以改动 优先级(或者说服务质量)改变执行顺序

8.NSOperation的高级:最大并发数,控制线程个数,优化了线程的暂停、继续、取消功能(GCD实现起来太难,可以用 KVO ),依赖关系,可以让异步任务

同步执行.

2、GCD与NSThread的区别:  

1). NSThread 通过 @selector 指定要执行的方法,代码分散, 依靠的是NSObject的分类实现的线程之间的通讯,如果要开线程必须创建多个线程对象。经常只用的是[NSTread current] 查看当前的线程。

NSThread是一个控制线程执行的对象,它不如NSOperation抽象,通过它我们可以方便的得到一个线程,并控制它。但NSThread的线程之间的并发控制,是需要我们自己来控制的,可以通过NSCondition实现。

 2).GCD 通过 block指定要执行的代码,代码集中, 所有的代码写在一起的,让代码更加简单,易于阅读和维护,不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期

3 利用NSOperation与NSOperationqueue处理多线程时,有3个NSOperation分别为A,B,C,要求A,B执行完毕后,在执行C,如何做?

有三种实现方案

方案一:串行队列同步执行(GCD实现)

A,B放在串行队列中执行,执行完毕,主队列执行任务C

方案二:队列依赖实现

方案三:并发队列和主队列实现

http://www.jianshu.com/p/0b0d9b1f1f19

多线程

一.资源抢夺

2> 资源抢夺解决方案

@sychronized{ }

dispatch_barrier_async

NSLock NSCondition

dispatch_semaphore_wait

4+(void)load与+(void)initialize区别

 load方法:

 1.当类加载到OC运行时环境(内存)中的时候,就会调用一次(一个类只会加载一次).

 2. 程序一启动就会调用.

 3.程序运行过程中,只会调用1次.

 initialize方法:

 1.当第一次使用这个类的时候(比如调用了类的某个方法)才会调用.

 2. 并非程序一启动就会调用.

load和initialize的共同特点

在不考虑开发者主动使用的情况下,系统最多会调用一次

 如果父类和子类都被调用,父类的调用一定在子类之前

都是为了应用运行提前创建合适的运行环境

 在使用时都不要过重地依赖于这两个方法,除非真正必要

 它们的相同点在于:方法只会被调用一次。(其实这是相对runtime来说的,后边会做进一步解释)。

  load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。

 文档也明确阐述了方法调用的顺序:父类(Superclass)的方法优先于子类(Subclass)的方法,类中的方法优先于类别(Category)中的方法。

5 说说关于UDP/TCP的区别?

UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去! UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。

1.只管发送,不确认对方是否接收到

2.将数据及源和目的封装成数据包中,不需要建立连接

3.每个数据报的大小限制在64K之内

4.因为无需连接,因此是不可靠协议

5.不需要建立连接,速度快

应用场景:多媒体教室/网络流媒体

TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能 建立起来,其中的过程非常复杂,我们这里只做简单、形象的介绍,你只要做到能够理解这个过程即可。

1.建立连接,形成传输数据的通道

2.在连接中进行大数据传输(数据大小不收限制)

3.通过三次握手完成连接,是可靠协议,安全送达

4.必须建立连接,效率会稍低

TCP传输控制协议主要包含下列任务和功能:

* 确保IP数据报的成功传递。

* 对程序发送的大块数据进行分段和重组。

* 确保正确排序及按顺序传递分段的数据。

* 通过计算校验和,进行传输数据的完整性检查。

简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般

3> TCPUDP的区别:

3.1>基于连接与无连接;

3.2>对系统资源的要求(TCP较多,UDP少);

3.3>UDP程序结构较简单;

3.4>流模式与数据报模式;

3.5>TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证

6 长链接&短链接

 TCP短连接

 我们模拟一下TCP短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server 发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起 close操作。为什么呢,一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。从上面的描述看,短连接一般只会在 client/server间传递一次读写操作

 TCP长连接

 接下来我们再模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。

 长连接短连接操作过程

短连接的操作步骤是:

建立连接——数据传输——关闭连接…建立连接——数据传输——关闭连接

 长连接的操作步骤是:

  建立连接——数据传输…(保持连接)…数据传输——关闭连接

 什么时候用长连接,短连接?

 长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话 那么处理速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

长连接和短连接的优点和缺点

 由上可以看出,长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户来说,较适用长连接。不过这里存在一个问 题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。在长连接的应用场景 下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越 多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可 以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的 客户端连累后端服务。

 短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。

长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。


7 TCP 传输原理

1>TCP如何防止乱序和丢包

   TCP数据包的头格式中有两个概念,Sequence Number是数据包的序号,用来解决网络包乱序(reordering)问题。Acknowledgement Number就是ACK——用于确认收到,用来解决不丢包的问题。

   位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码).

  SeqNum的增加是和传输的字节数相关的,TCP传输数据时,A主机第一次传输1440个字节,seq=1,那么第二次时seq = 1441,B拼接数据就是根据seq进行拼接的,seq数字不断累加避免了乱序.B主机收到第一次数据包以后会返回ack = 1441.

   A主机收到B的ack = 1441时,就知道第一个数据包B已收到. 如果B没有收到第一次的数据包,那么B再收到A的数据包时,他就会发ack = 1回去,A收到B的回复,发现B没有收到第一次数据包,就会重发第一次数据包,这样就可以防止丢包.


2>描述一下三次握手


  第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

  第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

  第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。完成三次握手,客户端与服务器开始传送数据.

3>三次握手实现的过程:

第一次握手:建立连接时,客户端发送同步序列编号到服务器,并进入发送状态,等待服务器确认

第二次:服务器收到同步序列编号,并确认同时自己也发送一个同步序列编号+确认标志,此时服务器进入接收状态

第三次:客户端收到服务器发送的包,并向服务器发送确认标志,随后链接成功。

注意:是在链接成功后在进行数据传输。

8 .http和scoket通信的区别?socket连接相关库,TCP,UDP的连接方法,HTTP的几种常用方式?

http和scoket通信的区别:

http是客户端用http协议进行请求,发送请求时候需要封装http请求头,并绑定请求的数据,服务器一般有web服务器配合(当然也非绝对)。 http请求方式为客户端主动发起请求,服务器才能给响应,一次请求完毕后则断开连接,以节省资源。服务器不能主动给客户端响应(除非采取http长连接技术)。iphone主要使用类是NSUrlSe on。 

scoket是客户端跟服务器直接使用socket“套接字”进行连接,并没有规定连接后断开,所以客户端和服务器可以保持连接通道,双方都可以主动发送数据。一般在游戏开发或股票开发这种要求即时性很强并且保持发送数据量比较大的场合使用。主要使用类是CFSocketRef。

9 BLOCK

使用block时什么情况会发生引用循环,如何解决?

一个对象中强引用了block,在block中又使用了该对象,就会发射循环引用。 解决方法是将该对象使用__weak或者__block修饰符修饰之后再在block中使用。

id weak weakSelf = self; 或者weak __typeof(&*self)weakSelf = self该方法可以设置宏

id __block weakSelf = self;

事后解除指控

10 ivar、getter、setter 是如何生成并添加到这个类中的?

“自动合成”( autosynthesis)

完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为_firstName与 _lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字.

11 @protocol和 category 中如何使用@property

在 protocol 中使用 property 只会生成setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性

category使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:

objc_setAssociatedObject

objc_getAssociatedObject

12 .runtime如何通过selector找到对应的IMP地址?

每一个类对象中都一个方法列表(isa),方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.

13.TableView性能优化

1.行高一定要缓存!

2.不要动态创建子视图

所有的子视图都预先创建,如果不需要显示可以设置 hidden

3.所有的子视图都应该添加到 contentView 上

4.所有的子视图都必须指定背景颜色,且所有的颜色都不要使用 alpha

5.cell 栅格化

6.异步绘制

UITableView的优化主要从三个方面入手:

1.提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;

2.异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;

3.滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!(SDWebImage已经实现异步加载,配合这条性能杠杠的)。

正确使用reuseIdentifier来重用Cells

尽量使所有的view opaque,包括Cell自身

尽量少用或不用透明图层

如果Cell内现实的内容来自web,使用异步加载,缓存请求结果

减少subviews的数量

在heightForRowAtIndexPath:中尽量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果

尽量少用addView给Cell动态添加View,可以初始化时就添加,然后通过hide来控制是否显示

 在使用UITableView的时候,有的时候你会碰到Cell卡顿,图片加载慢,使得滑动cell时变得不那么流畅,这些都会影响用户体验,拉低整体app的效果。

1.   使用cell重用机制,尽可能快地返回重用cell实例   这点大家都应该比较清楚,使用reuse机制能大幅降低创建cell所带来的损耗,这就要各位在UITableView的dataSource中实现的tableView:cellForRowAtIndexPath:方法。只是有一点,尽量不要在此时绑定数据,因为目前在屏幕上还没有cell,可以在UITableView的delegate方法tableView:willDisplayCell:forRowAtIndexPath:中进行数据的填充。     需要说明的是,你可能会动态计算cell高度,但最好是不要选择Autolayout。使用Autolayout后,会根据cell的子视图使得求解的约束也越多,从而降低计算速度,影响滑动时FPS。所以,为了使tableview平滑滚动,请使用动态计算高度,不要选择Autolayout。

2. cell的subViews的各级opaque值要设成YES

 opaque用于辅助绘图系统,表示UIView是否透明。在不透明的情况下,渲染视图时需要快速地渲染,以提高性能。渲染最慢的操作之一是混合(blending)。提高性能的方法是减少混合操作的次数,其实就是GPU的不合理使用,这是硬件来完成的(混合操作由GPU来执行,因为这个硬件就是用来做混合操作的,当然不只是混合)。 优化混合操作的关键点是在平衡CPU和GPU的负载。  

       还有就是cell的layer的shouldRasterize要设成YES。

3. 在绘制字符串时,尽可能使用drawAtPoint:withFont: , 在绘制图片,尽量使用drawAtPoint

    不要使用更复杂的drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withFont:(UIFont*)font lineBreakMode:(UILineBreakMode)lineBreakMode;如果要绘制过长的字符串,建议先截断,然后使用drawAtPoint:withFont:方法绘制。

  不要使用drawInRect, 因为它在绘制过程中对图片放缩大小,消耗CPU。

       其实,最快的绘制就是你不要做任何绘制。有时,通过UIGraphicsBeginImageContextWithOptions()或者CGBitmapContextCeate()创建位图会显得更有意义,从位图上面抓取图像,并设置为 CALayer 的内容。

如果你必须实现 -drawRect:,并且你必须绘制大量的东西,这将占用时间。

图片的话,你可能会用位图来替代:

5. cell异步加载图片以及缓存

 对于cell里的图片采用异步的方式,加载好后缓存。当图片还没有请求加载时,你可以使用默认图片。

 一旦你缓存好图片,使用cell的重用机制时就可以从关联好的视图源里以相应的url来找到对应的缓存图片,缓存大大节省重复请求图片的耗损。只是你要考虑内存级别的缓存还是磁盘级别的缓存,记得使用完毕清缓存哦!(记得减少内存级别的拷贝)

为了防止图片多次下载,我们需要对图片做缓存,缓存分为内存缓存于沙盒缓存,我们当然两种都要实现。

般情况下在我们会在cellForRow方法里面设置cell的图片数据源,也就是说如果一个cell的imageview对象开启了一个下载任务,这个时候该cell对象发生了重用,新的image数据源会开启另外的一个下载任务,由于他们关联的imageview对象实际上是同一个cell实例的imageview对象,就会发生2个下载任务回调给同一个imageview对象。这个时候就有必要做一些处理,避免回调发生时,错误的image数据源刷新了UI。

在我们向下滑动tableview的时候我们需要手动去取消掉下载操作,当用户停止滑动,再去执行下载操作

如果快速滑下去,然后又滑回来的话,图片是过了一会才显示出来,这是因为快速滑动的时候,旧数据源的下载任务被取消掉了。

异步下载图片我们用的是NSOperation,并且创建一个全局的queue来管理下载图片的操作。

在把图片显示到Cell上之前

先判断内存中(images字典中)有没有图片,

如果有,则取出url对应的图片来显示,

如果没有,再去沙盒缓存中查看,当然存到沙盒中都是NSData。

如果沙盒缓存中有,我们取出对应的数据给Cell去显示

如果沙盒中也没有图片,我们先显示占位图片。再创建operation去执行下载操作了。

当然在创建operation之前,我们要判断这个operation操作是否存在

如果没有下载操作,我们才需要真正的去创建operation执行下载。

创建好下载操作之后应该把该操作存放到全局队列中去异步执行,同时吧操作放入operations字典中记录下来。

下载完成之后:

把下载好的图片放到内存中、同时存到沙盒缓存中

执行完上面的操作之后回到主线程刷新表格,

从operations字典中移除下载操作(防止operations越来越大,同时保证下载失败后,能重新下载)

2、cell高度的计算

这边我们分为两种cell,一种是定高的cell,另外一种是动态高度的cell。

(1)定高的cell,应该采用如下方式:

self.tableView.rowHeight = 88;

这个方法指定了所有cell高度都是88的tableview,rowHeight默认的值是44,所以一个空的TableView会显示成这个样子。对于定高cell,直接采用上面方式给定高度,不需要实现tableView:heightForRowAtIndexPath:以节省不必要的计算和开销。

(2)动态高度的cell

我们需要实现它的代理,来给出高度:


-(CGFloat)tableView:(UITableView *)tableViewheightForRowAtIndexPath:(NSIndexPath *)indexPath{


// return xxx

}

这个代理方法实现后,上面的rowHeight的设置将会变成无效。在这个方法中,我们需要提高cell高度的计算效率,来节省时间。

自从iOS8之后有了self-sizing cell的概念,cell可以自己算出高度,使用self-sizing cell需要满足以下三个条件:

(1)使用Autolayout进行UI布局约束(要求cell.contentView的四条边都与内部元素有约束关系)。

(2)指定TableView的estimatedRowHeight属性的默认值。

(3)指定TableView的rowHeight属性为UITableViewAutomaticDimension。

除了提高cell高度的计算效率之外,对于已经计算出的高度,我们需要进行缓存,对于已经计算过的高度,没有必要进行计算第二次。  


3、渲染

为了保证TableView的流畅,当快速滑动的时候,cell必须被快速的渲染出来。所以cell渲染的速度必须快。如何提高cell的渲染速度呢?

(1)当有图像时,预渲染图像,在bitmap context先将其画一遍,导出成UIImage对象,然后再绘制到屏幕,这会大大提高渲染速度。具体内容可以自行查找“利用预渲染加速显示iOS图像”相关资料。

(2)渲染最好时的操作之一就是混合(blending)了,所以我们不要使用透明背景,将cell的opaque值设为Yes,背景色不要使用clearColor,尽量不要使用阴影渐变等

(3)由于混合操作是使用GPU来执行,我们可以用CPU来渲染,这样混合操作就不再执行。可以在UIView的drawRect方法中自定义绘制。

4、减少视图的数目

我们在cell上添加系统控件的时候,实际上系统都会调用底层的接口进行绘制,大量添加控件时,会消耗很大的资源并且也会影响渲染的性能。当使用默认的UITableViewCell并且在它的ContentView上面添加控件时会相当消耗性能。所以目前最佳的方法还是继承UITableViewCell,并重写drawRect方法。

5、减少多余的绘制操作

在实现drawRect方法的时候,它的参数rect就是我们需要绘制的区域,在rect范围之外的区域我们不需要进行绘制,否则会消耗相当大的资源。

6、不要给cell动态添加subView

在初始化cell的时候就将所有需要展示的添加完毕,然后根据需要来设置hide属性显示和隐藏。

7、异步化UI,不要阻塞主线程

我们时常会看到这样一个现象,就是加载时整个页面卡住不动,怎么点都没用,仿佛死机了一般。原因是主线程被阻塞了。所以对于网路数据的请求或者图片的加载,我们可以开启多线程,将耗时操作放到子线程中进行,异步化操作。这个或许每个iOS开发者都知道的知识,不必多讲。

8、滑动时按需加载对应的内容

如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。


-(void)scrollViewWillEndDragging:(UIScrollView *)scrollViewwithVelocity:(CGPoint)velocitytargetContentOffset:(inoutCGPoint *)targetContentOffset{


NSIndexPath *ip=[selfindexPathForRowAtPoint:CGPointMake(0,targetContentOffset->y)];


NSIndexPath *cip=[[selfindexPathsForVisibleRows]firstObject];


NSIntegerskipCount=8;


if(labs(cip.row-ip.row)>skipCount){


NSArray *temp=[selfindexPathsForRowsInRect:CGRectMake(0,targetContentOffset->y,self.width,self.height)];


NSMutableArray *arr=[NSMutableArrayarrayWithArray:temp];


if(velocity.y<0){


NSIndexPath *indexPath=[templastObject];


if(indexPath.row+33){


[arraddObject:[NSIndexPathindexPathForRow:indexPath.row-3inSection:0]];


[arraddObject:[NSIndexPathindexPathForRow:indexPath.row-2inSection:0]];


[arraddObject:[NSIndexPathindexPathForRow:indexPath.row-1inSection:0]];


}


}


[needLoadArraddObjectsFromArray:arr];


}


}


记得在tableView:cellForRowAtIndexPath:方法中加入判断:


1

2

3

4

if(needLoadArr.count>0&&[needLoadArrindexOfObject:indexPath]==NSNotFound){

    [cellclear];

    return;

}

滑动很快时,只加载目标范围内的cell,这样按需加载(配合SDWebImage),极大提高流畅度。


14 优化方案

官方对离屏渲染产生性能问题也进行了优化:

iOS 9.0之前UIimageView跟UIButton设置圆角都会触发离屏渲染。

iOS 9.0之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。

(1)圆角优化

在APP开发中,圆角图片还是经常出现的。如果一个界面中只有少量圆角图片或许对性能没有非常大的影响,但是当圆角图片比较多的时候就会APP性能产生明显的影响。

我们设置圆角一般通过如下方式:

 imageView.layer.cornerRadius=CGFloat(10);


imageView.layer.masksToBounds=YES;

这样处理的渲染机制是GPU在当前屏幕缓冲区外新开辟一个渲染缓冲区进行工作,也就是离屏渲染,这会给我们带来额外的性能损耗,如果这样的圆角操作达到一定数量,会触发缓冲区的频繁合并和上下文的的频繁切换,性能的代价会宏观地表现在用户体验上——掉帧。

优化方案1:使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角


UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];

imageView.image = [UIImage imageNamed:@"myImg"];

//开始对imageView进行画图 

UIGraphicsBeginImageContextWithOptions(imageView.bounds.size,NO, 1.0);

//使用贝塞尔曲线画出一个圆形图 

[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];

[imageView drawRect:imageView.bounds];

imageView.image = UIGraphicsGetImageFromCurrentImageContext();

//结束画图 

UIGraphicsEndImageContext();

[self.view addSubview:imageView];

优化方案2:使用CAShapeLayer和UIBezierPath设置圆角

UIImageView *imageView=[[UIImageViewalloc]initWithFrame:CGRectMake(100,100,100,100)];


imageView.image=[UIImageimageNamed:@"myImg"];


UIBezierPath *maskPath=[UIBezierPathbezierPathWithRoundedRect:imageView.boundsbyRoundingCorners:UIRectCornerAllCornerscornerRadii:imageView.bounds.size];


CAShapeLayer *maskLayer=[[CAShapeLayeralloc]init];


//设置大小


maskLayer.frame=imageView.bounds;


//设置图形样子


maskLayer.path=maskPath.CGPath;


imageView.layer.mask=maskLayer;


[self.viewaddSubview:imageView];

对于方案2需要解释的是:

• CAShapeLayer继承于CALayer,可以使用CALayer的所有属性值;

• CAShapeLayer需要贝塞尔曲线配合使用才有意义(也就是说才有效果)

• 使用CAShapeLayer(属于CoreAnimation)与贝塞尔曲线可以实现不在view的drawRect(继承于CoreGraphics走的是CPU,消耗的性能较大)方法中画出一些想要的图形

• CAShapeLayer动画渲染直接提交到手机的GPU当中,相较于view的drawRect方法使用CPU渲染而言,其效率极高,能大大优化内存使用情况。

总的来说就是用CAShapeLayer的内存消耗少,渲染速度快,建议使用优化方案2。

(2)shadow优化

对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优化性能,能大幅提高性能。示例如下:

imageView.layer.shadowColor=[UIColorgrayColor].CGColor;


imageView.layer.shadowOpacity=1.0;


imageView.layer.shadowRadius=2.0;


UIBezierPath *path=[UIBezierPathbezierPathWithRect:imageView.frame];


imageView.layer.shadowPath=path.CGPath;


我们还可以通过设置shouldRasterize属性值为YES来强制开启离屏渲染。其实就是光栅化(Rasterization)。既然离屏渲染这么不好,为什么我们还要强制开启呢?当一个图像混合了多个图层,每次移动时,每一帧都要重新合成这些图层,十分消耗性能。当我们开启光栅化后,会在首次产生一个位图缓存,当再次使用时候就会复用这个缓存。但是如果图层发生改变的时候就会重新产生位图缓存。所以这个功能一般不能用于UITableViewCell中,cell的复用反而降低了性能。最好用于图层较多的静态内容的图形。而且产生的位图缓存的大小是有限制的,一般是2.5个屏幕尺寸。在100ms之内不使用这个缓存,缓存也会被删除。所以我们要根据使用场景而定。

(3)其他的一些优化建议

• 当我们需要圆角效果时,可以使用一张中间透明图片蒙上去

• 使用ShadowPath指定layer阴影效果路径

• 使用异步进行layer渲染(Facebook开源的异步绘制框架AsyncDisplayKit)

• 设置layer的opaque值为YES,减少复杂图层合成

• 尽量使用不包含透明(alpha)通道的图片资源

• 尽量设置layer的大小值为整形值

• 直接让美工把图片切成圆角进行显示,这是效率最高的一种方案

• 很多情况下用户上传图片进行显示,可以让服务端处理圆角

• 使用代码手动生成圆角Image设置到要显示的View上,利用UIBezierPath(CoreGraphics框架)画出来圆角图片

(4)Core Animation工具检测离屏渲染

对于离屏渲染的检测,苹果为我们提供了一个测试工具Core Animation。可以在Xcode->Open Develeper Tools->Instruments中找到,如下图:

Core Animation工具用来监测Core Animation性能,提供可见的FPS值,并且提供几个选项来测量渲染性能。如下图:

下面我们来说明每个选项的功能:

Color Blended Layers:这个选项如果勾选,你能看到哪个layer是透明的,GPU正在做混合计算。显示红色的就是透明的,绿色就是不透明的。

Color Hits Green and Misses Red:如果勾选这个选项,且当我们代码中有设置shouldRasterize为YES,那么红色代表没有复用离屏渲染的缓存,绿色则表示复用了缓存。我们当然希望能够复用。

Color Copied Images:按照官方的说法,当图片的颜色格式GPU不支持的时候,Core Animation会

拷贝一份数据让CPU进行转化。例如从网络上下载了TIFF格式的图片,则需要CPU进行转化,这个区域会显示成蓝色。还有一种情况会触发Core Animation的copy方法,就是字节不对齐的时候。如下图:

Color Immediately:默认情况下Core Animation工具以每毫秒10次的频率更新图层调试颜色,如果勾选这个选项则移除10ms的延迟。对某些情况需要这样,但是有可能影响正常帧数的测试。

Color Misaligned Images:勾选此项,如果图片需要缩放则标记为黄色,如果没有像素对齐则标记为紫色。像素对齐我们已经在上面有所介绍。

Color Offscreen-Rendered Yellow:用来检测离屏渲染的,如果显示黄色,表示有离屏渲染。当然还要结合Color Hits Green and Misses Red来看,是否复用了缓存。

Color OpenGL Fast Path Blue:这个选项对那些使用OpenGL的图层才有用,像是GLKView或者CAEAGLLayer,如果不显示蓝色则表示使用了CPU渲染,绘制在了屏幕外,显示蓝色表示正常。

Flash Updated Regions:当对图层重绘的时候回显示黄色,如果频繁发生则会影响性能。可以用增加缓存来增强性能。

以上就是本人的一些总结,当然对于UITableView的性能优化,网上有很多相关的资料。如果有什么不同的观点,欢迎大家补充。

你可能感兴趣的:(潭州ios面试题6 必看)