1.聊下HTTP post的body体使用form-urlencoded和multipart/form-data的区别。
1)application/x-www-form-urlencoded:
窗体数据被编码为名称/值对,这是标准且默认的编码格式。当action为get时候,客户端把form数据转换成一个字串append到url后面,用?分割。当action为post时候,浏览器把form数据封装到http body中,然后发送到server。
2)multipart/form-data:
multipart表示的意思是单个消息头包含多个消息体的解决方案。multipart媒体类型对发送非文本的各媒体类型是有用的。一般多用于文件上传。
multipart/form-data只是multipart的一种。目前常用的有以下这些类型(注:任何一种执行时无法识别的multipart子类型都被视为子类型"mixed")

2.让你设计一种机制检测UIViewController的内存泄漏,你会怎么做?
如果Controller被释放了,但其曾经持有过的子对象如果还存在,那么这些子对象就是泄漏的可疑目标。
一个小示例:子对象(比如view)建立一个对controller的weak引用,如果Controller被释放,这个weak引用也随之置为nil。那怎么知道子对象没有被释放呢?用一个单例对象每个一小段时间发出一个ping通知去ping这个子对象,如果子对象还活着就会一个pong通知。所以结论就是:如果子对象的controller已不存在,但还能响应这个ping通知,那么这个对象就是可疑的泄漏对象。

3.通过[UIImage imageNamed:]生成的对象什么时候被释放?
使用imageNamed这个方法生成的UIImage对象,会在应用的bundle中寻找图片,如果找到则Cache到系统缓存中,作为内存的cache,而程序员是无法操作cache的,只能由系统自动处理,如果我们需要重复加载一张图片,那这无疑是一种很好的方式,因为系统能很快的从内存的cache找到这张图片,但是试想,如果加载很多很大的图片的时候,内存消耗过大的时候,就会会强制释放内存,即会遇到内存警告(memory warnings).
由于在iOS系统中释放图片的内存比较麻烦,所以冲易产生内存泄露。
像[[UIImageView alloc] init]还有一些其他的 init 方法,返回的都是 autorelease 对象。而 autorelease 不能保证什么时候释放,所以不一定在引用计数为 0 就立即释放,只能保证在 autoreleasepool 结尾的时候释放。
像 UIImage 还有 NSData 这种,大部分情况应该是延迟释放的,可以理解为到 autoreleasepool 结束的时候才释放。

4.applicationWillEnterForeground和applicationDidBecomeActive都会在哪些场景下被调用?举例越多越好。
1)applicationWillResignActive(将进入后台)
对应applicationWillEnterForeground(将进入前台)
程序将要失去Active状态时调用,比如按下Home键或有电话信息进来,这个方法用来
• 暂停正在执行的任务;
• 禁止计时器;
• 减少OpenGL ES帧率;
• 若为游戏应暂停游戏;
总结为一个字:停!
2)applicationDidEnterBackground(已经进入后台)
对应applicationDidBecomeActive(已经变成前台)
程序已经进入后台时调用,这个方法用来
• 释放共享资源;
• 保存用户数据(写到硬盘);
• 作废计时器;
• 保存足够的程序状态以便下次恢复;总结为4个字:释放、保存!

5.dSYM你是如何分析的?
方法1 使用XCode
这种方法可能是最容易的方法了。
1. 要使用Xcode符号化 crash log,你需要下面所列的3个文件:
2. crash报告(.crash文件)
3. 符号文件 (.dsymb文件)
4. 应用程序文件 (appName.app文件,把IPA文件后缀改为zip,然后解压,Payload目录下的appName.app文件), 这里的appName是你的应用程序的名称。
5. 把这3个文件放到同一个目录下,打开Xcode的Window菜单下的organizer,然后点击Devices tab,然后选中左边的Device Logs。
6. 然后把.crash文件拖到Device Logs或者选择下面的import导入.crash文件。
7. 这样你就可以看到crash的详细log了。
方法2 使用命令行工具symbolicatecrash
1. 有时候Xcode不能够很好的符号化crash文件。我们这里介绍如何通过symbolicatecrash来手动符号化crash log。
2. 在处理之前,请依然将“.app“, “.dSYM”和 “.crash"文件放到同一个目录下。现在打开终端(Terminal)然后输入如下的命令:
3. export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
4. 然后输入命令:
5. /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash appName.crash appName.app > appName.log
6. 现在,符号化的crash log就保存在appName.log中了。
方法3 使用命令行工具atos
1. 如果你有多个“.ipa”文件,多个”.dSYMB"文件,你并不太确定到底“dSYMB”文件对应哪个".ipa"文件,那么,这个方法就非常适合你。
2. 特别当你的应用发布到多个渠道的时候,你需要对不同渠道的crash文件,写一个自动化的分析脚本的时候,这个方法就极其有用。
3. 具体方法 请百度

6.多线程有哪几种?你更倾向于哪一种?
1. NSThread
2. Cocoa NSOperation (使用NSOperation和NSOperationQueue)
3. GCD (Grand Central Dispatch)
1.NSThread:(两种创建方式)
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
NSThread myThread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:nil];
[myThread start];
优点:NSThread 比其他两个轻量级。 缺点:需要自己管理线程的生命周期,线程同步,线程同步时对数据的加锁会有一定的系统开销。
2.Cocoa Operation
NSOperationQueue
oprationQueue= [[NSOperationQueuealloc] init];
oprationQueueaddOperationWithBlock:^{
//这个block语句块在子线程中执行
}
优点:不需要关心线程管理,数据同步的事情。 Cocoa Operation 相关的类是 NSOperation ,NSOperationQueue。NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行,我们会把我们的执行操作放在NSOperation中main函数中。
3.GCD Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法,GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。它让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务,一个任务可以是一个函数(function)或者是一个block。 dispatch queue分为下面三种: private dispatch queues,同时只执行一个任务,通常用于同步访问特定的资源或数据。 global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。 Main dispatch queue 它是在应用程序主线程上执行任务的。 GCD 扫盲篇;

7.单例的弊端?
优点:
1:一个类只被实例化一次,提供了对唯一实例的受控访问。
2:节省系统资源
3:允许可变数目的实例。
缺点:
1:一个类只有一个对象,可能造成责任过重,在一定程度上违背了“单一职责原则”。
2:由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
3:滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

8.App启动过慢,你可能想到的因素有哪些?

  1. App启动过程

    1. 解析Info.plist
      • 加载相关信息,例如如闪屏
      • 沙箱建立、权限检查
    2. Mach-O加载
      • 如果是胖二进制文件,寻找合适当前CPU类别的部分
      • 加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
      • 定位内部、外部指针引用,例如字符串、函数等
      • 执行声明为attribute((constructor))的C函数
      • 加载类扩展(Category)中的方法
      • C++静态对象加载、调用ObjC的 +load 函数
    3. 程序执行
      • 调用main()
      • 调用UIApplicationMain()
      • 调用applicationWillFinishLaunching
      2、影响启动性能的因素
    4. main()函数之前耗时的影响因素
      • 动态库加载越多,启动越慢。
      • ObjC类越多,启动越慢
      • C的constructor函数越多,启动越慢
      • C++静态对象越多,启动越慢
      • ObjC的+load越多,启动越慢
    5. main()函数之后耗时的影响因素
      • 执行main()函数的耗时
      • 执行applicationWillFinishLaunching的耗时
      • rootViewController及其childViewController的加载、view及其subviews的加载

9.怎么防止反编译?
1. 本地数据加密。
• iOS应用防反编译加密技术之一:对NSUserDefaults,sqlite存储文件数据加密,保护帐号和关键信息
2. URL编码加密。
• iOS应用防反编译加密技术之二:对程序中出现的URL进行编码加密,防止URL被静态分析
3. 网络传输数据加密。
• iOS应用防反编译加密技术之三:对客户端传输数据提供加密方案,有效防止通过网络接口的拦截获取数据
4. 方法体,方法名高级混淆。
• iOS应用防反编译加密技术之四:对应用程序的方法名和方法体进行混淆,保证源码被逆向后无法解析代码
5. 程序结构混排加密。
• iOS应用防反编译加密技术之五:对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低

10.TCP和UDP的区别于联系?
• TCP为传输控制层协议,为面向连接、可靠的、点到点的通信;
• UDP为用户数据报协议,非连接的不可靠的点到多点的通信;
• TCP侧重可靠传输,UDP侧重快速传输。

11.TCP连接的三次握手?
第一次握手:客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SEND 状态,等待服务器确认;
第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开 TCP 连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)

12.Scoket连接和HTTP连接的区别:
区别:
• HTTP协议是基于TCP连接的,是应用层协议,主要解决如何包装数据。Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
• HTTP连接:短连接,客户端向服务器发送一次请求,服务器响应后连接断开,节省资源。服务器不能主动给客户端响应(除非采用HTTP长连接技术),iPhone主要使用类NSURLConnection。
• Socket连接:长连接,客户端跟服务器端直接使用Socket进行连接,没有规定连接后断开,因此客户端和服务器段保持连接通道,双方可以主动发送数据,一般多用于游戏.Socket默认连接超时时间是30秒,默认大小是8K(理解为一个数据包大小)。

13.HTTP协议的特点,关于HTTP请求GET和POST的区别?
GET和POST的区别:
• HTTP超文本传输协议,是短连接,是客户端主动发送请求,服务器做出响应,服务器响应之后,链接断开。HTTP是一个属于应用层面向对象的协议,HTTP有两类报文:请求报文和响应报文。
• HTTP请求报文:一个HTTP请求报文由请求行、请求头部、空行和请求数据4部分组成。
• HTTP响应报文:由三部分组成:状态行、消息报头、响应正文。
• GET请求:参数在地址后拼接,没有请求数据,不安全(因为所有参数都拼接在地址后面),不适合传输大量数据(长度有限制,为1024个字节)。
GET提交、请求的数据会附在URL之后,即把数据放置在HTTP协议头中。
以分割URL和传输数据,多个参数用&连接。如果数据是英文字母或数字,原样发送,
如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密。
• POST请求:参数在请求数据区放着,相对GET请求更安全,并且数据大小没有限制。把提交的数据放置在HTTP包的包体中.
• GET提交的数据会在地址栏显示出来,而POST提交,地址栏不会改变。
传输数据的大小:
• GET提交时,传输数据就会受到URL长度限制,POST由于不是通过URL传值,理论上书不受限。
安全性:
• POST的安全性要比GET的安全性高;
• 通过GET提交数据,用户名和密码将明文出现在URL上,比如登陆界面有可能被浏览器缓存。
• HTTPS:安全超文本传输协议(Secure Hypertext Transfer Protocol),它是一个安全通信通道,基于HTTP开发,用于客户计算机和服务器之间交换信息,使用安全套结字层(SSI)进行信息交换,即HTTP的安全版。

14.ASIHttpRequest、AFNetWorking之间的区别?
ASIHttpRequest功能强大,主要是在MRC下实现的,是对系统CFNetwork API进行了封装,支持HTTP协议的CFHTTP,配置比较复杂,并且ASIHttpRequest框架默认不会帮你监听网络改变,如果需要让ASIHttpRequest帮你监听网络状态改变,并且手动开始这个功能。
AFNetWorking构建于NSURLConnection、NSOperation以及其他熟悉的Foundation技术之上。拥有良好的架构,丰富的API及模块构建方式,使用起来非常轻松。它基于NSOperation封装的,AFURLConnectionOperation子类。
ASIHttpRequest是直接操作对象ASIHttpRequest是一个实现了NSCoding协议的NSOperation子类;AFNetWorking直接操作对象的AFHttpClient,是一个实现NSCoding和NSCopying协议的NSObject子类。
同步请求:ASIHttpRequest直接通过调用一个startSynchronous方法;AFNetWorking默认没有封装同步请求,如果开发者需要使用同步请求,则需要重写getPath:paraments:success:failures方法,对于AFHttpRequestOperation进行同步处理。
性能对比:AFNetworking请求优于ASIHttpRequest;

15.XML数据解析方式各有什么不同,JSON解析有哪些框架?
XML数据解析的两种解析方式:DOM解析和SAX解析;
DOM解析必须完成DOM树的构造,在处理规模较大的XML文档时就很耗内存,占用资源较多,读入整个XML文档并构建一个驻留内存的树结构(节点树),通过遍历树结构可以检索任意XML节点,读取它的属性和值,通常情况下,可以借助XPath查询XML节点;
SAX与DOM不同,它是事件驱动模型,解析XML文档时每遇到一个开始或者结束标签、属性或者一条指令时,程序就产生一个事件进行相应的处理,一边读取XML文档一边处理,不必等整个文档加载完才采取措施,当在读取解析过程中遇到需要处理的对象,会发出通知进行处理。因此,SAX相对于DOM来说更适合操作大的XML文档。
-JSON解析:性能比较好的主要是第三方的JSONKIT和iOS自带的JSON解析类,其中自带的JSON解析性能最高,只能用于iOS5之后。

16.SVN的使用?
SVN=版本控制+备份服务器,可以把SVN当成备份服务器,并且可以帮助你记住每次上服务器的档案内容,并自动赋予每次变更的版本;
SVN的版本控制:所有上传版本都会帮您记录下来,也有版本分支及合并等功能。SVN可以让不同的开发者存取同样的档案,并且利用SVN Server作为档案同步的机制,即您有档案更新时,无需将档案寄送给您的开发成员。SVN的存放档案方式是采用差异备份的方式,即会备份到不同的地方,节省硬盘空间,也可以对非文字文件进行差异备份。
SVN的重要性:备份工作档案的重要性、版本控管的重要性、伙伴间的数据同步的重要性、备份不同版本是很耗费硬盘空间的;
防止冲突:
1.防止代码冲突:不要多人同时修改同一文件,例如:A、B都修改同一个文件,先让A修改,然后提交到服务器,然后B更新下来,再进行修改;
2.服务器上的项目文件Xcodeproj,仅让一个人管理提交,其他人只更新,防止文件发生冲突。

17.如何进行网络消息推送?
一种是Apple自己提供的通知服务(APNS服务器)、一种是用第三方推送机制。
首先应用发送通知,系统弹出提示框询问用户是否允许,当用户允许后向苹果服务器(APNS)请求deviceToken,并由苹果服务器发送给自己的应用,自己的应用将DeviceToken发送自己的服务器,自己服务器想要发送网络推送时将deviceToken以及想要推送的信息发送给苹果服务器,苹果服务器将信息发送给应用。
推送信息内容,总容量不超过256个字节;
iOS SDK本身提供的APNS服务器推送,它可以直接推送给目标用户并根据您的方式弹出提示。
优点:不论应用是否开启,都会发送到手机端;
缺点:消息推送机制是苹果服务端控制,个别时候可能会有延迟,因为苹果服务器也有队列来处理所有的消息请求;
第三方推送机制,普遍使用Socket机制来实现,几乎可以达到即时的发送到目标用户手机端,适用于即时通讯类应用。
优点:实时的,取决于心跳包的节奏;
缺点:iOS系统的限制,应用不能长时间的后台运行,所以应用关闭的情况下这种推送机制不可用。

18.网络七层协议?
应用层:
1.用户接口、应用程序;
2.Application典型设备:网关;
3.典型协议、标准和应用:TELNET、FTP、HTTP
表示层:
1.数据表示、压缩和加密presentation
2.典型设备:网关
3.典型协议、标准和应用:ASCLL、PICT、TIFF、JPEG|MPEG
4.表示层相当于一个东西的表示,表示的一些协议,比如图片、声音和视频MPEG。
会话层:
1.会话的建立和结束;
2.典型设备:网关;
3.典型协议、标准和应用:RPC、SQL、NFS、X WINDOWS、ASP
传输层:
1.主要功能:端到端控制Transport;
2.典型设备:网关;
3.典型协议、标准和应用:TCP、UDP、SPX
网络层:
1.主要功能:路由、寻址Network;
2.典型设备:路由器;
3.典型协议、标准和应用:IP、IPX、APPLETALK、ICMP;
数据链路层:
1.主要功能:保证无差错的疏忽链路的data link;
2.典型设备:交换机、网桥、网卡;
3.典型协议、标准和应用:802.2、802.3ATM、HDLC、FRAME RELAY;
物理层:
1.主要功能:传输比特流Physical;
2.典型设备:集线器、中继器
3.典型协议、标准和应用:V.35、EIA/TIA-232.

19.关键字volatile有什么含意?并给出三个不同的例子:
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
• 并行设备的硬件寄存器(如:状态寄存器);
•一个中断服务子程序中会访问到的非自动变量(Non-automatic variables);
• 多线程应用中被几个任务共享的变量。

20.property属性的修饰符的作用?
getter=getName、setter=setName:设置setter与getter的方法名;
readwrite、readonly:设置可供访问级别;
assign:方法直接赋值,不进行任何retain操作,为了解决原类型与环循引用问题;
retain:其setter方法对参数进行release旧值再* retain新值,所有实现都是这个顺序;
copy:其setter方法进行copy操作,与retain处理流程一样,先对旧值release,再copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。
nonatomic:非原子性访问,不加同步, 多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。

21.XIB与Storyboards的优缺点?
优点:
• XIB:在编译前就提供了可视化界面,可以直接拖控件,也可以直接给控件添加约束,更直观一些,而且类文件中就少了创建控件的代码,确实简化不少,通常每个XIB对应一个类。
• Storyboard:在编译前提供了可视化界面,可拖控件,可加约束,在开发时比较直观,而且一个storyboard可以有很多的界面,每个界面对应一个类文件,通过storybard,可以直观地看出整个App的结构。
缺点:
• XIB:需求变动时,需要修改XIB很大,有时候甚至需要重新添加约束,导致开发周期变长。XIB载入相比纯代码自然要慢一些。对于比较复杂逻辑控制不同状态下显示不同内容时,使用XIB是比较困难的。当多人团队或者多团队开发时,如果XIB文件被发动,极易导致冲突,而且解决冲突相对要困难很多。
• Storyboard:需求变动时,需要修改storyboard上对应的界面的约束,与XIB一样可能要重新添加约束,或者添加约束会造成大量的冲突,尤其是多团队开发。对于复杂逻辑控制不同显示内容时,比较困难。当多人团队或者多团队开发时,大家会同时修改一个storyboard,导致大量冲突,解决起来相当困难。

22.内存的使用和优化的注意事项?
重用问题:如UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews设置正确的reuseIdentifier,充分重用;
尽量把views设置为不透明:当opque为NO的时候,图层的半透明取决于图片和其本身合成的图层为结果,可提高性能;
不要使用太复杂的XIB/Storyboard:载入时就会将XIB/storyboard需要的所有资源,包括图片全部载入内存,即使未来很久才会使用。那些相比纯代码写的延迟加载,性能及内存就差了很多;
选择正确的数据结构:学会选择对业务场景最合适的数组结构是写出高效代码的基础。比如,数组: 有序的一组值。使用索引来查询很快,使用值查询很慢,插入/删除很慢。字典: 存储键值对,用键来查找比较快。集合: 无序的一组值,用值来查找很快,插入/删除很快。
gzip/zip压缩:当从服务端下载相关附件时,可以通过gzip/zip压缩后再下载,使得内存更小,下载速度也更快。
延迟加载:对于不应该使用的数据,使用延迟加载方式。对于不需要马上显示的视图,使用延迟加载方式。比如,网络请求失败时显示的提示界面,可能一直都不会使用到,因此应该使用延迟加载。
数据缓存:对于cell的行高要缓存起来,使得reload数据时,效率也极高。而对于那些网络数据,不需要每次都请求的,应该缓存起来,可以写入数据库,也可以通过plist文件存储。
处理内存警告:一般在基类统一处理内存警告,将相关不用资源立即释放掉
重用大开销对象:一些objects的初始化很慢,比如NSDateFormatter和NSCalendar,但又不可避免地需要使用它们。通常是作为属性存储起来,防止反复创建。
避免反复处理数据:许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要;
使用Autorelease Pool:在某些循环创建临时变量处理数据时,自动释放池以保证能及时释放内存;
正确选择图片加载方式:UIImage加载方式

23.基于CTMediator的组件化方案,有哪些核心组成?
假如主APP调用某业务A,那么需要以下组成部分:
• CTMediator类,该类提供了函数 - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary )params shouldCacheTarget:(BOOL)shouldCacheTarget;
这个函数可以根据targetName生成对象,根据actionName构造selector,然后可以利用performSelector:withObject:方法,在目标上执行动作。
• 业务A的实现代码,另外要加一个专门的类,用于执行Target Action
类的名字的格式:Target_%@,这里就是Target_A。
这个类里面的方法,名字都以Action_开头,需要传参数时,都统一以NSDictionary
的形式传入。
CTMediator类会创建Target类的对象,并在对象上执行方法。
• 业务A的CTMediator扩展
扩展里声明了所有A业务的对外接口,参数明确,这样外部调用者可以很容易理解如何调用接口。
在扩展的实现里,对Target, Action需要通过硬编码进行指定。由于扩展的负责方和业务的负责方是相同的,所以这个不是问题。

24.为什么CTMediator方案优于基于Router的方案?
Router的缺点:
• 在组件化的实施过程中,注册URL并不是充分必要条件。组件是不需要向组件管理器注册URL的,注册了URL之后,会造成不必要的内存常驻。注册URL的目的其实是一个服务发现的过程,在iOS领域中,服务发现的方式是不需要通过主动注册的,使用runtime就可以了。另外,注册部分的代码的维护是一个相对麻烦的事情,每一次支持新调用时,都要去维护一次注册列表。如果有调用被弃用了,是经常会忘记删项目的。runtime由于不存在注册过程,那就也不会产生维护的操作,维护成本就降低了。 由于通过runtime做到了服务的自动发现,拓展调用接口的任务就仅在于各自的模块,任何一次新接口添加,新业务添加,都不必去主工程做操作,十分透明。
• 在iOS领域里,一定是组件化的中间件为openURL提供服务,而不是openURL方式为组件化提供服务。如果在给App实施组件化方案的过程中是基于openURL的方案的话,有一个致命缺陷:非常规对象(不能被字符串化到URL中的对象,例如UIImage)无法参与本地组件间调度。
• 在本地调用中使用URL的方式其实是不必要的,如果业务工程师在本地间调度时需要给出URL,那么就不可避免要提供params,在调用时要提供哪些params是业务工程师很容易懵逼的地方。
• 为了支持传递非常规参数,蘑菇街的方案采用了protocol,这个会侵入业务。由于业务中的某个对象需要被调用,因此必须要符合某个可被调用的protocol,然而这个protocol又不存在于当前业务领域,于是当前业务就不得不依赖public Protocol。这对于将来的业务迁移是有非常大的影响的。
CTMediator的优点:
• 调用时,区分了本地应用调用和远程应用调用。本地应用调用为远程应用调用提供服务。
• 组件仅通过Action暴露可调用接口,模块与模块之间的接口被固化在了Target-Action这一层,避免了实施组件化的改造过程中,对Business的侵入,同时也提高了组件化接口的可维护性。
• 方便传递各种类型的参数。

25.MVVM设计模式?
MVVM组成
在这个设计模式里,核心是ViewModel,它是一种特殊类型的model,代表了应用中UI的状态。它包含如下内容:
• 每个UI控件的一些属性。例如,text field控件的当前文本,某个button是否是enable状态。
• 视图可以执行的动作,例如按钮点击或者是手势。
将ViewModel想象为视图的模型,会比较容易理解。
MVVM模式中,三个组件的关系比MVC模式的要简单,有下面的严格规则:
• 视图引用ViewModel,但反向不成立。
• ViewModel引用Model,但反向不成立。
如果违背了上面两条规则,那么就是错误的MVVM实施行为。
这种模式的好处:
• 轻量级的视图(控制器), 所有的UI逻辑都位于ViewModel中。
• 易测试性。可以在没有视图的情况下,运行整个应用。

26.weak修饰的释放则自动被置为nil的实现原理?
Runtime维护着一个Weak表,用于存储指向某个对象的所有Weak指针
Weak表是Hash表,Key是所指对象的地址,Value是Weak指针地址的数组
在对象被回收的时候,经过层层调用,会最终触发下面的方法将所有Weak指针的值设为nil。* runtime源码,objc-weak.m 的 arr_clear_deallocating 函数
weak指针的使用涉及到Hash表的增删改查,有一定的性能开销.

27.HTTPS的加密原理?
服务器端用非对称加密(RSA)生成公钥和私钥
然后把公钥发给客户端, 服务器则保存私钥
客户端拿到公钥后, 会生成一个密钥, 这个密钥就是将来客户端和服务器用来通信的钥匙
然后客户端用公钥对密钥进行加密, 再发给服务器
服务器拿到客户端发来的加密后的密钥后, 再使用私钥解密密钥, 到此双方都获得通信的钥匙

28.你认为开发中那些导致crash?
当iOS设备上的App应用闪退时,操作系统会生成一个crash日志,保存在设备上。crash日志上有很多有用的信息,比如每个正在执行线程的完整堆栈跟踪信息和内存映像,这样就能够通过解析这些信息进而定位crash发生时的代码逻辑,从而找到App闪退的原因。
通常来说,crash产生来源于两种问题:违反iOS系统规则导致的crash和App代码逻辑BUG导致的crash
1.应用逻辑的Bug
• SEGV:(Segmentation Violation,段违例),无效内存地址,比如空指针,未初始化指针,栈溢出等;
• SIGABRT:收到Abort信号,可能自身调用abort()或者收到外部发送过来的信号;
• SIGBUS:总线错误。与SIGSEGV不同的是,SIGSEGV访问的是无效地址(比如虚存映射不到物理内存),而SIGBUS访问的是有效地址,但总线访问异常(比如地址对齐问题);
• SIGILL:尝试执行非法的指令,可能不被识别或者没有权限;
• SIGFPE:Floating Point Error,数学计算相关问题(可能不限于浮点计算),比如除零操作;
• SIGPIPE:管道另一端没有进程接手数据; 常见的崩溃原因基本都是代码逻辑问题或资源问题,比如数组越界,访问野指针或者资源不存在,或资源大小写错误等
2.违反iOS系统规则产生crash的三种类型
• 内存报警闪退
◦ 当iOS检测到内存过低时,它的VM系统会发出低内存警告通知,尝试回收一些内存;如果情况没有得到足够的改善,iOS会终止后台应用以回收更多内存;最后,如果内存还是不足,那么正在运行的应用可能会被终止掉。在Debug模式下,可以主动将客户端执行的动作逻辑写入一个log文件中,这样程序童鞋可以将内存预警的逻辑写入该log文件,当发生如下截图中的内存报警时,就是提醒当前客户端性能内存吃紧,可以通过Instruments工具中的Allocations 和 Leaks模块库来发现内存分配问题和内存泄漏问题。
• 响应超时
◦ 当应用程序对一些特定的事件(比如启动、挂起、恢复、结束)响应不及时,苹果的Watchdog机制会把应用程序干掉,并生成一份相应的crash日志。
• 用户强制退出
◦ 一看到“用户强制退出”,首先可能想到的双击Home键,然后关闭应用程序。不过这种场景一般是不会产生crash日志的,因为双击Home键后,所有的应用程序都处于后台状态,而iOS随时都有可能关闭后台进程,当应用阻塞界面并停止响应时这种场景才会产生crash日志。这里指的“用户强制退出”场景,是稍微比较复杂点的操作:先按住电源键,直到出现“滑动关机”的界面时,再按住Home键,这时候当前应用程序会被终止掉,并且产生一份相应事件的crash日志。

29.分析下SDWebImage?
1.SDWebImage 加载图片的流程
1.入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
2.进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.
3.先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
4.SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
5.如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
6.根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
7.如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
8.如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
9.共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
10.图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
11.connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。
12.connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
13.图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
14.在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
15.imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
16.通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
17.将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
18.SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
19.SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
20.SDWebImagePrefetcher 可以预先下载图片,方便后续使用。
SDWebImage原理图
2. SDImageCache是怎么做数据管理的?
SDImageCache分两个部分,一个是内存层面的,一个是硬盘层面的。内存层面的相当是个缓存器,以Key-Value的形式存储图片。当内存不够的时候会清除所有缓存图片。用搜索文件系统的方式做管理,文件替换方式是以时间为单位,剔除时间大于一周的图片文件。当SDWebImageManager向SDImageCache要资源时,先搜索内存层面的数据,如果有直接返回,没有的话去访问磁盘,将图片从磁盘读取出来,然后做Decoder,将图片对象放到内存层面做备份,再返回调用层。
3.内部做Decoder的原因 (典型的空间换时间)
由于UIImage的imageWithData函数是每次画图的时候才将Data解压成ARGB的图像,所以在每次画图的时候,会有一个解压操作,这样效率很低,但是只有瞬时的内存需求。为了提高效率通过SDWebImageDecoder将包装在Data下的资源解压,然后画在另外一张图片上,这样这张新图片就不再需要重复解压了

30.SEL和Method和IMP分别说下再谈下对IMP的理解?
• SEL是“selector”的一个类型,表示一个方法的名字
• Method(我们常说的方法)表示一种类型,这种类型与selector和实现(implementation)相关
• IMP定义为 id (*IMP) (id, SEL, …)。这样说来,IMP是一个指向函数的指针,这个被指向的函数包括id(“self”指针),调用的SEL(方法名),再加上一些其他参数.说白了IMP就是实现方法。
• 知名框架AFN源码涉及IMP的代码
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];

    while (class_getInstanceMethod(currentClass, @selector(resume))) {Class superClass = [currentClass superclass];IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));if (classResumeIMP != superclassResumeIMP &&originalAFResumeIMP != classResumeIMP) {[self swizzleResumeAndSuspendMethodForClass:currentClass];}currentClass = [currentClass superclass];}[localDataTask cancel];[session finishTasksAndInvalidate];

31.Autorelease的原理 ?
• ARC下面,我们使用@autoreleasepool{}来使用一个Autoreleasepool,实际上UIKit 通过RunLoopObserver 在RunLoop二次Sleep间Autoreleasepool进行Pop和Push,将这次Loop产生的autorelease对象释放 对编译器会编译大致如下:
void *DragonLiContext = objc_ AutoreleasepoolPush();
// {} 的 code
objc_ AutoreleasepoolPop(DragonLiContext);

32.ARC的工作原理?
Automatic Reference Counting,自动引用计数,即ARC,ARC会自动帮你插入retain和release语句,ARC编译器有两部分,分别是前端编译器和优化器
前端编译器:前端编译器会为“拥有的”每一个对象插入相应的release语句。如果对象的所有权修饰符是__strong,那么它就是被拥有的。如果在某个方法内创建了一个对象,前端编译器会在方法末尾自动插入release语句以销毁它。而类拥有的对象(实例变量/属性)会在dealloc方法内被释放。事实上,你并不需要写dealloc方法或调用父类的dealloc方法,ARC会自动帮你完成一切。此外,由编译器生成的代码甚至会比你自己写的release语句的性能还要好,因为编辑器可以作出一些假设。在ARC中,没有类可以覆盖release方法,也没有调用它的必要。ARC会通过直接使用objc_release来优化调用过程。而对于retain也是同样的方法。ARC会调用objc_retain来取代保留消息
ARC优化器: 虽然前端编译器听起来很厉害的样子,但代码中有时仍会出现几个对retain和release的重复调用。ARC优化器负责移除多余的retain和release语句,确保生成的代码运行速度高于手动引用计数的代码。

33.用户需要上传和下载一个重要的资料文件,应该如何判断用户本次是否上传成功和下载成功了?
用MD5验证文件的完整性!(仅仅通过代码来判断当前次的请求发送结束或者收到数据结束不可以的)
当客户端上传一个文件的时候,在请求body里面添加该文件的MD5值来告诉服务器,服务器接受文件完毕以后通过校验收到的文件的MD5值与请求body里面的MD5值来最终确定本次上传是否成功
当客户端下载一个文件的时候,在响应头里面收到了服务器附带的该文件的MD5值,文件下载结束以后,通过获取下载后文件的MD5值与本次请求服务器返回的响应头中的MD5值做一个比较,来最终判断本次下载是否成功
MD5,是一个将任意长度的数据字符串转化成短的固定长度的值的单向操作。任意两个字符串不应有相同的散列值
MD5校验可以应用在多个领域,比如说机密资料的检验,下载文件的检验,明文密码的加密等。MD5校验原理举例:如客户往我们数据中心同步一个文件,该文件使用MD5校验,那么客户在发送文件的同时会再发一个存有校验码的文件,我们拿到该文件后做MD5运算,得到的计算结果与客户发送的校验码相比较,如果一致则认为客户发送的文件没有出错,否则认为文件出错需要重新发送。

34.isa指针的作用?
对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。isa帮助一个对象找到它的方法
是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。

35.与 NSURLConnection 相比,NSURLsession 改进哪些?
• 可以配置每个 session 的缓存,协议,cookie,以及证书策略(credential policy),甚至跨程序共享这些信息
• session task。它负责处理数据的加载以及文件和数据在客户端与服务端之间的上传和下载。NSURLSessionTask 与 NSURLConnection 最大的相似之处在于它也负责数据的加载,最大的不同之处在于所有的 task 共享其创造者 NSURLSession 这一公共委托者(common delegate)

36.使用drawRect有什么影响?
drawRect方法依赖Core Graphics框架来进行自定义的绘制
• 缺点:它处理touch事件时每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例,那就会很糟糕了
• 这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为dirty,但还是会显示原来的内容,直到下一次的视图渲染周期,才会将标记为 dirty 的图层重新建立Core Graphics上下文,然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制

37.什么时候会报unrecognized selector的异常?如何避免?
当调用该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过“消息转发”进行解决,如果还是不行就会报unrecognized selector异常
objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:
• objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类然后在该类中的方法列表以及其父类方法列表中寻找方法运行 如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会
三次拯救程序崩溃的机会
• Method resolution:objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。 如果你添加了函数并返回 YES,那运行时系统就会重新启动一次消息发送的过程 如果 resolve 方法返回 NO ,运行时就会移到下一步,消息转发
• Fast forwarding:如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但Normal forwarding转发会创建一个NSInvocation对象,相对Normal forwarding转发更快点,所以这里叫Fast forwarding
• Normal forwarding 这一步是Runtime最后一次给你挽救的机会。 首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。 如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。 如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象

38.iOS中常用的数据存储方式有哪些?
综合
• 所有的本地持久化数据存储的本质都是写文件,而且只能存到沙盒中。
• 沙盒机制是苹果的一项安全机制,本质就是系统给每个应用分配了一个文件夹来存储数据,而且每个应用只能访问分配给自己的那个文件夹,其他应用的文件夹是不能访问的。
• 数据存储的核心都是写文件。主要有四种持久化方式:属性列表,对象序列化,SQLite 数据库, CoreData
• 属性列表:应用于少量数据存储,比如登陆的用户信息,应用程序配置信息等。只有NSString ,NSArray,NSDictory,NSData,可以WriteToFile;存储的依旧是plist文件,plist文件可以存储的7种数据类型:array,dictory,string,bool,data,date,number。
详细
• 对象序列化:最终也是存为属性列表文件,如果程序中,需要存储的时候,直接存储对象比较方便,例如有一个设置类,我们可以把设置类的对象直接存储,就没必要再把里面的每一个属性单独存到文件中。对象序列化是将一个实现了NSCoding协议的对象,通过序列化(NSKeydArchiver)的形式,将对象中的属性抽取出来,转化成二进制流,也就是NSData,NSData可以选择write to file 或者存储到NSUserdefault中。 必须实现的两个方法 encodeWithCoder,initWithCoder。对象序列化的本质就是 对象NSData。
• SQLite: 适合大量,重复,有规律的数据存储。而且频繁的读取,删除,过滤数据,这种适合使用数据库 (iOS 使用第三方FMDB)
• CoreData: Sqlite叫做关系型数据库,CoreData 是一中OR-Mapping的思想 ,O代表对象Object,R代表relationship,Mapping代表映射,直译过来就是对象关系映射,其实就是把对象的属性和表中的字段自动映射,简化程序员的负担,以面向对象的方式操作数据库。ORMapping是一种思想,CoreData实现了这种思想,在Java中,hibernate 也是对ORMapping的一种实现,只是利用java实现的。
• CoreData 本质还是数据库,只不过使用起来更加面向对象,不关注二维的表结构,而是只需要关注对象,纯面向对象的数据操作方式。我们直接使用数据库的时候,如果向数据库中插入数据,一般是把一个对象的属性和数据库中某个表的字段一一对应,然后把对象的属性存储到具体的表字段中.取一条数据的时候,把表中的一行数据取出,同样需要再封装到对象的属性中,这样的方式有点繁琐,不面向对象。CoreData解决的问题就是不需要这个中间的转换过程,看起来是直接把对象存储进去,并且取出来,不关心表的存在,实际内部帮你做好了映射关系。

39.描述一个ViewController的生命周期?
当我们调用UIViewControlller的view时,
系统首先判断当前的 UIViewControlller是否存在* view,如果存在直接返回view,
如果不存在的话,会调用loadview方法,
然后判断loadview方法是否是自定义方法,
如果是自定义方法,就执行自定义方法,
如果不是自定义方法,判断当时视图控制器是否有* xib、stroyboard。
如果有xib、stroyboard 就加载xib、stroyboard。
如果没有创建一个空白的view。
调用viewDidLoad方法。
最后返回view

40.Block中可以修改全局变量,全局静态变量,局部静态变量吗?
可以.深入研究Block捕获外部变量和__block实现原理
• 全局变量和静态全局变量的值改变,以及它们被Block捕获进去,因为是全局的,作用域很广
• 静态变量和自动变量,被Block从外面捕获进来,成为__main_block_impl_0这个结构体的成员变量
• 自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。
• Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量
• Block就分为以下3种
◦ _NSConcreteStackBlock:只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。 StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了,是不持有对象的
▪ _NSConcreteStackBlock所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈copy到堆。比如当Block作为函数返回值的时候,肯定会copy到堆上
◦ _NSConcreteMallocBlock:有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制,是持有对象的
◦ _NSConcreteGlobalBlock:没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束,也不持有对象
ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上
ARC下,Block中引用id类型的数据有没有__block都一样都是retain,而对于基础变量而言,没有的话无法修改变量值,有的话就是修改其结构体令其内部的forwarding指针指向拷贝后的地址达到值的修改。

41.SDWebImage实现原理是什么? 它是如何解决tableView的复用时出现图片错乱问题的呢?
解决tableView复用错乱问题:每次都会调UIImageView+WebCache文件中的 [self sd_cancelCurrentImageLoad];
原理解释参考
• SDWebImageDownloader
• 图片的下载操作放在一个NSOperationQueue并发操作队列中,队列默认最大并发数是6
• 每个图片对应一些回调(下载进度,完成回调等),回调信息会存在downloader的URLCallbacks(一个字典,key是url地址,value是图片下载回调数组)中,URLCallbacks可能被多个线程访问,所以downloader把下载任务放在一个barrierQueue中,并设置屏障保证同一时间只有一个线程访问URLCallbacks。,在创建回调URLCallbacks的block中创建了一个NSOperation并添加到NSOperationQueue中
• 下载的核心是利用NSURLSession加载数据,每个图片的下载都有一个operation操作来完成,并将这些操作放到一个操作队列中,这样可以实现图片的并发下载。
• 内存缓存的处理由NSCache对象实现,NSCache类似一个集合的容器,它存储key-value对,类似于nsdictionary类,我们通常使用缓存来临时存储短时间使用但创建昂贵的对象,重用这些对象可以优化新能,同时这些对象对于程序来说不是紧要的,如果内存紧张就会自动释放。
• 先在内存中放置一份缓存,如果需要缓存到磁盘,将磁盘缓存操作作为一个task放到串行队列中处理,会先检查图片格式是jpeg还是png,将其转换为响应的图片数据,最后吧数据写入磁盘中(文件名是对key值做MD5后的串)。

42.AFNetworking 底层原理分析?
AFNetworking主要是对NSURLSession和NSURLConnection(iOS9.0废弃)的封装,其中主要有以下类:
1). AFHTTPRequestOperationManager:内部封装的是 NSURLConnection, 负责发送网络请求, 使用最多的一个类。(3.0废弃)
2). AFHTTPSessionManager:内部封装是 NSURLSession, 负责发送网络请求,使用最多的一个类。
3). AFNetworkReachabilityManager:实时监测网络状态的工具类。当前的网络环境发生改变之后,这个工具类就可以检测到。
4). AFSecurityPolicy:网络安全的工具类, 主要是针对 HTTPS 服务。
5). AFURLRequestSerialization:序列化工具类,基类。上传的数据转换成JSON格式
(AFJSONRequestSerializer).使用不多。
6). AFURLResponseSerialization:反序列化工具类;基类.使用比较多:
7). AFJSONResponseSerializer; JSON解析器,默认的解析器.
8). AFHTTPResponseSerializer; 万能解析器; JSON和XML之外的数据类型,直接返回二进制数据.对服务器返回的数据不做任何处理.
9). AFXMLParserResponseSerializer; XML解析器;

43.描述下SDWebImage里面给UIImageView加载图片的逻辑?
SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。
加载图片的过程大致如下:
1. 首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
2. 如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
3. 如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
4. 下载后的图片会加入缓存中,并写入磁盘中
5. 整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
SDWebImage原理:
调用类别的方法:
1. 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。
2. 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。
3. 从网络上获取,使用,缓存到内存,缓存到沙盒。

44.分析json、xml 的区别? json、xml 解析 式的底层是如何让处理的?
(一)JSON与XML的区别:
(1)可读性方面:基本相同,XML的可读性比较好;
(2)可扩展性方面:都具有良好的扩展性;
(3)编码难度方面:相对而言,JSON的编码比较容易;
(4)解码难度:JSON的解码难度基本为零,XML需要考虑子节点和父节点;
(5)数据体积方面:JSON相对于XML来讲,数据体积小,传递的速度比较快;
(6)数据交互方面:JSON与javascript的交互更加方便,更容易解析处理,更好的数据交互;
(7)数据描述方面:XML对数据描述性比较好
(8)传输速度方面:JSON的速度远远快于XML。
(二)JSON与XML底层实现原理:
(1)JSON底层原理:遍历字符串中的字符,最终根据格式规定的特殊字符,比如{}、[]、:等进行区分,{}号表示字典,[]号表示数组,:号是字典的键和值的分水岭,最终仍是将JSON转化为字典,只不过字典中的值可能是“字典、数组或者字符串而已”。
  (2)XML底层原理:XML解析常用的解析方法有两种:DOM解析和SAX解析;DOM采用的是树形结构的方式访问XML文档,而SAX采用的是事件模型;DOM解析把XML文档转化为一个包含其内容的树,并可以对树进行遍历,使用DOM解析器的时候需要处理整个XML文档,所以对内存和性能的要求比较高;SAX在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,他可以激活一个回调方法,告诉该方法指定的标签已经找到,SAX对内存的要求通常会比较低,因为他让开发人员自己来决定所要处理的tag,特别是当开发人员只需要处理文档中所包含部分数据时,SAX这种扩展能力得到了更好的体现。

45.对程序性能的优化你有什么建议?
1.使用复用机制
2.尽可能设置 View 为不透明
3.避免臃肿的 XIB 文件
4.不要阻塞主线程
5.图片尺寸匹配 UIImageView
6.选择合适的容器
7.启用 GZIP 数据压缩
8.View 的复用和懒加载机制
9、缓存
服务器的响应信息(response)。
图片。
计算值。比如:UITableView 的 row heights。
10.关于图形绘制
11.处理 Memory Warnings
在 AppDelegate 中实现 - [AppDelegate applicationDidReceiveMemoryWarning:] 代理方法。
在 UIViewController 中重载 didReceiveMemoryWarning 方法。
监听 UIApplicationDidReceiveMemoryWarningNotification 通知。
12.复用高开销的对象
13.减少离屏渲染(设置圆角和阴影的时候可以选用绘制的方法)
14.优化 UITableView
通过正确的设置 reuseIdentifier 来重用 Cell。
尽量减少不必要的透明 View。
尽量避免渐变效果、图片拉伸和离屏渲染。
当不同的行的高度不一样时,尽量缓存它们的高度值。
如果 Cell 展示的内容来自网络,确保用异步加载的方式来获取数据,并且缓存服务器的 response。
使用 shadowPath 来设置阴影效果。
尽量减少 subview 的数量,对于 subview 较多并且样式多变的 Cell,可以考虑用异步绘制或重写 drawRect。
尽量优化 - [UITableView tableView:cellForRowAtIndexPath:] 方法中的处理逻辑,如果确实要做一些处理,可以考虑做一次,缓存结果。
选择合适的数据结构来承载数据,不同的数据结构对不同操作的开销是存在差异的。
对于 rowHeight、sectionFooterHeight、sectionHeaderHeight 尽量使用常量。
15.选择合适的数据存储方式
在 iOS 中可以用来进行数据持有化的方案包括:
NSUserDefaults。只适合用来存小数据。
XML、JSON、Plist 等文件。JSON 和 XML 文件的差异在「选择正确的数据格式」已经说过了。
使用 NSCoding 来存档。NSCoding 同样是对文件进行读写,所以它也会面临必须加载整个文件才能继续的问题。
使用 SQLite 数据库。可以配合 FMDB 使用。数据的相对文件来说还是好处很多的,比如可以按需取数据、不用暴力查找等等。
使用 CoreData。也是数据库技术,跟 SQLite 的性能差异比较小。但是 CoreData 是一个对象图谱模型,显得更面向对象;SQLite 就是常规的 DBMS。
16.减少应用启动时间
快速启动应用对于用户来说可以留下很好的印象。尤其是第一次使用时。
保证应用快速启动的指导原则:
尽量将启动过程中的处理分拆成各个异步处理流,比如:网络请求、数据库访问、数据解析等等。
避免臃肿的 XIB 文件,因为它们会在你的主线程中进行加载。重申:Storyboard 没这个问题,放心使用。
注意:在测试程序启动性能的时候,最好用与 Xcode 断开连接的设备进行测试。因为 watchdog 在使用 Xcode 进行调试的时候是不会启动的。
17.使用 Autorelease Pool (内存释放池)
18.imageNamed 和 imageWithContentsOfFile

46.讲讲iOS事件响应链的原理?
1、响应者链通常是由视图(UIView)构成的;
2、一个视图的下一个响应者是它视图控制器(UIViewController)(如果有的话),然后再转给它的父视图(Super View);
3、视图控制器(如果有的话)的下一个响应者为其管理的视图的父视图;
4、单例的窗口(UIWindow)的内容视图将指向窗口本身作为它的下一个响应者
需要指出的是,Cocoa Touch应用不像Cocoa应用,它只有一个UIWindow对象,因此整个响应者链要简单一点;
5、单例的应用(UIApplication)是一个响应者链的终点,它的下一个响应者指向nil,以结束整个循环。

47.假如Controller太臃肿,如何优化?
1.将网络请求抽象到单独的类中
方便在基类中处理公共逻辑;
方便在基类中处理缓存逻辑,以及其它一些公共逻辑;
方便做对象的持久化。
2.将界面的封装抽象到专门的类中
构造专门的 UIView 的子类,来负责这些控件的拼装。这是最彻底和优雅的方式,不过稍微麻烦一些的是,你需要把这些控件的事件回调先接管,再都一一暴露回 Controller。
3.构造 ViewModel
借鉴MVVM。具体做法就是将 ViewController 给 View 传递数据这个过程,抽象成构造 ViewModel 的过程。
4.专门构造存储类
专门来处理本地数据的存取。
5.整合常量

48.介绍下App启动的完成过程?

  1. App启动过程
    • 解析Info.plist
    ▪ 加载相关信息,例如如闪屏
    ▪ 沙箱建立、权限检查
    • Mach-O加载
    ▪ 如果是胖二进制文件,寻找合适当前CPU类别的部分
    ▪ 加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
    ▪ 定位内部、外部指针引用,例如字符串、函数等
    ▪ 执行声明为attribute((constructor))的C函数
    ▪ 加载类扩展(Category)中的方法
    ▪ C++静态对象加载、调用ObjC的 +load 函数
    • 程序执行
    · 1.main函数
    · 2.执行UIApplicationMain函数
    ·   1.创建UIApplication对象
    ·   2.创建UIApplicationDelegate对象并复制
    ·   3.读取配置文件info.plist,设置程序启动的一些属性,(关于info.plist的内容可网上搜索下)
    ·   4.创建应用程序的Main Runloop循环
    · 3.UIApplicationDelegate对象开始处理监听到的事件
    ·   1.程序启动成功之后,首先调用application:didFinishLaunchingWithOptions:方法,
    ·   如果info.plist文件中配置了启动storyboard文件名,则加载storyboard文件。
    ·   如果没有配置,则根据代码来创建UIWindow—>UIWindow的rootViewController–>显示
    更多:iOS面试题合集

49.哈希原理?
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
哈希概念:哈希表的本质是一个数组,数组中每一个元素称为一个箱子(bin),箱子中存放的是键值对。

50.哈希存储过程?
1.根据 key 计算出它的哈希值 h。
2.假设箱子的个数为 n,那么这个键值对应该放在第 (h % n) 个箱子中。
3.如果该箱子中已经有了键值对,就使用开放寻址法或者拉链法解决冲突。
在使用拉链法解决哈希冲突时,每个箱子其实是一个链表,属于同一个箱子的所有键值对都会排列在链表中。
哈希表还有一个重要的属性: 负载因子(load factor),它用来衡量哈希表的空/满程度,一定程度上也可以体现查询的效率,计算公式为:
负载因子 = 总键值对数 / 箱子个数
负载因子越大,意味着哈希表越满,越容易导致冲突,性能也就越低。因此,一般来说,当负载因子大于某个常数(可能是 1,或者 0.75 等)时,哈希表将自动扩容。
哈希表在自动扩容时,一般会创建两倍于原来个数的箱子,因此即使 key 的哈希值不变,对箱子个数取余的结果也会发生改变,因此所有键值对的存放位置都有可能发生改变,这个过程也称为重哈希(rehash)。
哈希表的扩容并不总是能够有效解决负载因子过大的问题。假设所有 key 的哈希值都一样,那么即使扩容以后他们的位置也不会变化。虽然负载因子会降低,但实际存储在每个箱子中的链表长度并不发生改变,因此也就不能提高哈希表的查询性能。
基于以上总结,细心的朋友可能会发现哈希表的两个问题:
1.如果哈希表中本来箱子就比较多,扩容时需要重新哈希并移动数据,性能影响较大。
2.如果哈希函数设计不合理,哈希表在极端情况下会变成线性表,性能极低。
3.block和函数指针的理解;
相似点:
函数指针和Block都可以实现回调的操作,声明上也很相似,实现上都可以看成是一个代码片段。
函数指针类型和Block类型都可以作为变量和函数参数的类型。(typedef定义别名之后,这个别名就是一个类型)
不同点:
函数指针只能指向预先定义好的函数代码块(可以是其他文件里面定义,通过函数参数动态传入的),函数地址是在编译链接时就已经确定好的。
Block本质是Objective-C对象,是NSObject的子类,可以接收消息。
函数里面只能访问全局变量,而Block代码块不光能访问全局变量,还拥有当前栈内存和堆内存变量的可读性(当然通过__block访问指示符修饰的局部变量还可以在block代码块里面进行修改)。
从内存的角度看,函数指针只不过是指向代码区的一段可执行代码,而block实际上是程序运行过程中在栈内存动态创建的对象,可以向其发送copy消息将block对象拷贝到堆内存,以延长其生命周期。

51.NSDate\CFAbsoluteTimeGetCurrent\CACurrentMediaTime的区别?
1.从框架的角度
• NSDate 属于Foundation
• CFAbsoluteTimeGetCurrent 属于 Core Foundation
• CACurrentMediaTime 属于 QuartzCore
2.本质区别
• NSDate 或 CFAbsoluteTimeGetCurrent 返回的系统时钟时间将会从网络时钟同步.
• 从时钟偏移量的角度 mach_absolute_time() 和 CACurrentMediaTime 基于内建时钟.能够更精确的测试时间,并且不会根据外部的时间变化而变化.(例如,时区变化\夏时制),它和系统的upTime有关.系统重启后,CACurrentMediaTime 也会重新设置.
3.常用用法
• NSDate \CFAbsoluteTimeGetCurrent 常用于日常时间,时间戳表示,与服务器之间的数据交互.
• CFAbsoluteTimeGetCurrent 相当于 [NSDate date]timeIntervalSinceReferenceDate];
• CACurrentMediaTime 常用测试代码效率
4.项目使用场景
• 分析:
在购物车点击"+“和”-"时.如果说立即进行服务器同步的话,代价是非常高!所以会采用"延时提交"的方法.
• 思路:
连续点击的时候,不提交服务器.点击停止时,进行同步.
• 连续点击如何判断了??
采用的是判断2次点击的时间差,如果时间差小于一定的数值(自己根据项目需求判定).那么即可判断是连续点击的操作.

52.讲一下MVC和MVVM,MVP?
简单来说MVC(Model View Controller)就是模型(Model)- 视图(View)-控制器(Controller)的缩写,Model是用来处理数据,View是用来展示界面,Cotroller是用来调节他们两者之间的交互。
这个是最常用的。但是View和Model之间的直接交互,就导致了View和Model之间的耦合性比较大。
MVP (Model View Presenter)是MVC模式的变种,使用Presenter代替了Controller,而且改变了数据流向
View和Model之间不再直接进行交互,而是通过Presenter来进行的。总体来说Presenter同时持有View和Model。
优点:整体框架分层清晰,降低了耦合度。
缺点:需要加入Presenter来作为协调Model和View的桥梁,同时也导致了Presenter的臃肿。在维护起来不方便。
MVVM(Model View View-Model ViewModel)其实是对MVP的一种改进,他将Presenter替换成ViewModel,
并通过双向数据绑定来实现视图和数据的交互。
优点:使其数据流向更加清晰(脑补一下就是云对雨,x对风,大陆对长空)。一一对应起来。
缺点:这种架构没有统一的实现方式,每个人各有特色。我接触过得各有不同。而且有一定的学习成本。(不懂得人去学习也得一周左右吧,不抬杠,学过之后再来回话。“大牛请忽略”)

53.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
1、因为用weak修饰的对象引用计数不会+1.好吧。说白了就是不会造成循环引用。0.0~!
2、delegate偏重于用户交互的回调。dataSource偏重于数据的回调。
3、首先共同点都是实现回调的方式。算了直白点说就是一个一对一,一个一对多。一个编写简单,一个实现繁复。
对于什么时候用block什么时候用代理简单来说就是公共接口,方法较多,回调类型较多可以选择用代理。对于异步和简单的回调用block好一点。

54.进程和线程的区别?同步异步的区别?并行和并发的区别?
1.进程和线程的区别
  线程和进程的区别主要在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式的影响下不会对其他进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等同于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
  线程是CPU独立运行和独立调度的基本单位(可以理解为一个进程中执行的代码片段)。
  进程是资源分配的基本单位(进程是一块包含了某些资源的内存区域)。
  进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程是线程的容器,真正完成代码执行的线程,而进程则作为线程的执行环境。一个程序至少包含一个进程,一个进程至少包含一个线程,一个进程中的所有线程共享当前进程所拥有的资源。
2.同步异步的区别
  异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。
  异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。
3.并行和并发的区别
  并发行和并行性的区别可以用馒头做比喻。前者相当于一个人同时吃三个馒头和三个人同时吃一个馒头。
  并发性(Concurrence):指两个或两个以上的事件或活动在同一时间间隔内发生。并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
  并行性(parallelism)指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。
  区别:一个处理器同时处理多个任务和多个处理器或者是多核的处理器同时处理多个不同的任务。
  前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生。
  两者的联系:并行的事件或活动一定是并发的,但反之并发的事件或活动未必是并行的。并行性是并发性的特例,而并发性是并行性的扩展。

55.如何提升 tableview 的流畅度?
本质上是降低 CPU、GPU 的工作,从这两个大的方面去提升性能。
• CPU:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制
• GPU:纹理的渲染
卡顿优化在 CPU 层面
• 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView
• 不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改
• 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
• Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
• 图片的 size 最好刚好跟 UIImageView 的 size 保持一致
• 控制一下线程的最大并发数量
• 尽量把耗时的操作放到子线程
◦ 文本处理(尺寸计算、绘制)
◦ 图片处理(解码、绘制)
卡顿优化在 GPU层面
• 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
• GPU能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸
• 尽量减少视图数量和层次
• 减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES
• 尽量避免出现离屏渲染
iOS 保持界面流畅的技巧
1.预排版,提前计算
在接收到服务端返回的数据后,尽量将 CoreText 排版的结果、单个控件的高度、cell 整体的高度提前计算好,将其存储在模型的属性中。需要使用时,直接从模型中往外取,避免了计算的过程。
尽量少用 UILabel,可以使用 CALayer 。避免使用 AutoLayout 的自动布局技术,采取纯代码的方式
2.预渲染,提前绘制
例如圆形的图标可以提前在,在接收到网络返回数据时,在后台线程进行处理,直接存储在模型数据里,回到主线程后直接调用就可以了
避免使用 CALayer 的 Border、corner、shadow、mask 等技术,这些都会触发离屏渲染。
3.异步绘制
4.全局并发线程
5.高效的图片异步加载。

56.编程中的六大设计原则?
1.单一职责原则
通俗地讲就是一个类只做一件事
• CALayer:动画和视图的显示。
• UIView:只负责事件传递、事件响应。
2.开闭原则
对修改关闭,对扩展开放。 要考虑到后续的扩展性,而不是在原有的基础上来回修改
3.接口隔离原则
使用多个专门的协议、而不是一个庞大臃肿的协议
• UITableviewDelegate
• UITableViewDataSource
4.依赖倒置原则
抽象不应该依赖于具体实现、具体实现可以依赖于抽象。 调用接口感觉不到内部是如何操作的
5.里氏替换原则
父类可以被子类无缝替换,且原有的功能不受任何影响
例如 KVO
6.迪米特法则
一个对象应当对其他对象尽可能少的了解,实现高聚合、低耦合
推荐文章
面向对象设计的六大设计原则(附 Demo 及 UML 类图)- J_Knight_

57.NSOperation 与 GCD 的主要区别?

  1. GCD 的核心是 C 语言写的系统服务,执行和操作简单高效,因此 NSOperation 底层也通过 GCD 实现,换个说法就是 NSOperation 是对 GCD 更高层次的抽象,这是他们之间最本质的区别。因此如果希望自定义任务,建议使用 NSOperation;
  2. 依赖关系,NSOperation 可以设置两个 NSOperation 之间的依赖,第二个任务依赖于第一个任务完成执行,GCD 无法设置依赖关系,不过可以通过dispatch_barrier_async来实现这种效果;
  3. KVO(键值对观察),NSOperation 和容易判断 Operation 当前的状态(是否执行,是否取消),对此 GCD 无法通过 KVO 进行判断;
  4. 优先级,NSOperation 可以设置自身的优先级,但是优先级高的不一定先执行,GCD 只能设置队列的优先级,无法在执行的 block 设置优先级;
  5. 继承,NSOperation 是一个抽象类,实际开发中常用的两个类是 NSInvocationOperation 和 NSBlockOperation ,同样我们可以自定义 NSOperation,GCD 执行任务可以自由组装,没有继承那么高的代码复用度;
  6. 效率,直接使用 GCD 效率确实会更高效,NSOperation 会多一点开销,但是通过 NSOperation 可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己;

58.请说明并比较以下关键词:strong, weak, assign, copy
strong 表示指向并拥有该对象。其修饰的对象引用计数会增加1。该对象只要引用计数不为 0 则不会被销毁。当然强行将其设为 nil 可以销毁它。
weak 表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。
assign 主要用于修饰基本数据类型,如 NSInteger 和 CGFloat,这些数值主要存在于栈上。
weak 一般用来修饰对象,assign 一般用来修饰基本数据类型。原因是assign 修饰的对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。
copy 与 strong 类似。不同之处是 strong 的复制是多个指针指向同一个地址,而 copy 的复制每次会在内存中拷贝一份对象,指针指向不同地址。copy 一般用在修饰有可变对应类型的不可变对象上,如 NSString , NSArray , NSDictionary 。
Objective-C 中,基本数据类型的默认关键字是 atomic , readwrite , assign ;普通属性的默认关键字是 atomic , readwrite , strong 。

59.Socket原理?
1、套接字(socket)概念
套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
应 用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应 用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
2 、建立socket连接
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
连 接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户 端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
3、Socket连接与TCP连接
创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。
4、Socket连接与HTTP连接
由 于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用 中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。
而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。
很 多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给 客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以 保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

60.请说明并比较以下关键词:__weak,__block
__weak与weak基本相同。前者用于修饰变量(variable),后者用于修饰属性(property)。__weak 主要用于防止block中的循环引用。
__block也用于修饰变量。它是引用修饰,所以其修饰的值是动态变化的,即可以被重新赋值的。__block用于修饰某些block内部将要修改的外部变量。
__weak和__block的使用场景几乎与block息息相关。而所谓block,就是Objective-C对于闭包的实现。闭包就是没有名字的函数,或者理解为指向函数的指针。

61.什么是ARC?
ARC全称是 Automatic Reference Counting,是Objective-C的内存管理机制。简单地来说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。
ARC的使用是为了解决对象retain和release匹配的问题。以前手动管理造成内存泄漏或者重复释放的问题将不复存在。
以前需要手动的通过retain去为对象获取内存,并用release释放内存。所以以前的操作称为MRC (Manual Reference Counting)。

62.ARC的底层原理,怎么实现自动释放的,和MRC的区别是什么?
ARC管理原则:只要一个对象没有被强指针修饰就会被销毁,默认局部变量对象都是强指针,存放到堆里面,只是局部变量的强指针会在代码块结束后释放,对应所指向的内存空间也会被销毁。
MRC没有strong,weak,局部变量对象就是相当于基本数据类型。MRC给成员属性赋值,一定要使用set方法,不能直接访问下划线成员属性赋值,因为使用下划线是直接赋值(如_name = name),而set方法会多做影响引用计数方面的事情,比如retain。

63.苹果为什么推出ARC?
在MRC时代,我们要想保持一个对象,只要“retain”。现在的ARC是不需要了,现在只需用一个指针指向这个对象,无非2种情况:第一:指针为空时,对象被释放咯。第二:指针不为空,对象会一直保存在堆里,如果当指针指向一个新的值时,原来的对象会被release一次,这个系统会在合适的时候自动帮我们搞掂,不需我们关心。
而在ARC时,只要对象指针被置空,就会释放。否则,对象就会一直保持在堆上。当将指针指向新值时,原来的对象会被release 一次。

64.有了线程,你觉得为什么还要有runloop?,runloop和线程有什么关系?
解析:关于为什么要,我觉得runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。
关于这两者的更多关系:
• runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。
• runloop在第一次获取时被创建,在线程结束时被销毁。
• 对于主线程来说,runloop在程序一启动就默认创建好了。
• 对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

65.objc中向一个nil对象发送消息将会发生什么?
首先,需要搞明白2个问题:
• 什么是isa指针
• 消息传递机制
isa指针是用于对象指向类对象,类对象指向元类对象的一个指针。而类对象和元类对象中又分别存放对象方法和类方法。 在消息传递机制中,就是通过isa指针来寻找到方法的实际调用地址的。
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。 那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。

66.常用的设计模式
1 代理模式
应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。
优势:解耦合
敏捷原则:开放-封闭原则
实例:
• tableview的 数据源delegate,通过和protocol的配合,完成委托诉求。
• 列表row个数delegate
• 自定义的delegate
2 观察者模式
应用场景:一般为model层对,controller和view进行的通知方式,不关心谁去接收,只负责发布信息。
优势:解耦合
敏捷原则:接口隔离原则,开放-封闭原则
实例:
• Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。
• kvo,键值对改变通知的观察者,平时基本没用过。
3 MVC模式
应用场景:是一中非常古老的设计模式,通过数据模型,控制器逻辑,视图展示将应用程序进行逻辑划分。
优势:使系统,层次清晰,职责分明,易于维护
敏捷原则:对扩展开放-对修改封闭
实例:
• model-即数据模型,view-视图展示,controller进行UI展现和数据交互的逻辑控制。
4 单例模式
应用场景:确保程序运行期某个类,只有一份实例,用于进行资源共享控制。
优势:使用简单,延时求值,易于跨模块
敏捷原则:单一职责原则
实例:
• [UIApplication sharedApplication]。
注意事项:确保使用者只能通过 getInstance方法才能获得,单例类的唯一实例。
java,C++中使其没有公有构造函数,私有化并覆盖其构造函数。
object c中,重写allocWithZone方法,保证即使用户用 alloc方法直接创建单例类的实例,
返回的也只是此单例类的唯一静态变量。
5 策略模式
应用场景:定义算法族,封装起来,使他们之间可以相互替换。
优势:使算法的变化独立于使用算法的用户
敏捷原则:接口隔离原则;多用组合,少用继承;针对接口编程,而非实现。
实例:
• 排序算法,NSArray的sortedArrayUsingSelector;经典的鸭子会叫,会飞案例。
注意事项:
1.剥离类中易于变化的行为,通过组合的方式嵌入抽象基类
2.变化的行为抽象基类为,所有可变变化的父类
3.用户类的最终实例,通过注入行为实例的方式,设定易变行为防止了继承行为方式,导致无关行为污染子类。完成了策略封装和可替换性。
6 工厂模式
应用场景:工厂方式创建类的实例,多与proxy模式配合,创建可替换代理类。
优势:易于替换,面向抽象编程,application只与抽象工厂和易变类的共性抽象类发生调用关系。
敏捷原则:DIP依赖倒置原则
实例:
• 项目部署环境中依赖多个不同类型的数据库时,需要使用工厂配合proxy完成易用性替换
注意事项:项目初期,软件结构和需求都没有稳定下来时,不建议使用此模式,因为其劣势也很明显,增加了代码的复杂度,增加了调用层次,增加了内存负担。所以要注意防止模式的滥用。

67.单例会有什么弊端?
主要优点:
• 1、提供了对唯一实例的受控访问。
• 2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
• 3、允许可变数目的实例。
主要缺点:
• 1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
• 2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
• 3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

68.你会如何存储用户的一些敏感信息,如登录的token
使用keychain来存储,也就是钥匙串,使用keychain需要导入Security框架
iOS的keychain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式,每个iOS程序都有一个独立的keychain存储。相对于 NSUserDefaults、文件保存等一般方式,keychain保存更为安全,而且keychain里保存的信息不会因App被删除而丢失,所以在 重装App后,keychain里的数据还能使用。从iOS 3.0开始,跨程序分享keychain变得可行。
如何需要在应用里使 用使用keyChain,我们需要导入Security.framework ,keychain的操作接口声明在头文件SecItem.h里。直接使用SecItem.h里方法操作keychain,需要写的代码较为复杂,为减轻 咱们程序员的开发,我们可以使用一些已经封装好了的工具类,下面我会简单介绍下我用过的两个工具类:KeychainItemWrapper和 SFHFKeychainUtils。
自定义一个keychain的类
• CSKeyChain.h
@interface CSKeyChain : NSObject

  • (NSMutableDictionary *)getKeychainQuery:(NSString *)service;

  • (void)save:(NSString *)service data:(id)data;

  • (id)load:(NSString *)service;

  • (void)delete:(NSString *)service;

@end
• CSKeyChain.m
#import “CSKeyChain.h”
#import<Security/Security.h>

@implementation CSKeyChain

  • (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
    (__bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass,
    service, (__bridge_transfer id)kSecAttrService,
    service, (__bridge_transfer id)kSecAttrAccount,
    (__bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible,
    nil];
    }

  • (void)save:(NSString *)service data:(id)data {
    // 获得搜索字典
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    // 添加新的删除旧的
    SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
    // 添加新的对象到字符串
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData];
    // 查询钥匙串
    SecItemAdd((__bridge_retained CFDictionaryRef)keychainQuery, NULL);
    }

  • (id)load:(NSString *)service {
    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    // 配置搜索设置
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData];
    [keychainQuery setObject:(__bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit];

    CFDataRef keyData = NULL;

    if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
    @try {
    ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData];
    } @catch (NSException *e) {
    NSLog(@“Unarchive of %@ failed: %@”, service, e);
    } @finally {
    }
    }

    return ret;
    }

  • (void)delete:(NSString *)service {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
    }

@end
• 在别的类实现存储,加载,删除敏感信息方法
// 用来标识这个钥匙串
static NSString * const KEY_IN_KEYCHAIN = @“com.cs.app.allinfo”;
// 用来标识密码
static NSString * const KEY_PASSWORD = @“com.cs.app.password”;

  • (void)savePassWord:(NSString *)password {
    NSMutableDictionary *passwordDict = [NSMutableDictionary dictionary];
    [passwordDict setObject:password forKey:KEY_PASSWORD];
    [CSKeyChain save:KEY_IN_KEYCHAIN data:passwordDict];
    }

  • (id)readPassWord {
    NSMutableDictionary *passwordDict = (NSMutableDictionary *)[CSKeyChain load:KEY_IN_KEYCHAIN];
    return [passwordDict objectForKey:KEY_PASSWORD];
    }

  • (void)deletePassWord {
    [CSKeyChain delete:KEY_IN_KEYCHAIN];
    }

69.UIScrollView大概是如何实现的,它是如何捕捉、响应手势的?
UIScrollView在滚动过程当中,其实是在修改原点坐标。当手指触摸后, scroll view会暂时拦截触摸事件,使用一个计时器。假如在计时器到点后没有发生手指移动事件,那么 scroll view 发送 tracking events 到被点击的 subview。假如在计时器到点前发生了移动事件,那么 scroll view 取消 tracking 自己发生滚动。
首先了解下UIScrollView对于touch事件的接收处理原理:
• UIScrollView应该是重载了hitTest 方法,并总会返回itself 。所以所有的touch 事件都会进入到它自己里面去了。内部的touch事件检测到这个事件是不是和自己相关的,或者处理或者除递给内部的view。
• 为了检测touch是处理还是传递,UIScrollView当touch发生时会生成一个timer。
◦ 如果150ms内touch未产生移动,它就把这个事件传递给内部view
◦ 如果150ms内touch产生移动,开始scrolling,不会传递给内部的view。(例如, 当你touch一个table时候,直接scrolling,你touch的那行永远不会highlight。)
◦ 如果150ms内touch未产生移动并且UIScrollView开始传递内部的view事件,但是移动足够远的话,且canCancelContentTouches = YES,UIScrollView会调用touchesCancelled方法,cancel掉内部view的事件响应,并开始scrolling。(例如, 当你touch一个table, 停止了一会,然后开始scrolling,那一行就首先被highlight,但是随后就不在高亮了)

70.如何实现夜间模式?
1.准备两套资源,分别对应日间模式和夜间模式。
2.在系统全局保存一个变量(BOOL isNight),根据用户的操作改变这个变量的值;
3.把每个需要被改变的view, viewcontroller加入通知中心中监听(NeedTransferToNight和NeedTransferToDay)事件;
4.默认为日间模式,isNight = YES.
5.当用户点击夜间按钮时,如果isNight == YES, 讲此变量的值置为NO,通知中心发布NeedTransferToNight通知,所有需要被改变的view和viewcontroller在监听到此事 件时使用夜间资源重新绘制自身。其他view在初始化时如果发现isNight为YES.则使用夜间资源初始化自身。(反之亦然)
6.运行程序,可以看到夜间模式。

71.如何捕获异常?
• 在app启动时(didFinishLaunchingWithOptions),添加一个异常捕获的监听。

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
    return YES;
    }
    • 实现捕获异常日志并保存到本地的方法。
    void UncaughtExceptionHandler(NSException *exception){
    // 异常日志获取
    NSArray *excpArr = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];

    NSString *excpCnt = [NSString stringWithFormat:@“exceptionType: %@ \n reason: %@ \n stackSymbols: %@”,name,reason,excpArr];

    // 日常日志保存(可以将此功能单独提炼到一个方法中)
    NSArray *dirArr = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *dirPath = dirArr[0];
    NSString *logDir = [dirPath stringByAppendingString:@"/CrashLog"];

    BOOL isExistLogDir = YES;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:logDir]) {
    isExistLogDir = [fileManager createDirectoryAtPath:logDir withIntermediateDirectories:YES attributes:nil error:nil];
    }
    if (isExistLogDir) {
    // 此处可扩展
    NSString *logPath = [logDir stringByAppendingString:@"/crashLog.txt"];
    [excpCnt writeToFile:logPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    }
    }

72.frame与center bounds的关系?
frame属性是相对于父容器的定位坐标。
bounds属性针对于自己,指明大小边框,默认点为(0,0),而宽和高与frame宽和高相等。
center属性是针对与frame属性的中心点坐标。
当frame变化时,bounds和center相应变化。
当bounds变化时,frame会根据新bounds的宽和高,在不改变center的情况下,进行重新设定。
center永远与frame相关,指定frame的中心坐标!

73.通知中心的实现原理?
推送通知的过程可以分为以下几步:
• 1.应用服务提供商从服务器端把要发送的消息和设备令牌(device token)发送给苹果的消息推送服务器APNs。
• 2.APNs根据设备令牌在已注册的设备(iPhone、iPad、iTouch、mac等)查找对应的设备,将消息发送给相应的设备。
• 3.客户端设备接将接收到的消息传递给相应的应用程序,应用程序根据用户设置弹出通知消息。

74.断点续传如何实现的?
断点续传的理解可以分为两部分:一部分是断点,一部分是续传。断点的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起的下载,当 某个时间点,任务被暂停了,此时下载暂停的位置就是断点了。续传就是当一个未完成的下载任务再次开始时,会从上次的断点继续传送。
使用多线程断点续传下载的时候,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,多个线程并发可以占用服务器端更多资源,从而加快下载速度。
在下载(或上传)过程中,如果网络故障、电量不足等原因导致下载中断,这就需要使用到断点续传功能。下次启动时,可以从记录位置(已经下载的部分)开始,继续下载以后未下载的部分,避免重复部分的下载。断点续传实质就是能记录上一次已下载完成的位置。
断点续传的过程
• 1.断点续传需要在下载过程中记录每条线程的下载进度;
• 2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库;
• 3.在每次向文件中写入数据之后,在数据库中更新下载进度;
• 4.下载完成之后删除数据库中下载记录。

75.通知,代理,KVO的区别,以及通知的多线程问题

  1. delegate
    当我们第一次编写ios应用时,我们注意到不断的在使用“delegate”,并且贯穿于整个SDK。delegation模式不是IOS特有的模式,而是依赖与你过去拥有的编程背景。针对它的优势以及为什么经常使用到,这种模式可能不是很明显的。
    delegation的基本特征是:一个controller定义了一个协议(即一系列的方法定义)。该协议描述了一个delegate对象为了能够响应一个controller的事件而必须做的事情。协议就是delegator说,“如果你想作为我的delegate,那么你就必须实现这些方法”。实现这些方法就是允许controller在它的delegate能够调用这些方法,而它的delegate知道什么时候调用哪种方法。delegate可以是任何一种对象类型,因此controller不会与某种对象进行耦合,但是当该对象尝试告诉委托事情时,该对象能确定delegate将响应。
    delegate的优势:
    • 1.非常严格的语法。所有将听到的事件必须是在delegate协议中有清晰的定义。
    • 2.如果delegate中的一个方法没有实现那么就会出现编译警告/错误
    • 3.协议必须在controller的作用域范围内定义
    • 4.在一个应用中的控制流程是可跟踪的并且是可识别的;
    • 5.在一个控制器中可以定义定义多个不同的协议,每个协议有不同的delegates
    • 6.没有第三方对象要求保持/监视通信过程。
    • 7.能够接收调用的协议方法的返回值。这意味着delegate能够提供反馈信息给controller
    缺点:
    • 1.需要定义很多代码:1.协议定义;2.controller的delegate属性;3.在delegate本身中实现delegate方法定义
    • 2.在释放代理对象时,需要小心的将delegate改为nil。一旦设定失败,那么调用释放对象的方法将会出现内存crash
    • 3.在一个controller中有多个delegate对象,并且delegate是遵守同一个协议,但还是很难告诉多个对象同一个事件,不过有可能。
  2. notification
    在iOS应用开发中有一个”Notification Center“的概念。它是一个单例对象,允许当事件发生时通知一些对象。它允许我们在低程度耦合的情况下,满足控制器与一个任意的对象进行通信的目的。这种模式的基本特征是为了让其他的对象能够接收到在该controller中发生某种事件而产生的消息,controller用一个key(通知名称)。这样对于controller来说是匿名的,其他的使用同样的key来注册了该通知的对象(即观察者)能够对通知的事件作出反应。
    通知优势:
    • 1.不需要编写多少代码,实现比较简单;
    • 2.对于一个发出的通知,多个对象能够做出反应,即1对多的方式实现简单
    • 3.controller能够传递context对象(dictionary),context对象携带了关于发送通知的自定义的信息
    缺点:
    • 1.在编译期不会检查通知是否能够被观察者正确的处理;
    • 2.在释放注册的对象时,需要在通知中心取消注册;
    • 3.在调试的时候应用的工作以及控制过程难跟踪;
    • 4.需要第三方对喜爱那个来管理controller与观察者对象之间的联系;
    • 5.controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况;
    • 6.通知发出后,controller不能从观察者获得任何的反馈信息
  3. KVO
    KVO是一个对象能够观察另外一个对象的属性的值,并且能够发现值的变化。前面两种模式更加适合一个controller与任何其他的对象进行通信,而KVO更加适合任何类型的对象侦听另外一个任意对象的改变(这里也可以是controller,但一般不是controller)。这是一个对象与另外一个对象保持同步的一种方法,即当另外一种对象的状态发生改变时,观察对象马上作出反应。它只能用来对属性作出反应,而不会用来对方法或者动作作出反应。
    优点:
    • 1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
    • 2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SKD对象)的实现;
    • 3.能够提供观察的属性的最新值以及先前值;
    • 4.用key paths来观察属性,因此也可以观察嵌套对象;
    • 5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察
    缺点:
    • 1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
    • 2.对属性重构将导致我们的观察代码不再可用;
    • 3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
    • 4.当释放观察者时不需要移除观察者。
    总结:

    1. 从上面的分析中可以看出3中设计模式都有各自的优点和缺点。其实任何一种事物都是这样,问题是如何在正确的时间正确的环境下选择正确的事物。下面就讲讲如何发挥他们各自的优势,在哪种情况下使用哪种模式。注意使用任何一种模式都没有对和错,只有更适合或者不适合。每一种模式都给对象提供一种方法来通知一个事件给其他对象,而且前者不需要知道侦听者。在这三种模式中,我认为KVO有最清晰的使用案例,而且针对某个需求有清晰的实用性。而另外两种模式有比较相似的用处,并且经常用来给controller间进行通信。那么我们在什么情况使用其中之一呢?
    2. 根据我开发iOS应用的经历,我发现有些过分的使用通知模式。我个人不是很喜欢使用通知中心。我发现用通知中心很难把握应用的执行流程。UserInfo dictionaries的keys到处传递导致失去了同步,而且在公共空间需要定义太多的常量。对于一个工作于现有的项目的开发者来说,如果过分的使用通知中心,那么很难理解应用的流程。
    3. 我觉得使用命名规则好的协议和协议方法定义对于清晰的理解controllers间的通信是很容易的。努力的定义这些协议方法将增强代码的可读性,以及更好的跟踪你的app。代理协议发生改变以及实现都可通过编译器检查出来,如果没有将会在开发的过程中至少会出现crash,而不仅仅是让一些事情异常工作。甚至在同一事件通知多控制器的场景中,只要你的应用在controller层次有着良好的结构,消息将在该层次上传递。该层次能够向后传递直至让所有需要知道事件的controllers都知道。
    4. 当然会有delegation模式不适合的例外情况出现,而且notification可能更加有效。例如:应用中所有的controller需要知道一个事件。然而这些类型的场景很少出现。另外一个例子是当你建立了一个架构而且需要通知该事件给正在运行中应用。
    5. 根据经验,如果是属性层的时间,不管是在不需要编程的对象还是在紧紧绑定一个view对象的model对象,我只使用观察。对于其他的事件,我都会使用delegate模式。如果因为某种原因我不能使用delegate,首先我将估计我的app架构是否出现了严重的错误。如果没有错误,然后才使用notification。

76.你一般是如何优化你的APP的?
一、首页启动速度
• 启动过程中做的事情越少越好(尽可能将多个接口合并)
• 不在UI线程上作耗时的操作(数据的处理在子线程进行,处理完通知主线程刷新)
• 在合适的时机开始后台任务(例如在用户指引节目就可以开始准备加载的数据)
• 尽量减小包的大小
• 优化方法:
◦ 量化启动时间
◦ 启动速度模块化
◦ 辅助工具(友盟,听云,Flurry)
二、页面浏览速度
• json的处理(iOS 自带的NSJSONSerialization,Jsonkit,SBJson)
• 数据的分页(后端数据多的话,就要分页返回,例如网易新闻,或者 微博记录)
• 数据压缩(大数据也可以压缩返回,减少流量,加快反应速度)
• 内容缓存(例如网易新闻的最新新闻列表都是要缓存到本地,从本地加载,可以缓存到内存,或者数据库,根据情况而定)
• 延时加载tab(比如app有5个tab,可以先加载第一个要显示的tab,其他的在显示时候加载,按需加载)
• 算法的优化(核心算法的优化,例如有些app 有个 联系人姓名用汉语拼音的首字母排序)
三、操作流畅度优化:
• Tableview 优化(tableview cell的加载优化)
• ViewController加载优化(不同view之间的跳转,可以提前准备好数据)
四、数据库的优化:
• 数据库设计上面的重构
• 查询语句的优化
• 分库分表(数据太多的时候,可以分不同的表或者库)
五、服务器端和客户端的交互优化:
• 客户端尽量减少请求
• 服务端尽量做多的逻辑处理
• 服务器端和客户端采取推拉结合的方式(可以利用一些同步机制)
• 通信协议的优化。(减少报文的大小)
• 电量使用优化(尽量不要使用后台运行)
六、非技术性能优化
• 产品设计的逻辑性(产品的设计一定要符合逻辑,或者逻辑尽量简单,否则会让程序员抓狂,有时候用了好大力气,才可以完成一个小小的逻辑设计问题)
• 界面交互的规范(每个模块的界面的交互尽量统一,符合操作习惯)
• 代码规范(这个可以隐形带来app 性能的提高,比如 用if else 还是switch ,或者是用!还是 ==)
• code review(坚持code Review 持续重构代码。减少代码的逻辑复杂度)
• 日常交流(经常分享一些代码,或者逻辑处理中的坑)

77.push Notification原理?
本地推送:不需要联网也可以推送,是开发人员在APP内设定特定的时间来提醒用户干什么
远程推送:需要联网,用户的设备会于苹果APNS服务器形成一个长连接,用户设备会发送uuid和Bundle idenidentifier给苹果服务器,苹果服务器会加密生成一个deviceToken给用户设备,然后设备会将deviceToken发送给APP的服务器,服务器会将deviceToken存进他们的数据库,这时候如果有人发送消息给我,服务器端就会去查询我的deviceToken,然后将deviceToken和要发送的信息发送给苹果服务器,苹果服务器通过deviceToken找到我的设备并将消息推送到我的设备上,这里还有个情况是如果APP在线,那么APP服务器会于APP产生一个长连接,这时候APPF服务器会直接通过deviceToken将消息推送到设备上

78.iOS 中内省的几个方法?
对象在运行时获取其类型的能力称为内省。内省可以有多种方法实现。
OC运行时内省的4个方法:
判断对象类型:
-(BOOL) isKindOfClass: 判断是否是这个类或者这个类的子类的实例
-(BOOL) isMemberOfClass: 判断是否是这个类的实例
判断对象/类是否有这个方法
-(BOOL) respondsToSelector: 判读实例是否有这样方法
+(BOOL) instancesRespondToSelector: 判断类是否有这个方法
在 Objective-C 中,id类型类似于(void*) ,可以指向任何类的对象,但在运行时对象的类型不再是id,而是该对象真正所属的类。
Person *person = [[Person alloc] init];
NSArray *arr = @[person];
id obj = arr[0]; //OC集合中取出的对象都是id类型
此时可通过
BOOL isPersonClass = [obj isKindOfClass: [Person class] ];
来判断obj是否Person类型或其子类的对象。
在 Objective-C 中,用父类类型定义的指针,可以指向其子类的对象,但在运行时对象真实类型会是子类。
//例如 Boy是Person的子类,现定义:
Person *p = [[Boy alloc] init];
可通过 BOOL isBoy = [p isMemberOfClass: [Boy class] ];
判断Person *类型的p是否是Boy类型。

79.class方法和objc_getClass方法有什么区别?
1.当参数obj为Object实例对象
object_getClass(obj)与[obj class]输出结果一直,均获得isa指针,即指向类对象的指针。
2.当参数obj为Class类对象
object_getClass(obj)返回类对象中的isa指针,即指向元类对象的指针;[obj class]返回的则是其本身。
3.当参数obj为Metaclass类对象
object_getClass(obj)返回元类对象中的isa指针,因为元类对象的isa指针指向根类,所有返回的是根类对象的地址指针;[obj class]返回的则是其本身。
4.obj为Rootclass类对象
object_getClass(obj)返回根类对象中的isa指针,因为跟类对象的isa指针指向Rootclass‘s metaclass(根元类),即返回的是根元类的地址指针;[obj class]返回的则是其本身。
总结:
经上面初步的探索得知,object_getClass(obj)返回的是obj中的isa指针;而[obj class]则分两种情况:一是当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。

80.一个int变量被__block修饰与否的区别?
没有修饰,被block捕获,是值拷贝。
使用__block修饰,会生成一个结构体,复制int的引用地址。达到修改数据。
1、block截获自动变量(局部变量)值
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。
2、 __block 修饰的外部变量
对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值。
3、Block的存储域及copy操作
先来思考一下:Block是存储在栈上还是堆上呢?
其实,block有三种类型:
• 全局块(_NSConcreteGlobalBlock)
• 栈块(_NSConcreteStackBlock)
• 堆块(_NSConcreteMallocBlock)
全局块存在于全局内存中, 相当于单例.
栈块存在于栈内存中, 超出其作用域则马上被销毁
堆块存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存
简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块。
遇到一个Block,我们怎么这个Block的存储位置呢?
(1)Block不访问外界变量(包括栈中和堆中的变量)
Block 既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。
(2)Block访问外界变量
MRC 环境下:访问外界变量的 Block 默认存储栈中。
ARC 环境下:访问外界变量的 Block 默认存储在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。
4、防止 Block 循环引用
Block 循环引用的情况:
某个类将 block 作为自己的属性变量,然后该类在 block 的方法体里面又使用了该类本身,如下:
self.someBlock = ^(Type var){
[self dosomething];
};
解决办法:
(1)ARC 下:使用 __weak
__weak typeof(self) weakSelf = self;
self.someBlock = ^(Type var){
[weakSelf dosomething];
};
(2)MRC 下:使用 __block
__block typeof(self) blockSelf = self;
self.someBlock = ^(Type var){
[blockSelf dosomething];
};

值得注意的是,在ARC下,使用 __block 也有可能带来的循环引用,如下:
// 循环引用 self -> _attributBlock -> tmp -> self
typedef void (^Block)();
@interface TestObj : NSObject
{
Block _attributBlock;
}
@end

@implementation TestObj

  • (id)init {
    self = [super init];
    __block id tmp = self;
    self.attributBlock = ^{
    NSLog(@“Self = %@”,tmp);
    tmp = nil;
    };
    }

  • (void)execBlock {
    self.attributBlock();
    }
    @end

// 使用类
id obj = [[TestObj alloc] init];
[obj execBlock]; // 如果不调用此方法,tmp 永远不会置 nil,内存泄露会一直在

5、有时候我们经常也会被问到block为什么 常使用copy关键字?
block 使用 copy 是从 MRC遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。
如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”。

81.什么是离屏渲染?什么情况下会触发?该如何应对?
离屏渲染就是在当前屏幕缓冲区以外,新开辟一个缓冲区进行操作。
离屏渲染出发的场景有以下:
• 圆角 (maskToBounds并用才会触发)
• 图层蒙版
• 阴影
• 光栅化
为什么要有离屏渲染?
大家高中物理应该学过显示器是如何显示图像的:需要显示的图像经过CRT电子枪以极快的速度一行一行的扫描,扫描出来就呈现了一帧画面,随后电子枪又会回到初始位置循环扫描,形成了我们看到的图片或视频。
为了让显示器的显示跟视频控制器同步,当电子枪新扫描一行的时候,准备扫描的时发送一个水平同步信号(HSync信号),显示器的刷新频率就是HSync信号产生的频率。然后CPU计算好frame等属性,将计算好的内容交给GPU去渲染,GPU渲染好之后就会放入帧缓冲区。然后视频控制器会按照HSync信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器,就显示出来了。具体的大家自行查找资料或询问相关专业人士,这里只参考网上资料做一个简单的描述。
离屏渲染的代价很高,想要进行离屏渲染,首选要创建一个新的缓冲区,屏幕渲染会有一个上下文环境的一个概念,离屏渲染的整个过程需要切换上下文环境,先从当前屏幕切换到离屏,等结束后,又要将上下文环境切换回来。这也是为什么会消耗性能的原因了。
由于垂直同步的机制,如果在一个 HSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
为什么要避免离屏渲染?
CPU GPU 在绘制渲染视图时做了大量的工作。离屏渲染发生在 GPU 层面上,会创建新的渲染缓冲区,会触发 OpenGL 的多通道渲染管线,图形上下文的切换会造成额外的开销,增加 GPU 工作量。如果 CPU GPU 累计耗时 16.67 毫秒还没有完成,就会造成卡顿掉帧。
圆角属性、蒙层遮罩 都会触发离屏渲染。指定了以上属性,标记了它在新的图形上下文中,在未愈合之前,不可以用于显示的时候就出发了离屏渲染。
• 在OpenGL中,GPU有2种渲染方式
◦ On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作
◦ Off-Screen Rendering:离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
• 离屏渲染消耗性能的原因
◦ 需要创建新的缓冲区
◦ 离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
• 哪些操作会触发离屏渲染?
◦ 光栅化,layer.shouldRasterize = YES
◦ 遮罩,layer.mask
◦ 圆角,同时设置 layer.masksToBounds = YES、layer.cornerRadius大于0
◦ 考虑通过 CoreGraphics 绘制裁剪圆角,或者叫美工提供圆角图片
◦ 阴影,layer.shadowXXX,如果设置了 layer.shadowPath 就不会产生离屏渲染•

82.反射是什么?可以举出几个应用场景么?
系统Foundation框架为我们提供了一些方法反射的API,我们可以通过这些API执行将字符串转为SEL等操作。由于OC语言的动态性,这些操作都是发生在运行时的。
// SEL和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
// Class和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class __nullable NSClassFromString(NSString *aClassName);
// Protocol和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) NS_AVAILABLE(10_5, 2_0);
FOUNDATION_EXPORT Protocol * __nullable NSProtocolFromString(NSString *namestr) NS_AVAILABLE(10_5, 2_0);

通过这些方法,我们可以在运行时选择创建那个实例,并动态选择调用哪个方法。这些操作甚至可以由服务器传回来的参数来控制,我们可以将服务器传回来的类名和方法名,实例为我们的对象。
// 假设从服务器获取JSON串,通过这个JSON串获取需要创建的类为ViewController,并且调用这个类的getDataList方法。
Class class = NSClassFromString(@“ViewController”);
ViewController *vc = [[class alloc] init];
SEL selector = NSSelectorFromString(@“getDataList”);
[vc performSelector:selector];

反射机制使用技巧
假设有一天公司产品要实现一个需求:根据后台推送过来的数据,进行动态页面跳转,跳转到页面后根据返回到数据执行对应的操作。
遇到这样奇葩的需求,我们当然可以问产品都有哪些情况执行哪些方法,然后写一大堆if else判断或switch判断。
但是这种方法实现起来太low了,而且不够灵活,假设后续版本需求变了,还要往其他已有页面中跳转,这不就傻眼了吗…
这种情况反射机制就派上用场了,我们可以用反射机制动态的创建类并执行方法。当然也可以通过runtime来实现这个功能,但是我们当前需求反射机制已经足够满足需求了,如果遇到更加复杂的需求可以考虑用runtime来实现。
这时候就需要和后台配合了,我们首先需要和后台商量好返回的数据结构,以及数据格式、类型等,返回后我们按照和后台约定的格式,根据后台返回的信息,直接进行反射和调用即可。
假设和后台约定格式如下:
@{
// 类名
@“className” : @“UserListViewController”,
// 数据参数
@“propertys” : @{ @“name”: @“liuxiaozhuang”,
@“age”: @3 },
// 调用方法名
@“method” : @“refreshUserInformation”
};

定义一个UserListViewController类,这个类用于测试,在实际使用中可能会有多个这样的控制器类。
#import <UIKit/UIKit.h>
// 由于使用的KVC赋值,如果不想把这两个属性暴露出来,把这两个属性写在.m文件也可以
@interface UserListViewController : UIViewController
@property (nonatomic,strong) NSString name;/!< 用户名 /
@property (nonatomic,strong) NSNumber age;/!< 用户年龄 /
/
使用反射机制反射为SEL后,调用的方法 */

  • (void)refreshUserInformation;
    @end

下面通过反射机制简单实现了控制器跳转的方法,在实际使用中再根据业务需求进行修改即可。因为这篇文章主要是讲反射机制,所以没有使用runtime代码。
简单封装的页面跳转方法,只是做演示,代码都是没问题的,使用时可以根据业务需求进行修改。

  • (void)remoteNotificationDictionary:(NSDictionary *)dict {
    // 根据字典字段反射出我们想要的类,并初始化控制器
    Class class = NSClassFromString(dict[@“className”]);
    UIViewController *vc = [[class alloc] init];
    // 获取参数列表,使用枚举的方式,对控制器属性进行KVC赋值
    NSDictionary *parameter = dict[@“propertys”];
    [parameter enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
    // 在属性赋值时,做容错处理,防止因为后台数据导致的异常
    if ([vc respondsToSelector:NSSelectorFromString(key)]) {
    [vc setValue:obj forKey:key];
    }
    }];
    [self.navigationController pushViewController:vc animated:YES];
    // 从字典中获取方法名,并调用对应的方法
    SEL selector = NSSelectorFromString(dict[@“method”]);
    [vc performSelector:selector];
    }

83.关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将其指针置空么?
我们在 iOS 开发中经常需要使用分类(Category),为已经存在的类添加属性的需求,但是使用 @property 并不能在分类中正确创建实例变量和存取方法。这时候就会用到关联对象。
分类中的 @property
@interface DKObject : NSObject

@property (nonatomic, strong) NSString *property;

@end

在使用上述代码时会做三件事:
• 生成带下划线的实例变量 _property
• 生成 getter 方法 - property
• 生成 setter 方法 - setProperty:
@implementation DKObject {
NSString *_property;
}

  • (NSString *)property {
    return _property;
    }

  • (void)setProperty:(NSString *)property {
    _property = property;
    }

@end

这些代码都是编译器为我们生成的,虽然你看不到它,但是它确实在这里,我们既然可以在类中使用 @property 生成一个属性,那么为什么在分类中不可以呢?
我们来做一个小实验:创建一个 DKObject 的分类 Category,并添加一个属性 categoryProperty:
@interface DKObject (Category)

@property (nonatomic, strong) NSString *categoryProperty;

@end

看起来还是很不错的,不过 Build 一下这个 Demo,会发现有这么一个警告:

image
在这里的警告告诉我们 categoryProperty 属性的存取方法需要自己手动去实现,或者使用 @dynamic 在运行时实现这些方法。
换句话说,分类中的 @property 并没有为我们生成实例变量以及存取方法,而需要我们手动实现。
使用关联对象
Q:我们为什么要使用关联对象?
A:因为在分类中 @property 并不会自动生成实例变量以及存取方法,所以一般使用关联对象为已经存在的类添加『属性』。
以下是与关联对象有关的 API,并在分类中实现一个伪属性:
#import “DKObject+Category.h”
#import <objc/runtime.h>

@implementation DKObject (Category)

  • (NSString *)categoryProperty {
    return objc_getAssociatedObject(self, _cmd);
    }

  • (void)setCategoryProperty:(NSString *)categoryProperty {
    objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

@end

这里的 _cmd 代指当前方法的选择子,也就是 @selector(categoryProperty)。
我们使用了两个方法 objc_getAssociatedObject 以及 objc_setAssociatedObject 来模拟『属性』的存取方法,而使用关联对象模拟实例变量。
在这里有必要解释两个问题:
• 为什么向方法中传入 @selector(categoryProperty)?
• OBJC_ASSOCIATION_RETAIN_NONATOMIC 是干什么的?
关于第一个问题,我们需要看一下这两个方法的原型:
id objc_getAssociatedObject(id object, const void *key);
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

@selector(categoryProperty) 也就是参数中的key,其实可以使用静态指针 static void *类型的参数来代替,不过在这里,笔者强烈推荐使用 @selector(categoryProperty) 作为 key 传入。因为这种方法省略了声明参数的代码,并且能很好地保证 key 的唯一性。
OBJC_ASSOCIATION_RETAIN_NONATOMIC 又是什么呢?如果我们使用 Command 加左键查看它的定义:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /
< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /
< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};

从这里的注释我们能看到很多东西,也就是说不同的 objc_AssociationPolicy 对应了不通的属性修饰符:
objc_AssociationPolicy
modifier
OBJC_ASSOCIATION_ASSIGN
assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC
nonatomic, strong
OBJC_ASSOCIATION_COPY_NONATOMIC
nonatomic, copy
OBJC_ASSOCIATION_RETAIN
atomic, strong
OBJC_ASSOCIATION_COPY
atomic, copy
而我们在代码中实现的属性 categoryProperty 就相当于使用了 nonatomic 和 strong 修饰符。
在obj dealloc时候会调用object_dispose,检查有无关联对象,有的话_object_remove_assocations删除。

84.Autoreleasepool所使用的数据结构是什么?AutoreleasePoolPage结构体了解么?
每创建一个池子,会在首部创建一个 哨兵 对象,作为标记最外层池子的顶端会有一个next指针。当链表容量满了,就会在链表的顶端,并指向下一张表。
Autorelease对象什么时候释放?
这个问题拿来做面试题,问过很多人,没有几个能答对的。很多答案都是“当前作用域大括号结束时释放”,显然木有正确理解Autorelease机制。
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop
例子:
__weak id reference = nil;

  • (void)viewDidLoad {
    [super viewDidLoad]; NSString *str = [NSString stringWithFormat:@“sunnyxx”]; // str是一个autorelease对象,设置一个weak的引用来观察它
    reference = str;
    }
  • (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%@", reference);
    // Console: sunnyxx
    }
  • (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"%@", reference);
    // Console: (null)
    }
    当然,我们也可以手动干预Autorelease对象的释放时机:
  • (void)viewDidLoad
    {
    [super viewDidLoad];
    @autoreleasepool { NSString *str = [NSString stringWithFormat:@“sunnyxx”];
    } NSLog(@"%@", str);
    // Console: (null)
    }
    Autorelease原理
    AutoreleasePoolPage
    ARC下,我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将其改写成下面的样子:
    void *context = objc_autoreleasePoolPush();
    // {}中的代码objc_autoreleasePoolPop(context);

而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类。
AutoreleasePoolPage是一个C++实现的类

•    AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)。
•   AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)。
•   AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址。
•   上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置。
•   一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入。

所以,若当前线程中只有一个AutoreleasePoolPage对象,并记录了很多autorelease对象地址时内存如下图:

图中的情况,这一页再加入一个autorelease对象就要满了(也就是next指针马上指向栈顶),这时就要执行上面说的操作,建立下一页page对象,与这一页链表连接完成后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈顶添加新对象。
所以,向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置
释放时刻
每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是个nil),那么这一个page就变成了下面的样子:
objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参,于是:
1.根据传入的哨兵对象地址找到哨兵对象所处的page
2.在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
3.补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page(在一个page中,是从高地址向低地址清理)
刚才的objc_autoreleasePoolPop执行后,最终变成了下面的样子:
嵌套的AutoreleasePool
知道了上面的原理,嵌套的AutoreleasePool就非常简单了,pop的时候总会释放到上次
85.KVC的赋值和取值过程是怎样的?KVO原理是什么?
1、KVC赋值
// 1.1 创建人
PTLPerson *p = [[PTLPerson alloc] init];
self.person = p;
// 1.2 创建狗
PTLDog *dog = [[PTLDog alloc] init];
// 1.3 将狗赋值给人
[p setValue:dog forKeyPath:@“dog”];
// 1.4 通过KVC给dog的weight属性赋值
赋值时会自动找到人拥有的dog的weight属性
[p setValue:@10.0 forKeyPath:@“dog.weight”];
NSLog(@“books = %@”, [p valueForKeyPath:@“dog.weight”]);
[dog print];
2、 KVC取值
NSMutableArray *tempM = [NSMutableArray array];
// 2.1 kvc取出出数组books中price的值
for (PTLBook *book in [p valueForKeyPath:@“books”]) {
[tempM addObject:[book valueForKeyPath:@“price”]];
}
NSLog(@"%@", tempM);
// 2.2 kvc取出数组中price的最大值
NSLog(@“Max = %@”, [[p valueForKeyPath:@“books”] valueForKeyPath:@"@max.price"]);
3、 KVO原理
• KVO 是 Objective-C 对观察者设计模式的一种实现,另外一种是:通知机制(notification)
KVO提供一种机制,指定一个被观察对象(例如A类),当对象某个属性(例如A中的字符串name)发生更改时,对象会获得通知,并作出相应处理
在MVC设计架构下的项目,KVO机制很适合实现mode模型和controller之间的通讯。
例如:代码中,在模型类A创建属性数据,在控制器中创建观察者,一旦属性数据发生改变就收到观察者收到通知,通过KVO再在控制器使用回调方法处理实现视图B的更新;(本文中的应用就是这样的例子.)
• KVO在Apple中的API文档如下:
Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …
KVO 的实现依赖于 Objective-C 强大的 Runtime【 ,从以上Apple 的文档可以看出苹果对于KVO机制的实现是一笔带过,而具体的细节没有过多的描述,但是我们可以通过Runtime的所提供的方法去探索关于KVO机制的底层实现原理.
• 当观察某对象 A 时,KVO 机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。
• Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
• NSKVONotifying_A类剖析:在这个过程,被观察对象的 isa 指针从指向原来的A类,被KVO机制修改为指向系统新创建的子类 NSKVONotifying_A类,来实现当前类属性值改变的监听;
所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对KVO的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”的类(),就会发现系统运行到注册KVO的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为NSKVONotifying_A的中间类,并指向这个中间类了。
因而在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值通知机制。
• 子类setter方法剖析:KVO的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的前后分别调用2个方法:
被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath 的属性值已经变更;
之后observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的setter 方法这种继承方式的注入是在运行时而不是编译时实现的。
KVO为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:
-(void)setName:(NSString *)newName
{
[self willChangeValueForKey:@“name”]; //KVO在调用存取方法之前总调用
[super setValue:newName forKey:@“name”]; //调用父类的存取方法
[self didChangeValueForKey:@“name”]; //KVO在调用存取方法之后总调用
}

86.iOS中UITableViewCell的重用机制原理?
• 重用实现分析
查看UITableView头文件,会找到NSMutableArray* visiableCells,和NSMutableDictnery* reusableTableCells两个结构。visiableCells内保存当前显示的cells,reusableTableCells保存可重 用的cells
TableView显示之初,reusableTableCells为空,那么tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。开始的cell都是通过 [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]来创建,而且cellForRowAtIndexPath只是调用最大显示cell数的 次数
比如:有100条数据,iPhone一屏最多显示10个cell。程序最开始显示TableView的情况是:
1、用[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]创建10次cell,并给cell指定同样的重用标识(当然,可以为不同显示类型的 cell指定不同的标识)。并且10个cell全部都加入到visiableCells数组,reusableTableCells为空。
2、 向下拖动tableView,当cell1完全移出屏幕,并且cell11(它也是alloc出来的,原因同上)完全显示出来的时候。cell11加入到 visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。
3、接着向下拖动tableView,因为reusableTableCells中已经有值,所以,当需要显示新的 cell,cellForRowAtIndexPath再次被调用的时候,tableView dequeueReusableCellWithIdentifier:CellIdentifier,返回cell1。cell1加入到 visiableCells,cell1移出reusableTableCells;cell2移出visiableCells,cell2加入到 reusableTableCells。之后再需要显示的Cell就可以正常重用了。
所以整个过程并不难理解,但需要注意正是因为这样的原因:配置Cell的时候一定要注意,对取出的重用的cell做重新赋值,不要遗留老数据。
• 一些情况
使用过程中,我注意到,并不是只有拖动超出屏幕的时候才会更新reusableTableCells表,还有:
1、reloadData,这种情况比较特殊。一般是部分数据发生变化,需要重新刷新cell显示的内容时调用。在 cellForRowAtIndexPath调用中,所有cell都是重用的。我估计reloadData调用后,把visiableCells中所有 cell移入reusableTableCells,visiableCells清空。cellForRowAtIndexPath调用后,再把 reuse的cell从reusableTableCells取出来,放入到visiableCells。
2、 reloadRowsAtIndex,刷新指定的IndexPath。如果调用时reusableTableCells为空,那么 cellForRowAtIndexPath调用后,是新创建cell,新的cell加入到visiableCells。老的cell移出 visiableCells,加入到reusableTableCells。于是,之后的刷新就有cell做reuse了。

87.RunLoop剖析
一、RunLoop概念
RunLoop是通过内部维护的事件循环(Event Loop)来对事件/消息进行管理的一个对象。
1、没有消息处理时,休眠已避免资源占用,由用户态切换到内核态(CPU-内核态和用户态)
2、有消息需要处理时,立刻被唤醒,由内核态切换到用户态
为什么main函数不会退出?
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
UIApplicationMain内部默认开启了主线程的RunLoop,并执行了一段无限循环的代码(不是简单的for循环或while循环)
//无限循环代码模式(伪代码)
int main(int argc, char * argv[]) {
BOOL running = YES;
do {
// 执行各种任务,处理各种事件
// …
} while (running);

return 0;

}

UIApplicationMain函数一直没有返回,而是不断地接收处理消息以及等待休眠,所以运行程序之后会保持持续运行状态。
二、RunLoop的数据结构
NSRunLoop(Foundation)是CFRunLoop(CoreFoundation)的封装,提供了面向对象的API
RunLoop 相关的主要涉及五个类:
CFRunLoop:RunLoop对象
CFRunLoopMode:运行模式
CFRunLoopSource:输入源/事件源
CFRunLoopTimer:定时源
CFRunLoopObserver:观察者
1、CFRunLoop
由pthread(线程对象,说明RunLoop和线程是一一对应的)、currentMode(当前所处的运行模式)、modes(多个运行模式的集合)、commonModes(模式名称字符串集合)、commonModelItems(Observer,Timer,Source集合)构成
2、CFRunLoopMode
由name、source0、source1、observers、timers构成
3、CFRunLoopSource
分为source0和source1两种
• source0:
即非基于port的,也就是用户触发的事件。需要手动唤醒线程,将当前线程从内核态切换到用户态
• source1:
基于port的,包含一个 mach_port 和一个回调,可监听系统端口和通过内核和其他线程发送的消息,能主动唤醒RunLoop,接收分发系统事件。
具备唤醒线程的能力
4、CFRunLoopTimer
基于时间的触发器,基本上说的就是NSTimer。在预设的时间点唤醒RunLoop执行回调。因为它是基于RunLoop的,因此它不是实时的(就是NSTimer 是不准确的。 因为RunLoop只负责分发源的消息。如果线程当前正在处理繁重的任务,就有可能导致Timer本次延时,或者少执行一次)。
5、CFRunLoopObserver
监听以下时间点:CFRunLoopActivity
• kCFRunLoopEntry
RunLoop准备启动
• kCFRunLoopBeforeTimers
RunLoop将要处理一些Timer相关事件
• kCFRunLoopBeforeSources
RunLoop将要处理一些Source事件
• kCFRunLoopBeforeWaiting
RunLoop将要进行休眠状态,即将由用户态切换到内核态
• kCFRunLoopAfterWaiting
RunLoop被唤醒,即从内核态切换到用户态后
• kCFRunLoopExit
RunLoop退出
• kCFRunLoopAllActivities
监听所有状态
6、各数据结构之间的联系
线程和RunLoop一一对应, RunLoop和Mode是一对多的,Mode和source、timer、observer也是一对多的
image
三、RunLoop的Mode
关于Mode首先要知道一个RunLoop 对象中可能包含多个Mode,且每次调用 RunLoop 的主函数时,只能指定其中一个 Mode(CurrentMode)。切换 Mode,需要重新指定一个 Mode 。主要是为了分隔开不同的 Source、Timer、Observer,让它们之间互不影响。
image
当RunLoop运行在Mode1上时,是无法接受处理Mode2或Mode3上的Source、Timer、Observer事件的
总共是有五种CFRunLoopMode:
• kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行
• UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
• UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
• GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
• kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,是同步Source/Timer/Observer到多个Mode中的一种解决方案
四、RunLoop的实现机制
image
这张图在网上流传比较广。
对于RunLoop而言最核心的事情就是保证线程在没有消息的时候休眠,在有消息时唤醒,以提高程序性能。RunLoop这个机制是依靠系统内核来完成的(苹果操作系统核心组件Darwin中的Mach)。
image
RunLoop通过mach_msg()函数接收、发送消息。它的本质是调用函数mach_msg_trap(),相当于是一个系统调用,会触发内核状态切换。在用户态调用 mach_msg_trap()时会切换到内核态;内核态中内核实现的mach_msg()函数会完成实际的工作。
即基于port的source1,监听端口,端口有消息就会触发回调;而source0,要手动标记为待处理和手动唤醒RunLoop
Mach消息发送机制
大致逻辑为:
1、通知观察者 RunLoop 即将启动。
2、通知观察者即将要处理Timer事件。
3、通知观察者即将要处理source0事件。
4、处理source0事件。
5、如果基于端口的源(Source1)准备好并处于等待状态,进入步骤9。
6、通知观察者线程即将进入休眠状态。
7、将线程置于休眠状态,由用户态切换到内核态,直到下面的任一事件发生才唤醒线程。
• 一个基于 port 的Source1 的事件(图里应该是source0)。
• 一个 Timer 到时间了。
• RunLoop 自身的超时时间到了。
• 被其他调用者手动唤醒。
8、通知观察者线程将被唤醒。
9、处理唤醒时收到的事件。
• 如果用户定义的定时器启动,处理定时器事件并重启RunLoop。进入步骤2。
• 如果输入源启动,传递相应的消息。
• 如果RunLoop被显示唤醒而且时间还没超时,重启RunLoop。进入步骤2
10、通知观察者RunLoop结束。
五、RunLoop与NSTimer
一个比较常见的问题:滑动tableView时,定时器还会生效吗?
默认情况下RunLoop运行在kCFRunLoopDefaultMode下,而当滑动tableView时,RunLoop切换到UITrackingRunLoopMode,而Timer是在kCFRunLoopDefaultMode下的,就无法接受处理Timer的事件。
怎么去解决这个问题呢?把Timer添加到UITrackingRunLoopMode上并不能解决问题,因为这样在默认情况下就无法接受定时器事件了。
所以我们需要把Timer同时添加到UITrackingRunLoopMode和kCFRunLoopDefaultMode上。
那么如何把timer同时添加到多个mode上呢?就要用到NSRunLoopCommonModes了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Timer就被添加到多个mode上,这样即使RunLoop由kCFRunLoopDefaultMode切换到UITrackingRunLoopMode下,也不会影响接收Timer事件
六、RunLoop和线程
• 线程和RunLoop是一一对应的,其映射关系是保存在一个全局的 Dictionary 里
• 自己创建的线程默认是没有开启RunLoop的
1、怎么创建一个常驻线程?
1、为当前线程开启一个RunLoop(第一次调用 [NSRunLoop currentRunLoop]方法时实际是会先去创建一个RunLoop)
1、向当前RunLoop中添加一个Port/Source等维持RunLoop的事件循环(如果RunLoop的mode中一个item都没有,RunLoop会退出)
2、启动该RunLoop
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
2、输出下边代码的执行顺序
NSLog(@“1”);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@“2”);
[self performSelector:@selector(test) withObject:nil afterDelay:10];
NSLog(@“3”);
});
NSLog(@“4”);

  • (void)test
    {
    NSLog(@“5”);
    }
    答案是1423,test方法并不会执行。
    原因是如果是带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的RunLoop中。也就是如果当前线程没有开启RunLoop,该方法会失效。
    那么我们改成:
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@“2”);
    [[NSRunLoop currentRunLoop] run];
    [self performSelector:@selector(test) withObject:nil afterDelay:10];
    NSLog(@“3”);
    });
    然而test方法依然不执行。
    原因是如果RunLoop的mode中一个item都没有,RunLoop会退出。即在调用RunLoop的run方法后,由于其mode中没有添加任何item去维持RunLoop的时间循环,RunLoop随即还是会退出。
    所以我们自己启动RunLoop,一定要在添加item后
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@“2”);
    [self performSelector:@selector(test) withObject:nil afterDelay:10];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@“3”);
    });
    3、怎样保证子线程数据回来更新UI的时候不打断用户的滑动操作?
    当我们在子请求数据的同时滑动浏览当前页面,如果数据请求成功要切回主线程更新UI,那么就会影响当前正在滑动的体验。
    我们就可以将更新UI事件放在主线程的NSDefaultRunLoopMode上执行即可,这样就会等用户不再滑动页面,主线程RunLoop由UITrackingRunLoopMode切换到NSDefaultRunLoopMode时再去更新UI
    [self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];

88.内存管理、自动释放池与循环引用
一、内存布局
• 栈(stack):方法调用
• 堆(heap):通过alloc等分配的对象
• 未初始化数据(bss):未初始化的全局变量等
• 已初始化数据(data):已初始化的全局变量等
• 代码段(text):程序代码
二、内存管理方案
• taggedPointer :存储小对象如NSNumber。深入理解Tagged Pointer
• NONPOINTER_ISA(非指针型的isa):在64位架构下,isa指针是占64比特位的,实际上只有30多位就已经够用了,为了提高利用率,剩余的比特位存储了内存管理的相关数据内容。
• 散列表:复杂的数据结构,包括了引用计数表和弱引用表
通过SideTables()结构来实现的,SideTables()结构下,有很多SideTable的数据结构。
而sideTable当中包含了自旋锁,引用计数表,弱引用表。
SideTables()实际上是一个哈希表,通过对象的地址来计算该对象的引用计数在哪个sideTable中。
自旋锁:
• 自旋锁是“忙等”的锁。
• 适用于轻量访问。
引用计数表和弱引用表实际是一个哈希表,来提高查找效率。
三、MRC(手动引用计数)和ARC(自动引用计数)
1、MRC:alloc,retain,release,retainCount,autorelease,dealloc
2、ARC:
• ARC是LLVM和Runtime协作的结果
• ARC禁止手动调用retain,release,retainCount,autorelease关键字
• ARC新增weak,strong关键字
3、引用计数管理:
• alloc: 经过一系列函数调用,最终调用了calloc函数,这里并没有设置引用计数为1
• retain: 经过两次哈希查找,找到其对应引用计数值,然后将引用计数加1(实际是加偏移量)
• release:和retain相反,经过两次哈希查找,找到其对应引用计数值,然后将引用计数减1
• dealloc:
4、弱引用管理:
• 添加weak变量:通过哈希算法位置查找添加。如果查找对应位置中已经有了当前对象所对应的弱引用数组,就把新的弱引用变量添加到数组当中;如果没有,就创建一个弱引用数组,并将该弱引用变量添加到该数组中。
• 当一个被weak修饰的对象被释放后,weak对象怎么处理的?
清除weak变量,同时设置指向为nil。当对象被dealloc释放后,在dealloc的内部实现中,会调用弱引用清除的相关函数,会根据当前对象指针查找弱引用表,找到当前对象所对应的弱引用数组,将数组中的所有弱引用指针都置为nil。
5、自动释放池:
在当次runloop将要结束的时候调用objc_autoreleasePoolPop,并push进来一个新的AutoreleasePool
AutoreleasePoolPage是以栈为结点通过双向链表的形式组合而成,是和线程一一对应的。
内部属性有parent,child对应前后两个结点,thread对应线程 ,next指针指向栈中下一个可填充的位置。
• AutoreleasePool实现原理?
编译器会将 @autoreleasepool {} 改写为:
void * ctx = objc_autoreleasePoolPush;
{}
objc_autoreleasePoolPop(ctx);
• objc_autoreleasePoolPush:
把当前next位置置为nil,即哨兵对象,然后next指针指向下一个可入栈位置,
AutoreleasePool的多层嵌套,即每次objc_autoreleasePoolPush,实际上是不断地向栈中插入哨兵对象。
• objc_autoreleasePoolPop:
根据传入的哨兵对象找到对应位置。
给上次push操作之后添加的对象依次发送release消息。
回退next指针到正确的位置。
四、循环引用
循环引用的实质:多个对象相互之间有强引用,不能释放让系统回收。
如何解决循环引用?
1、避免产生循环引用,通常是将 strong 引用改为 weak 引用。
比如在修饰属性时用weak
在block内调用对象方法时,使用其弱引用,这里可以使用两个宏
#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self; // 弱引用
#define ST(strongSelf) __strong __typeof(&*self)strongSelf = weakSelf; //使用这个要先声明weakSelf
还可以使用__block来修饰变量
在MRC下,__block不会增加其引用计数,避免了循环引用
在ARC下,__block修饰对象会被强引用,无法避免循环引用,需要手动解除。
2、在合适时机去手动断开循环引用。
通常我们使用第一种。
循环引用场景:
• 自循环引用
对象强持有的属性同时持有该对象
• 相互循环引用
• 多循环引用
1、代理(delegate)循环引用属于相互循环引用
delegate 是iOS中开发中比较常遇到的循环引用,一般在声明delegate的时候都要使用弱引用 weak,或者assign,当然怎么选择使用assign还是weak,MRC的话只能用assign,在ARC的情况下最好使用weak,因为weak修饰的变量在释放后自动指向nil,防止野指针存在
2、NSTimer循环引用属于相互循环使用
在控制器内,创建NSTimer作为其属性,由于定时器创建后也会强引用该控制器对象,那么该对象和定时器就相互循环引用了。
如何解决呢?
这里我们可以使用手动断开循环引用:
如果是不重复定时器,在回调方法里将定时器invalidate并置为nil即可。
如果是重复定时器,在合适的位置将其invalidate并置为nil即可
3、block循环引用
一个简单的例子:
@property (copy, nonatomic) dispatch_block_t myBlock;
@property (copy, nonatomic) NSString *blockString;

  • (void)testBlock {
    self.myBlock = ^() {
    NSLog(@"%@",self.blockString);
    };
    }
    由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用。
    解决方案就是使用__weak修饰self即可
    __weak typeof(self) weakSelf = self;

self.myBlock = ^() {
NSLog(@"%@",weakSelf.blockString);
};
• 并不是所有block都会造成循环引用。
只有被强引用了的block才会产生循环引用
而比如dispatch_async(dispatch_get_main_queue(), ^{}),[UIView animateWithDuration:1 animations:^{}]这些系统方法等
或者block并不是其属性而是临时变量,即栈block
[self testWithBlock:^{
NSLog(@"%@",self);
}];

  • (void)testWithBlock:(dispatch_block_t)block {
    block();
    }
    还有一种场景,在block执行开始时self对象还未被释放,而执行过程中,self被释放了,由于是用weak修饰的,那么weakSelf也被释放了,此时在block里访问weakSelf时,就可能会发生错误(向nil对象发消息并不会崩溃,但也没任何效果)。
    对于这种场景,应该在block中对 对象使用__strong修饰,使得在block期间对 对象持有,block执行结束后,解除其持有。
    __weak typeof(self) weakSelf = self;

self.myBlock = ^() {
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf test];
};

89.剖析Block
一、什么是Block?
• Block是将函数及其执行上下文封装起来的对象。
比如:
NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};

block(2);

通过clang -rewrite-objc WYTest.m命令编译该.m文件,发现该block被编译成这个形式:
NSInteger num = 3;

NSInteger(*block)(NSInteger) = ((NSInteger (*)(NSInteger))&__WYTest__blockTest_block_impl_0((void *)__WYTest__blockTest_block_func_0, &__WYTest__blockTest_block_desc_0_DATA, num));((NSInteger (*)(__block_impl *, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2);

其中WYTest是文件名,blockTest是方法名,这些可以忽略。
其中__WYTest__blockTest_block_impl_0结构体为
struct __WYTest__blockTest_block_impl_0 {
struct __block_impl impl;
struct __WYTest__blockTest_block_desc_0* Desc;
NSInteger num;
__WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

__block_impl结构体为
struct __block_impl {
void *isa;//isa指针,所以说Block是对象
int Flags;
int Reserved;
void *FuncPtr;//函数指针
};

block内部有isa指针,所以说其本质也是OC对象
block内部则为:
static NSInteger __WYTest__blockTest_block_func_0(struct __WYTest__blockTest_block_impl_0 *__cself, NSInteger n) {
NSInteger num = __cself->num; // bound by copy

    return n*num;
}

所以说 Block是将函数及其执行上下文封装起来的对象
既然block内部封装了函数,那么它同样也有参数和返回值。
二、Block变量截获
1、局部变量截获 是值截获。 比如:
NSInteger num = 3;

NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){return n*num;
};
num = 1;
NSLog(@"%zd",block(2));

这里的输出是6而不是2,原因就是对局部变量num的截获是值截获。
同样,在block里如果修改变量num,也是无效的,甚至编译器会报错。
2、局部静态变量截获 是指针截获。
static NSInteger num = 3;

NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){return n*num;
};
num = 1;NSLog(@"%zd",block(2));

输出为2,意味着num = 1这里的修改num值是有效的,即是指针截获。
同样,在block里去修改变量m,也是有效的。
3、全局变量,静态全局变量截获:不截获,直接取值。
我们同样用clang编译看下结果。
static NSInteger num3 = 300;

NSInteger num4 = 3000;

  • (void)blockTest
    {
    NSInteger num = 30;

    static NSInteger num2 = 3;

    __block NSInteger num5 = 30000;

    void(^block)(void) = ^{

      NSLog(@"%zd",num);//局部变量NSLog(@"%zd",num2);//静态变量NSLog(@"%zd",num3);//全局变量NSLog(@"%zd",num4);//全局静态变量NSLog(@"%zd",num5);//__block修饰变量
    

    };

    block();
    }

编译后
struct __WYTest__blockTest_block_impl_0 {
struct __block_impl impl;
struct __WYTest__blockTest_block_desc_0* Desc;
NSInteger num;//局部变量
NSInteger *num2;//静态变量
__Block_byref_num5_0 *num5; // by ref//__block修饰变量
__WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, NSInteger *_num2, __Block_byref_num5_0 *_num5, int flags=0) : num(_num), num2(_num2), num5(_num5->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

( impl.isa = &_NSConcreteStackBlock;这里注意到这一句,即说明该block是栈block)
可以看到局部变量被编译成值形式,而静态变量被编成指针形式,全局变量并未截获。而__block修饰的变量也是以指针形式截获的,并且生成了一个新的结构体对象:
struct __Block_byref_num5_0 {
void *__isa;
__Block_byref_num5_0 *__forwarding;
int __flags;
int __size;
NSInteger num5;
};

该对象有个属性:num5,即我们用__block修饰的变量。
这里__forwarding是指向自身的(栈block)。
一般情况下,如果我们要对block截获的局部变量进行赋值操作需添加__block
修饰符,而对全局变量,静态变量是不需要添加__block修饰符的。
另外,block里访问self或成员变量都会去截获self。
三、Block的几种形式
• 分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式
其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区

1、不使用外部变量的block是全局block
比如:
NSLog(@"%@",[^{
NSLog(@“globalBlock”);
} class]);

输出:
NSGlobalBlock

2、使用外部变量并且未进行copy操作的block是栈block
比如:
NSInteger num = 10;
NSLog(@"%@",[^{
NSLog(@“stackBlock:%zd”,num);
} class]);

输出:
NSStackBlock

日常开发常用于这种情况:
[self testWithBlock:^{
NSLog(@"%@",self);
}];

  • (void)testWithBlock:(dispatch_block_t)block {
    block();

    NSLog(@"%@",[block class]);
    }

3、对栈block进行copy操作,就是堆block,而对全局block进行copy,仍是全局block
• 比如堆1中的全局进行copy操作,即赋值:
void (^globalBlock)(void) = ^{
NSLog(@“globalBlock”);
};

NSLog(@"%@",[globalBlock class]);

输出:
NSGlobalBlock

仍是全局block
• 而对2中的栈block进行赋值操作:
NSInteger num = 10;

void (^mallocBlock)(void) = ^{

    NSLog(@"stackBlock:%zd",num);
};

NSLog(@"%@",[mallocBlock class]);

输出:
NSMallocBlock

对栈blockcopy之后,并不代表着栈block就消失了,左边的mallock是堆block,右边被copy的仍是栈block
比如:
[self testWithBlock:^{

NSLog(@"%@",self);

}];

  • (void)testWithBlock:(dispatch_block_t)block
    {
    block();

    dispatch_block_t tempBlock = block;

    NSLog(@"%@,%@",[block class],[tempBlock class]);
    }

输出:
NSStackBlock,NSMallocBlock

•    即如果对栈Block进行copy,将会copy到堆区,对堆Block进行copy,将会增加引用计数,对全局Block进行copy,因为是已经初始化的,所以什么也不做。

另外,__block变量在copy时,由于__forwarding的存在,栈上的__forwarding指针会指向堆上的__forwarding变量,而堆上的__forwarding指针指向其自身,所以,如果对__block的修改,实际上是在修改堆上的__block变量。
即__forwarding指针存在的意义就是,无论在任何内存位置, 都可以顺利地访问同一个__block变量。
• 另外由于block捕获的__block修饰的变量会去持有变量,那么如果用__block修饰self,且self持有block,并且block内部使用到__block修饰的self时,就会造成多循环引用,即self持有block,block 持有__block变量,而__block变量持有self,造成内存泄漏。
比如:
__block typeof(self) weakSelf = self;

_testBlock = ^{NSLog(@"%@",weakSelf);
};_testBlock();

如果要解决这种循环引用,可以主动断开__block变量对self的持有,即在block内部使用完weakself后,将其置为nil,但这种方式有个问题,如果block一直不被调用,那么循环引用将一直存在。
所以,我们最好还是用__weak来修饰self。
收录:原文地址

90.SDWebImage原理
SDWebImage
一个为UIImageView提供一个分类来支持远程服务器图片加载的库。
功能简介:
1、一个添加了web图片加载和缓存管理的UIImageView分类
2、一个异步图片下载器
3、一个异步的内存加磁盘综合存储图片并且自动处理过期图片
4、支持动态gif图
5、支持webP格式的图片
6、后台图片解压处理
7、确保同样的图片url不会下载多次
8、确保伪造的图片url不会重复尝试下载
9、确保主线程不会阻塞
工作流程
1、入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
2、进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.
3、先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
4、SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
5、如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
6、根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
7、如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
8、如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
9、共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
10、图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
11、connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
12、图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
13、在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
14、通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
15、SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
16、SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
17、SDWebImagePrefetcher 可以预先下载图片,方便后续使用。
源码分析
主要用到的对象
一、图片下载
1、 SDWebImageDownloader
• 1.单例,图片下载器,负责图片异步下载,并对图片加载做了优化处理
• 2.图片的下载操作放在一个NSOperationQueue并发操作队列中,队列默认最大并发数是6
• 3.每个图片对应一些回调(下载进度,完成回调等),回调信息会存在downloader的URLCallbacks(一个字典,key是url地址,value是图片下载回调数组)中,URLCallbacks可能被多个线程访问,所以downloader把下载任务放在一个barrierQueue中,并设置屏障保证同一时间只有一个线程访问URLCallbacks。,在创建回调URLCallbacks的block中创建了一个NSOperation并添加到NSOperationQueue中。
• 4.每个图片下载都是一个operation类,创建后添加到一个队列中,SDWebimage定义了一个协议 SDWebImageOperation作为图片下载操作的基础协议,声明了一个cancel方法,用于取消操作。
@protocol SDWebImageOperation
-(void)cancel;
@end
• 5.对于图片的下载,SDWebImageDownloaderOperation完全依赖于NSURLConnection类,继承和实现了NSURLConnectionDataDelegate协议的方法
connection:didReceiveResponse:
connection:didReceiveData:
connectionDidFinishLoading:
connection:didFailWithError:
connection:willCacheResponse:
connectionShouldUseCredentialStorage:
-connection:willSendRequestForAuthenticationChalleng
-connection:didReceiveData:方法,接受数据,创建一个CGImageSourceRef对象,在首次获取数据时(图片width,height),图片下载完成之前,使用CGImageSourceRef对象创建一个图片对象,经过缩放、解压操作生成一个UIImage对象供回调使用,同时还有下载进度处理。
注:缩放:SDWebImageCompat中SDScaledImageForKey函数
解压:SDWebImageDecoder文件中decodedImageWithImage

2、SDWebImageDownloaderOption
• 1.继承自NSOperation类,没有简单实现main方法,而是采用更加灵活的start方法,以便自己管理下载的状态
• 2.start方法中创建了下载使用的NSURLConnections对象,开启了图片的下载,并抛出一个下载开始的通知,
• 3.小结:下载的核心是利用NSURLSession加载数据,每个图片的下载都有一个operation操作来完成,并将这些操作放到一个操作队列中,这样可以实现图片的并发下载。
3、SDWebImageDecoder(异步对图片进行解码)
二、缓存
减少网络流量,下载完图片后存储到本地,下载再获取同一张图片时,直接从本地获取,提升用户体验,能快速从本地获取呈现给用户。
SDWebImage提供了对图片进行了缓存,主要由SDImageCache完成。该类负责处理内存缓存以及一个可选的磁盘缓存,其中磁盘缓存的写操作是异步的,不会对UI造成影响。
1、内存缓存及磁盘缓存
• 1.内存缓存的处理由NSCache对象实现,NSCache类似一个集合的容器,它存储key-value对,类似于nsdictionary类,我们通常使用缓存来临时存储短时间使用但创建昂贵的对象,重用这些对象可以优化新能,同时这些对象对于程序来说不是紧要的,如果内存紧张就会自动释放。
• 2.磁盘缓存的处理使用NSFileManager对象实现,图片存储的位置位于cache文件夹,另外SDImageCache还定义了一个串行队列来异步存储图片。
• 3.SDImageCache提供了大量方法来缓存、获取、移除及清空图片。对于图片的索引,我们通过一个key来索引,在内存中,我们将其作为NSCache的key值,而在磁盘中,我们用这个key值作为图片的文件名,对于一个远程下载的图片其url实作为这个key的最佳选择。
2、存储图片
先在内存中放置一份缓存,如果需要缓存到磁盘,将磁盘缓存操作作为一个task放到串行队列中处理,会先检查图片格式是jpeg还是png,将其转换为响应的图片数据,最后吧数据写入磁盘中(文件名是对key值做MD5后的串)
3、查询图片
内存和磁盘查询图片API:

  • (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
  • (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
    查看本地是否存在key指定的图片,使用一下API:
  • (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
    4、移除图片
    移除图片API:
  • (void)removeImageForKey:(NSString *)key;
  • (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
  • (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
  • (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;

5、清理图片(磁盘)
清空磁盘图片可以选择完全清空和部分清空,完全清空就是吧缓存文件夹删除。

  • (void)clearDisk;
  • (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
    部分清理 会根据设置的一些参数移除部分文件,主要有两个指标:文件的缓存有效期(maxCacheAge:默认是1周)和最大缓存空间大小(maxCacheSize:如果所有文件大小大于最大值,会按照文件最后修改时间的逆序,以每次一半的递归来移除哪些过早的文件,知道缓存文件总大小小于最大值),具体代码参考- (void)cleanDiskWithCompletionBlock;
    6、小结
    SDImageCache处理提供以上API,还提供了获取缓存大小,缓存中图片数量等API,
    常用的接口和属性:
    (1)-getSize :获得硬盘缓存的大小
    (2)-getDiskCount : 获得硬盘缓存的图片数量
    (3)-clearMemory : 清理所有内存图片
    (4)- removeImageForKey:(NSString *)key 系列的方法 : 从内存、硬盘按要求指定清除图片
    (5)maxMemoryCost : 保存在存储器中像素的总和
    (6)maxCacheSize : 最大缓存大小 以字节为单位。默认没有设置,也就是为0,而清理磁盘缓存的先决条件为self.maxCacheSize > 0,所以0表示无限制。
    (7)maxCacheAge : 在内存缓存保留的最长时间以秒为单位计算,默认是一周
    三、SDWebImageManager
    实际使用中并不直接使用SDWebImageDownloader和SDImageCache类对图片进行下载和存储,而是使用SDWebImageManager来管理。包括平常使用UIImageView+WebCache等控件的分类,都是使用SDWebImageManager来处理,该对象内部定义了一个图片下载器(SDWebImageDownloader)和图片缓存(SDImageCache)
    @interface SDWebImageManager : NSObject
    @property (weak, nonatomic) id delegate;
    @property (strong, nonatomic, readonly) SDImageCache *imageCache;
    @property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;

@end
SDWebImageManager声明了一个delegate属性,其实是一个id对象,代理声明了两个方法
// 控制当图片在缓存中没有找到时,应该下载哪个图片

  • (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;

// 允许在图片已经被下载完成且被缓存到磁盘或内存前立即转换

  • (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
    这两个方法会在SDWebImageManager的-downloadImageWithURL:options:progress:completed:方法中调用,而这个方法是SDWebImageManager类的核心所在(具体看源码)
    SDWebImageManager的几个API:
    (1)- (void)cancelAll : 取消runningOperations中所有的操作,并全部删除
    (2)- (BOOL)isRunning :检查是否有操作在运行,这里的操作指的是下载和缓存组成的组合操作
    (3) - downloadImageWithURL:options:progress:completed: 核心方法
    (4)- (BOOL)diskImageExistsForURL:(NSURL *)url :指定url的图片是否进行了磁盘缓存

四、视图扩展
在使用SDWebImage的时候,使用最多的是UIImageView+WebCache中的针对UIImageView的扩展,核心方法是sd_setImageWithURL:placeholderImage:options:progress:completed:, 其使用SDWebImageManager单例对象下载并缓存图片。
除了扩展UIImageView外,SDWebImage还扩展了UIView,UIButton,MKAnnotationView等视图类,具体可以参考源码,除了可以使用扩展的方法下载图片,同时也可以使用SDWebImageManager下载图片。
UIView+WebCacheOperation分类:
把当前view对应的图片操作对象存储起来(通过运行时设置属性),在基类中完成
存储的结构:一个loadOperationKey属性,value是一个字典(字典结构: key:UIImageViewAnimationImages或者UIImageViewImageLoad,value是 operation数组(动态图片)或者对象)
UIButton+WebCache分类
会根据不同的按钮状态,下载的图片根据不同的状态进行设置
imageURLStorageKey:{state:url}
五、技术点
• 1.dispatch_barrier_sync函数,用于对操作设置顺序,确保在执行完任务后再确保后续操作。常用于确保线程安全性操作
• 2.NSMutableURLRequest:用于创建一个网络请求对象,可以根据需要来配置请求报头等信息
• 3.NSOperation及NSOperationQueue:操作队列是OC中一种告诫的并发处理方法,基于GCD实现,相对于GCD来说,操作队列的优点是可以取消在任务处理队列中的任务,另外在管理操作间的依赖关系方面容易一些,对SDWebImage中我们看到如何使用依赖将下载顺序设置成后进先出的顺序
• 4.NSURLSession:用于网络请求及相应处理
• 5.开启后台任务
• 6.NSCache类:一个类似于集合的容器,存储key-value对,这一点类似于nsdictionary类,我们通常用使用缓存来临时存储短时间使用但创建昂贵的对象。重用这些对象可以优化性能,因为它们的值不需要重新计算。另外一方面,这些对象对于程序来说不是紧要的,在内存紧张时会被丢弃
• 7.清理缓存图片的策略:特别是最大缓存空间大小的设置。如果所有缓存文件的总大小超过这一大小,则会按照文件最后修改时间的逆序,以每次一半的递归来移除那些过早的文件,直到缓存的实际大小小于我们设置的最大使用空间。
• 8.图片解压操作:这一操作可以查看SDWebImageDecoder.m中+decodedImageWithImage方法的实现。
• 9.对GIF图片的处理
• 10.对WebP图片的处理。
收录:原文地址

91.如何高性能的给 UIImageView 加个圆角?
不好的解决方案:使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现。
self.view.layer.cornerRadius = 5.0f;
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];

92.了解CoreData
CoreData的介绍:
• CoreData是面向对象的API,CoreData是iOS中非常重要的一项技术,几乎在所有编写的程序中,CoreData都作为数据存储的基础。
• CoreData是苹果官方提供的一套框架,用来解决与对象声明周期管理、对象关系管理和持久化等方面相关的问题。
• 大多数情况下,我们引用CoreData作为持久化数据的解决方案,并利用它作为持久化数据映射为内存对象。提供的是对象-关系映射功能,也就是说,CoreData可以将Objective-C对象转换成数据,保存到SQL中,然后将保存后的数据还原成OC对象。
CoreData的特征:
• 通过CoreData管理应用程序的数据模型,可以极大程度减少需要编写的代码数量。
• 将对象数据存储在SQLite数据库已获得性能优化。
• 提供NSFetchResultsController类用于管理表视图的数据,即将Core Data的持久化存储在表视图中,并对这些数据进行管理:增删查改。
• 管理undo/redo操纵;
• 检查托管对象的属性值是否正确。
Core Data的6成员对象
• NSManageObject:被管理的数据记录Managed Object Model是描述应用程序的数据模型,这个模型包含实体(Entity)、特性(Property)、读取请求(Fetch Request)等。
• NSManageObjectContext:管理对象上下文,持久性存储模型对象,参与数据对象进行各种操作的全过程,并监测数据对象的变化,以提供对undo/redo的支持及更新绑定到数据的UI。
• NSPersistentStoreCoordinator:连接数据库的Persistent Store Coordinator相当于数据文件管理器,处理底层的对数据文件的读取和写入,一般我们与这个没有交集。
• NSManagedObjectModel:被管理的数据模型、数据结构。
• NSFetchRequest:数据请求;
• NSEntityDescription:表格实体结构,还需知道.xcdatamodel文件编译后为.momd或者.mom文件。
Core Data的功能
• 对于KVC和KVO完整且自动化的支持,除了为属性整合KVO和KVC访问方法外,还整合了适当的集合访问方法来处理多值关系;
• 自动验证属性(property)值;
• 支持跟踪修改和撤销操作;
• 关系维护,Core Data管理数据的关系传播,包括维护对象间的一致性;
• 在内存上和界面上分组、过滤、组织数据;
• 自动支持对象存储在外部数据仓库的功能;
• 创建复杂请求:无需动手写SQL语句,在获取请求(fetch request)中关联NSPredicate。NSPreadicate支持基本功能、相关子查询和其他高级的SQL特性。它支持正确的Unicode编码、区域感知查询、排序和正则表达式;
• 延迟操作:Core Data使用 懒加载(lazy loading)方式减少内存负载,还支持部分实体化延迟加载和复制对象的数据共享机制;
• 合并策略:Core Data内置版本跟踪和乐观锁(optimistic locking)来支持多用户写入冲突的解决,其中,乐观锁就是对数据冲突进行检测,若冲突就返回冲突的信息;
• 数据迁移:Core Data的Schema Migration工具可以简化应对数据库结构变化的任务,在某些情况允许你执行高效率的数据库原地迁移工作;
• 可选择针对程序Controller层的集成,来支持UI的显示同步Core Data在IPhone OS之上,提供NSFetchedResultsController对象来做相关工作,在Mac OS X上我们用Cocoa提供的绑定(Binding)机制来完成的。

93.简述内存管理基本原则
• 之前:OC内存管理遵循“谁创建,谁释放,谁引用,谁管理”的机制,当创建或引用一个对象的时候,需要向她发送alloc、copy、retain消息,当释放该对象时需要发送release消息,当对象引用计数为0时,系统将释放该对象,这是OC的手动管理机制(MRC)。
• 目前:iOS 5.0之后引用自动管理机制——自动引用计数(ARC),管理机制与手动机制一样,只是不再需要调用retain、release、autorelease;它编译时的特性,当你使用ARC时,在适当位置插入release和autorelease;它引用strong和weak关键字,strong修饰的指针变量指向对象时,当指针指向新值或者指针不复存在,相关联的对象就会自动释放,而weak修饰的指针变量指向对象,当对象的拥有者指向新值或者不存在时weak修饰的指针会自动置为nil。
• 如果使用alloc、copy(mutableCopy)或者retian一个对象时,你就有义务,向它发送一条release或者autorelease消息。其他方法创建的对象,不需要由你来管理内存。
• 向一个对象发送一条autorelease消息,这个对象并不会立即销毁, 而是将这个对象放入了自动释放池,待池子释放时,它会向池中每一个对象发送 一条release消息,以此来释放对象.
• 向一个对象发送release消息,并不意味着这个对象被销毁了,而是当这个对象的引用计数为0时,系统才会调用dealloc方法,释放该对象和对象本身它所拥有的实例。
其他注意事项
1. 如果一个对象有一个_strong类型的指针指向着,找个对象就不会被释放。如果一个指针指向超出了它的作用域,就会被指向nil。如果一个指针被指向nil,那么它原来指向的对象就被释放了。当一个视图控制器被释放时,它内部的全局指针会被指向nil。用法“:不管全局变量还是局部变量用_strong描述就行。
2. 局部变量:出了作用域,指针会被置为nil。
3. 方法内部创建对象,外部使用需要添加_autorelease;
4. 连线的时候,用_weak描述。
5. 代理使用unsafe_unretained就相当于assign;
6. block中为了避免循环引用问题,使用_weak描述;
7. 声明属性时,不要以new开头。如果非要以new开头命名属性的名字,需要自己定制get方法名,如:
@property(getter=theString) NSString * newString;
8. 如果要使用自动释放池,用@autoreleasepool{}
9. ARC只能管理Foundation框架的变量,如果程序中把Foundation中的变量强制换成COreFoundation中的变量需要交换管理权;
10. 在非ARC工程中采用ARC去编译某些类:-fobjc-arc。
11. 在ARC下的工程采用非ARC去编译某些类:-fno-fobjc-arc。

94, GCD死锁问题解读
一.题干:
__block int x = 0;
__block int y = 0;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d", x++);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%d", ++y);
});
NSLog(@"%d", y++);
NSLog(@"%d", x + y);
while (1) {}
});

那么下面直接解答
1.首先调用dispatch_async(异步调度)把我们的任务抛到dispatch_get_global_queue(全局并行队列), 这会让我们第一个block中的线程跳到子线程, 下文称gq.
2.打印NSLog(@"%d", x++);, 由于++在x后面, 分号结束才自增1, 所以打印出0.
3.调用dispatch_sync把NSLog(@"%d", ++y);任务同步调度到主线程队列中, 主线程会去执行该任务, 不死锁的原因是dispatch_sync并没有在主线程中创建, 而是在dispatch_get_global_queue中创建并等待任务执行结束, 由于它是子线程, 所以并不会阻塞.
4.下面就都是简单的逻辑运算了, 直到碰上while (1) {}死循环.
下面是答案
2019-03-07 15:21:14.471910+0800 [75481:2405428] 0
2019-03-07 15:21:14.476663+0800 [75481:2405369] 1
2019-03-07 15:21:14.476834+0800 [75481:2405428] 1
2019-03-07 15:21:14.476986+0800 [75481:2405428] 3

可能有些人对上面的子线程和主线程理解不太深刻, 这里我打印出block中的线程关系给大家看一下.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@“A — %@”, [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@“B — %@”, [NSThread currentThread]);
});
NSLog(@“C — %@”, [NSThread currentThread]);
while (1) {}
});

A — <NSThread: 0x600002f836c0>{number = 3, name = (null)}
B — <NSThread: 0x600002fd5400>{number = 1, name = main}
C — <NSThread: 0x600002f836c0>{number = 3, name = (null)}

我们可以看到打印结果, 在执行dispatch_async后, 线程跳到了number = 3的子线程, 在子线程中进行同步方法的处理, 并不会阻塞主线程且阻塞子线程不会造成死锁, 所以这个理论是完全可以说得过去的.
二.拓展
通过上面的问题又拓展一下关于多线程和GCD的知识, 你如果没看懂上面的解答, 不妨先了解一下基础知识.
通常上网络上对死锁的解释是
主线程队列是串行队列, 队列中的任务是一个执行完成后才去执行另一个, 用同步方法将任务1提交到主线程队列就会阻塞住主线程, 而这个刚提交的任务1必须等待主线程中的任务执行完毕才可以执行, 但这时主线程已经被阻塞了, 就是说要等任务1执行完成后才能去执行原有的任务, 所以双方在互相等待而卡住, 最后一个任务也没机会执行到, 就造成了死锁.
按照这种逻辑, 我也自动生成了一套自己的理解, 如果有不对的地方也请指出:
我认为主线程是永远都在执行任务的, 什么任务不重要, 这时候我们有一个任务要dispatch_sync同步抛到主线程队列中执行, 当我们抛进去的那一瞬间, 这个任务就捕获了主线程的控制权(或可以说成不阻塞), 但是主线程只能眼睁睁看着它却不能执行它, 因为主线程在等待之前的任务执行完毕, 这样双方都在等待主线程处理自己, 所以会造成死锁, 而我们的题目刚开始就跳到了子线程, 在子线程中等待不会捕获主线程控制权(或可以说成不阻塞主线程), 所以不会造成死锁.
假设声明:
这里还是要声明一点就是 捕获了主线程的控制权 是我自己假想出来的东西, 不真实, 没有任何依据, 只是凭空想象出这么一个东西来一定程度上的帮助理解, 如果思想不对也请指出.
下面拓展一下队列的知识:
队列的概念:队列就跟我们排队一样, 从类型上一共分为两种串行队列和并行队列.
串行队列: 10个人在售票窗口买票, 中途不能插队, 任务是按照顺序进行的, 第一个执行完了才继续执行第二个一直到第10个.
并行队列: 10个人在10个自动售货机前买票, 同时进行, 至于谁先买完, 我们无从得知…
然后说一下GCD, 平时常用的调度方法包括下面两种dispatch_sync和dispatch_async下面我们就依次介绍一下.
我们首先来看一下iOS中的线程
image
主线程: 应用只有一个, 编号为1且名称为main, 在主线程中同步处理耗时任务会阻塞.
子线程: 应用中可能有多个, 编号不确定可能为1也可能不为1, 处理耗时操作不会阻塞.(因为发现过为1且名字不为main的线程, 所以这里还要大家来证实)
dispatch_sync: 翻译为同步调度, 在指定队列中同步扔进去一个任务(block), 该任务可能由主线程或子线程处理.
dispatch_async: 翻译为异步调度, 在指定队列中扔进去一个任务(block), 该任务可能由主线程或子线程处理.
所以在这里我结合队列总结一下, 在我们的程序中一共存在4种队列(我知道的), 分别是:
1.主线程队列
dispatch_get_main_queue()

主线程队列是一个典型的串行队列, 里面最多只能容许一个线程来执行, 也就是主线程, 向里面插入任务, 无论是同步或异步, 该任务均由主线程执行, 但在主线程运行的队列中同步调度会死锁.

2.全局并行队列
dispatch_get_global_queue(0, 0)

全局并行队列是一个典型的并行队列.

3.串行队列
dispatch_queue_create(“com.objcat.serial”, DISPATCH_QUEUE_SERIAL);

串行队列是自己创建出的队列, 主线程队列一样, 任务也是一个一个来执行的.

4.并行队列
dispatch_queue_create(“com.objcat.concurrent”, DISPATCH_QUEUE_CONCURRENT);

并行队列是自己创建出的队列, 跟全局并行队列一样, 不同线程上的任务是一起执行的, 哪个先执行完并不一定.

所以到这里你应该会明白一个道理, 是否死锁与线程的种类和调度的类型有关, 当在主线程上同步调度任务的时候才会出现死锁.
网上还有这张图, 也给拿来了
我们可以看到任务3卡在了任务2之前并阻塞了线程, 而任务2在等任务3, 任务3在等任务2, 所以就造成了死锁.
三.反思
有可能你认为刚才讲的故事不是特别通顺, 没错, 我同样认为在很多地方仍解释不通, 假如当前主线程队列中不存在任务, 我向其中插入一个任务为什么就不能执行呢?
我尝试在死锁前面打断点来查看主线程队列中的任务
结果我发现任务是viewDidLoad, 证明确实有任务正在进行.
这样就可以解释通, 主线程一直在处理viewDidLoad的代码, 所以当我们强行插入一个任务的时候主线程队列就会因为这个任务的强行插入而转为互相等待状态因此会死锁.
收录原文

95.谈一谈网络中的 session 和 cookie?
因为 Http 无状态的特性,如果需要判断是哪个用户,这时就需要 Cookie 和 Session。
Cookie 存储在客户端,用来记录用户状态,区分用户。一般都是服务端把生成的 Cookie 通过响应返回给客户端,客户端保存。
Session 存储在网络端,需要依赖 Cookie 机制。服务端生成了 Session 后,返回给客户端,客户端 setCookie:sessionID,所以下次请求的时候,客户端把 Cookie 发送给服务端,服务端解析出 SessionID,在服务端根据 SessionID 判断当前的用户。
如何修改 Cookie?
• 相同 Key 值得新的 Cookie 会覆盖旧的 Cookie.
• 覆盖规则是 name path 和 domain 等需要与原有的一致
如何删除 Cookie?
• 相同 Key 值得新的 Cookie 会覆盖旧的 Cookie.
• 覆盖规则是 name path 和 domain 等需要与原有的一致
• 设置 Cookie 的 expires 为过去的一个时间点,或者 maxAge = 0
如何保证 Cookie 的安全?
• 对 Cookie 进行加密处理
• 只在 Https 上携带 Cookie。
• 设置 Cookie 为 httpOnly,防止跨站脚本攻击。

96.UIWindow,UIView,CALayer的区别

  1. UIWindow
    @interface UIWindow : UIView

@property(nonatomic) UIWindowLevel windowLevel; // default = 0.0
@property(nonatomic,readonly,getter=isKeyWindow) BOOL keyWindow;

  • (void)becomeKeyWindow; // override point for subclass. Do not call directly

  • (void)resignKeyWindow; // override point for subclass. Do not call directly

  • (void)makeKeyWindow;

  • (void)makeKeyAndVisible; // convenience. most apps call this to show the main window and also make it key. otherwise use view hidden property

@property(nullable, nonatomic,strong) UIViewController *rootViewController NS_AVAILABLE_IOS(4_0); // default is nil
@end

继承自UIView,是一种特殊的 UIView,通常在一个app中只会有一个keyUIWindow。
iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了
主要作用是提供一个区域用来显示UIView;将事件分发给UIView;与UIViewController一起处理屏幕的旋转事件。
2. UIView
@interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate>

@property(nonatomic,readonly,strong) CALayer *layer;
@end

@interface UIResponder : NSObject

继承自UIResponder,间接继承自NSObject,主要是用来构建用户界面的,并且可以响应事件。
对于UIView,侧重于对内容的显示管理;其实是相对于CALayer的高层封装。
3. CALayer
@interface CALayer : NSObject <NSSecureCoding, CAMediaTiming>

直接继承自NSObject,所以不能响应事件
其实就是一个图层,UIView之所以能显示在屏幕上,主要是它内部有一个CALayer对象。在创建UIView时,它内部会自动创建一个图层,当UIView需要显示在屏幕上的时候,会调用drawRect:方法进行绘图,并且会将所有内容绘制到自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,这样完成UIView的显示。
• layer给view提供了基础设施,使得绘制内容和呈现更高效动画更容易、更低耗
• layer不参与view的事件处理、不参与响应链

97.事件传递和响应机制

  1. 事件的产生
    • 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,为什么是队列而不是栈?因为队列的特点是FIFO,即先进先出,先产生的事件先处理才符合常理,所以把事件添加到队列。
    • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)。
    • 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步。
    找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理。
  2. 事件的传递
    • 触摸事件的传递是从父控件传递到子控件
    • 也就是UIApplication->window->寻找处理事件最合适的view
    注 意: 如果父控件不能接受触摸事件,那么子控件就不可能接收到触摸事件
  3. 应用如何找到最合适的控件来处理事件?
    • 1.首先判断主窗口(keyWindow)自己是否能接受触摸事件
    • 2.判断触摸点是否在自己身上
    • 3.子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)
    • 4.如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view。
    3.1 寻找最合适的view底层剖析
    两个重要的方法:
  • (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
  • (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

3.2 hitTest:withEvent 方法介绍
什么时候调用?
• 只要事件一传递给一个控件,这个控件就会调用他自己的hitTest:withEvent:方法
作用
• 寻找并返回最合适的view(能够响应事件的那个最合适的view)
注 意
1.不管这个控件能不能处理事件,也不管触摸点在不在这个控件上,事件都会先传递给这个控件,随后再调用hitTest:withEvent:方法
2.如果hitTest:withEvent:方法中返回nil,那么调用该方法的控件本身和其子控件都不是最合适的view,也就是在自己身上没有找到更合适的view。那么最合适的view就是该控件的父控件。
3.3 pointInside:withEvent 方法介绍
判断点在不在当前view上(方法调用者的坐标系上)如果返回YES,代表点在方法调用者的坐标系上;返回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能处理事件。
UIView不能接收触摸事件的三种情况:
• 不允许交互:userInteractionEnabled = NO
• 隐藏:如果把父控件隐藏,那么子控件也会隐藏,隐藏的控件不能接受事件
• 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明。
4. 事件的响应
4.1 触摸事件处理的整体过程
• 1 用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件
• 2 找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理touchesBegan…touchesMoved…touchedEnded…
• 3 这些touches方法的默认做法是将事件顺着响应者链条向上传递(也就是touch方法默认不处理事件,只传递事件),将事件交给上一个响应者进行处理
4.2 响应者链条
在iOS程序中无论是最后面的UIWindow还是最前面的某个按钮,它们的摆放是有前后关系的,一个控件可以放到另一个控件上面或下面,那么用户点击某个控件时是触发上面的控件还是下面的控件呢,这种先后关系构成一个链条就叫响应者链。也可以说,响应者链是由多个响应者对象连接起来的链条。
在iOS中响应者链的关系可以用下图表示:

响应者对象 能处理事件的对象,也就是继承自UIResponder的对象
作用 能很清楚的看见每个响应者之间的联系,并且可以让一个事件多个对象处理。
如何判断上一个响应者
• 1 如果当前这个view是控制器的view,那么控制器就是上一个响应者
• 2 如果当前这个view不是控制器的view,那么父控件就是上一个响应者
响应者链的事件传递过程
• 1 如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
• 2 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
• 3 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
• 4 如果UIApplication也不能处理该事件或消息,则将其丢弃
5. 总结
事件处理的整个流程总结:
• 1.触摸屏幕产生触摸事件后,触摸事件会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)。
• 2.UIApplication会从事件队列中取出最前面的事件,把事件传递给应用程序的主窗口(keyWindow)。
• 3.主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。(至此,第一步已完成)
• 4.最合适的view会调用自己的touches方法处理事件
• 5.touches默认做法是把事件顺着响应者链条向上抛。
如何做到一个事件多个对象处理:
因为系统默认做法是把事件上抛给父控件,所以可以通过重写自己的touches方法和父控件的touches方法来达到一个事件多个对象处理的目的。

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    // 1.自己先处理事件…
    NSLog(@“do somthing…”);
    // 2.再调用系统的默认做法,再把事件交给上一个响应者处理
    [super touchesBegan:touches withEvent:event];
    }

事件的传递和响应的区别:
事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。

98.UIView block动画实现原理
在了解UIView block动画实现原理之前,需要先了解CALayer的可动画属性。

  1. CALayer的可动画属性
    CALayer拥有大量的属性,看CALayer的头文件内容,会发现很多的属性的注释中,最后会有一个词叫做Animatable,直译过来是可动画的。下面的截图只是CALayer众多可动画属性中的一部分(注意frame并不是可动画的属性)
    /* The bounds of the layer. Defaults to CGRectZero. Animatable. */

@property CGRect bounds;

/* The position in the superlayer that the anchor point of the layer’s

  • bounds rect is aligned to. Defaults to the zero point. Animatable. */

@property CGPoint position;

/* The Z component of the layer’s position in its superlayer. Defaults

  • to zero. Animatable. */

@property CGFloat zPosition;

/* Defines the anchor point of the layer’s bounds rect, as a point in

  • normalized layer coordinates - ‘(0, 0)’ is the bottom left corner of
  • the bounds rect, ‘(1, 1)’ is the top right corner. Defaults to
  • ‘(0.5, 0.5)’, i.e. the center of the bounds rect. Animatable. */

@property CGPoint anchorPoint;

/* The Z component of the layer’s anchor point (i.e. reference point for

  • position and transform). Defaults to zero. Animatable. */

@property CGFloat anchorPointZ;

/* A transform applied to the layer relative to the anchor point of its

  • bounds rect. Defaults to the identity transform. Animatable. */

@property CATransform3D transform;

如果一个属性被标记为Animatable,那么它具有以下两个特点:
1、直接对它赋值可能产生隐式动画;
2、我们的CAAnimation的keyPath可以设置为这个属性的名字。
当我们直接对可动画属性赋值的时候,由于有隐式动画存在的可能,CALayer首先会判断此时有没有隐式动画被触发。它会让它的delegate(没错CALayer拥有一个属性叫做delegate)调用actionForLayer:forKey:来获取一个返回值,这个返回值在声明的时候是一个id对象,当然在运行时它可能是任何对象。这时CALayer拿到返回值,将进行判断:
• 如果返回的对象是一个nil,则进行默认的隐式动画;
• 如果返回的对象是一个[NSNull null] ,则CALayer不会做任何动画;
• 如果是一个正确的实现了CAAction协议的对象,则CALayer用这个对象来生成一个CAAnimation,并加到自己身上进行动画。
理解完这些,我们再来分析UIView的block动画就容易理解了。
2. UIView的block动画
Amazing things happen when they are in a block.

有趣的是,如果这个CALayer被一个UIView所持有,那么这个CALayer的delegate就是持有它的那个UIView。
大家应该可以思考出这样的问题:为什么同样的一行代码在block里面就有动画在block外面就没动画,就像下面这样:
/** 产生动画 */

  • (void)createAnimation {
    UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];

    // 这样写没有动画
    redView.center = CGPointMake(200, 300);

    [UIView animateWithDuration:1.25 animations:^{
    // 写在block里面就有动画
    redView.center = CGPointMake(200, 300);
    }];
    }
    既然UIView就是CALayer的delegate,那么actionForLayer:forKey:方法就是由UIView来实现的。所以UIView可以相当灵活的控制动画的产生。
    当我们对UIView的一个属性赋值的时候,它只是简单的调用了它持有的那个CALayer的对应的属性的setter方法而已,根据上面的可动画属性的特点,CALayer会让它的delegate(也就是这个UIView)调用actionForLayer:forKey:方法。实际上结果大家都应该能想得到:在UIView的动画block外面,UIView的这个方法将返回NSNull,而在block里面,UIView将返回一个正确的CAAction对象(这里将不深究UIView是如何判断此时setter的调用是在动画block外面还是里面的)。
    为了证明这个结论,我们将继续进行实验:
    /** UIView 动画产生原理 */

  • (void)uiviewAnimation {
    NSLog(@"%@",[self.view.layer.delegate actionForLayer:self.view.layer forKey:@“position”]);

    [UIView animateWithDuration:1.25 animations:^{
    NSLog(@"%@",[self.view.layer.delegate actionForLayer:self.view.layer forKey:@“position”]);
    }];
    }

我们分别在block外面和block里面打印actionForLayer:forKey:方法的返回值,看看它究竟是什么玩意。
2019-06-11 19:45:23.973002+0800 YaZhaiInterviewQuestionDemo[93569:1953578]
2019-06-11 19:45:23.973622+0800 YaZhaiInterviewQuestionDemo[93569:1953578] <_UIViewAdditiveAnimationAction: 0x600001ff0d40>

打印发现,我们的结论是正确的:在block外面,这个方法将返回一个NSNull(是尖括号的null,nil打印出来是圆括号的null),而在block里面返回了一个叫做UIViewAdditiveAnimationAction类的对象,这个类是一个私有类,遵循了苹果一罐的命名规范: xxAction,一定就是一个实现了CAAction协议的对象了。
这也就说明了为什么我们对一个view的center赋值,如果这行代码在动画block里面,就会有动画,在block外面则没有动画。

99.MVVM和MVC的区别
MVVM和MVC的区别

  1. MVC
    MVC
    MVC的弊端
    • 厚重的View Controller
    ◦ M:模型model的对象通常非常的简单。根据Apple的文档,model应包括数据和操作数据的业务逻辑。而在实践中,model层往往非常薄,不管怎样,model层的业务逻辑不应被拖入到controller。
    ◦ V:视图view通常是UIKit控件(component,这里根据习惯译为控件)或者编码定义的UIKit控件的集合。View的如何构建(PS:IB或者手写界面)何必让Controller知晓,同时View不应该直接引用model(PS:现实中,你懂的!),并且仅仅通过IBAction事件引用controller。业务逻辑很明显不归入view,视图本身没有任何业务。
    ◦ C:控制器controller。Controller是app的“胶水代码”:协调模型和视图之间的所有交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的loading、appearing、disappearing等等,同时往往也会充满我们不愿暴露的model的模型逻辑以及不愿暴露给视图的业务逻辑。网络数据的请求及后续处理,本地数据库操作,以及一些带有工具性质辅助方法都加大了Massive View Controller的产生。
    • 遗失(无处安放)的网络逻辑
    苹果使用的MVC的定义是这么说的:所有的对象都可以被归类为一个model,一个view,或是一个controller。
    你可能试着把它放在Model对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的model生命周期更长,事情将变的复杂。显然View里面做网络请求那就更格格不入了,因此只剩下Controller了。若这样,这又加剧了Massive View Controller的问题。若不这样,何处才是网络逻辑的家呢?
    • 较差的可测试性
    由于View Controller混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。
  2. MVVM
    一种可以很好地解决Massive View Controller问题的办法就是将 Controller 中的展示逻辑抽取出来,放置到一个专门的地方,而这个地方就是 viewModel 。MVVM衍生于MVC,是对 MVC 的一种演进,它促进了 UI 代码与业务逻辑的分离。它正式规范了视图和控制器紧耦合的性质,并引入新的组件。他们之间的结构关系如下:

MVVM
2.1 MVVM 的基本概念
• 在MVVM 中,view 和 view controller正式联系在一起,我们把它们视为一个组件
• view 和 view controller 都不能直接引用model,而是引用视图模型(viewModel)
• viewModel 是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他代码的地方
• 使用MVVM会轻微的增加代码量,但总体上减少了代码的复杂性
2.2 MVVM 的注意事项
• view 引用viewModel ,但反过来不行(即不要在viewModel中引入#import UIKit.h,任何视图本身的引用都不应该放在viewModel中)(PS:基本要求,必须满足)
• viewModel 引用model,但反过来不行* MVVM 的使用建议
• MVVM 可以兼容你当下使用的MVC架构。
• MVVM 增加你的应用的可测试性。
• MVVM 配合一个绑定机制效果最好(PS:ReactiveCocoa你值得拥有)。
• viewController 尽量不涉及业务逻辑,让 viewModel 去做这些事情。
• viewController 只是一个中间人,接收 view 的事件、调用 viewModel 的方法、响应 viewModel 的变化。
• viewModel 绝对不能包含视图 view(UIKit.h),不然就跟 view 产生了耦合,不方便复用和测试。
• viewModel之间可以有依赖。
• viewModel避免过于臃肿,否则重蹈Controller的覆辙,变得难以维护。
2.3 MVVM 的优势
• 低耦合:View 可以独立于Model变化和修改,一个 viewModel 可以绑定到不同的 View 上
• 可重用性:可以把一些视图逻辑放在一个 viewModel里面,让很多 view 重用这段视图逻辑
• 独立开发:开发人员可以专注于业务逻辑和数据的开发 viewModel,设计人员可以专注于页面设计
• 可测试:通常界面是比较难于测试的,而 MVVM 模式可以针对 viewModel来进行测试
2.4 MVVM 的弊端
• 数据绑定使得Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
• 对于过大的项目,数据绑定和数据转化需要花费更多的内存(成本)。主要成本在于:
• 数组内容的转化成本较高:数组里面每项都要转化成Item对象,如果Item对象中还有类似数组,就很头疼。
• 转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。
• 只有在API返回的数据高度标准化时,这些对象原型(Item)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。
• 调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray直观。
• 同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。
3. 总结
• MVC的设计模式也并非是病入膏肓,无药可救的架构,最起码目前MVC设计模式仍旧是iOS开发的主流框架,存在即合理。针对文章所述的弊端,我们依旧有许多可行的方法去避免和解决,从而打造一个轻量级的ViewController。
• MVVM是MVC的升级版,完全兼容当前的MVC架构,MVVM虽然促进了UI 代码与业务逻辑的分离,一定程度上减轻了ViewController的臃肿度,但是View和ViewModel之间的数据绑定使得 MVVM变得复杂和难用了,如果我们不能更好的驾驭两者之间的数据绑定,同样会造成Controller 代码过于复杂,代码逻辑不易维护的问题。
• 一个轻量级的ViewController是基于MVC和MVVM模式进行代码职责的分离而打造的。MVC和MVVM有优点也有缺点,但缺点在他们所带来的好处面前时不值一提的。他们的低耦合性,封装性,可测试性,可维护性和多人协作便利大大提高了开法效率。
• 同时,我们需要保持的是一个拥抱变化的心,以及理性分析的态度。在新技术的面前,不盲从,也不守旧,一切的决策都应该建立在认真分析的基础上,这样才能应对技术的变化。

100.NSCache,NSDictionary,NSArray的区别

  1. NSArray
    NSArray作为一个存储对象的有序集合,可能是被使用最多的集合类。
    性能特征
    在数组的开头和结尾插入/删除元素通常是一个O(1)操作,而随机的插入/删除通常是 O(N)的。
    有用的方法
    NSArray的大多数方法使用isEqual:来检查对象间的关系(例如containsObject:)。有一个特别的方法
    indexOfObjectIdenticalTo:
    用来检查指针相等,如果你确保在同一个集合中搜索,那么这个方法可以很大的提升搜索速度。
    更多相关资料参考
  2. NSDictionary
    一个字典存储任意的对象键值对。 由于历史原因,初始化方法使用相反的对象到值的方法,
    [NSDictionary dictionaryWithObjectsAndKeys:object, key, nil]

而新的快捷语法则从key开始
@{key : value, …}
NSDictionary中的键是被拷贝的并且需要是恒定的。如果在一个键在被用于在字典中放入一个值后被改变,那么这个值可能就会变得无法获取了。一个有趣的细节,在NSDictionary中键是被拷贝的,而在使用一个toll-free桥接的CFDictionary时却只被retain。CoreFoundation类没有通用对象的拷贝方法,因此这时拷贝是不可能的(*)。这只适用于使用CFDictionarySetValue()的时候。如果通过setObject:forKey使用toll-free桥接的CFDictionary,苹果增加了额外处理逻辑来使键被拷贝。反过来这个结论则不成立 — 转换为CFDictionary的NSDictionary对象,对其使用CFDictionarySetValue()方法会调用回setObject:forKey并拷贝键。
3. NSCache
NSCache是一个非常奇怪的集合。在iOS 4/Snow Leopard中加入,默认为可变并且线程安全的。这使它很适合缓存那些创建起来代价高昂的对象。它自动对内存警告做出反应并基于可设置的成本清理自己。与NSDictionary相比,键是被retain而不是被拷贝的。
NSCache的回收方法是不确定的,在文档中也没有说明。向里面放一些类似图片那样比被回收更快填满内存的大对象不是个好主意。(这是在PSPDFKit中很多跟内存有关的crash的原因,在使用自定义的基于LRU的链表的缓存代码之前,我们起初使用NSCache存储事先渲染的图片。)
NSCache可以设置撑自动回收实现了NSDiscardableContent协议的对象。实现该属性的一个比较流行的类是同时间加入的NSPurgeableData,但是在OS X 10.9之前,是非线程安全的(没有信息表明这是否也影响到iOS或者是否在iOS 7中被修复了)。
NSCache性能
那么NSCache如何承受NSMutableDictionary的考验?加入的线程安全必然会带来一些消耗。
4. iOS 构建缓存时选 NSCache 而非NSDictionary
• 当系统资源将要耗尽时,NSCache可以自动删减缓存。如果采用普通的字典,那么就要自己编写挂钩,在系统通知时手动删减缓存,NSCache会先行删减 时间最久为被使用的对象
• NSCache 并不会拷贝键,而是会保留它。此行为用NSDictionary也可以实现,但是需要编写比较复杂的代码。NSCache对象不拷贝键的原因在于,很多时候键都是不支持拷贝操作的对象来充当的。因此NSCache对象不会自动拷贝键,所以在键不支持拷贝操作的情况下,该类比字典用起来更方便
• NScache是线程安全的,NSDictionary不是。在开发者自己不编写加锁代码的前提下,多个线程可以同时访问NSCache。对缓存来说,线程安全通常是很重要的,因为开发者可能在某个线程中读取数据,此时如果发现缓存里找不着指定的键,那么就要下载该键对应的数据了。

(希望对你有用

iOS最新面试题(一)相关推荐

  1. 2017 iOS最新面试题汇总(二)

    原文地址   点击打开链接 就在上个月中旬,技术老大让我招两个1-2年的iOS开发,把简历的筛选和第一轮技术面试的任务交给了我. 从筛选第一份简历,准备面试题,到成功招到两个人一共花了两个星期多一点, ...

  2. iOS最新面试题4【经典】_技术成长笔记_新浪博客

    游戏题:   1.项目介绍: 你所在的团队要开始一项为期4个月的iPhone游戏项目.在团队开始工作之前,游戏引擎要支持一些功能. 任务一: 添加触屏支持(创建一个iPhone应用程序,让玩家能用3根 ...

  3. 2020,300道高级iOS开发面试题(最新整理)

    这个栏目将持续更新–请iOS的小伙伴关注! 一:知名大厂iOS开发面试题篇 1.腾讯-最新iOS面试题总结 2.百度-最新iOS面试题总结 3.头条-最新iOS面试题总结 4.阿里-最新iOS面试题总 ...

  4. 2020前端最新面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)(3年前端菜鸟级开发师含泪总结)

    2020前端最新面试题总结(js.html.小程序.React.ES6.Vue.算法.全栈热门视频资源) 文档描述 (今年确实挺难 3年前端菜鸟级开发师含泪总结 希望能帮助大家) 本文是关注微信小程序 ...

  5. 《招聘一个靠谱的iOS》面试题参考答案(下)

    参考: <招聘一个靠谱的iOS>面试题参考答案(下) <招聘一个靠谱的iOS> 25 _objc_msgForward函数是做什么的,直接调用它将会发生什么? _objc_ms ...

  6. 李洪强iOS经典面试题153- 补充

    李洪强iOS经典面试题153- 补充   补充 有空就来解决几个问题,已经懒癌晚期没救了... UML 统一建模语言(UML,UnifiedModelingLanguage)是面向对象软件的标准化建模 ...

  7. 甘肃省计算机二级考试题库,2011甘肃省计算机等级考试二级最新考试试题库(完整版)...

    2011甘肃省计算机等级考试二级最新考试试题库(完整版) 1.在软件生命周期中,能准确地确定软件系统必须做什么和必须具备哪些功能的阶段是(D) A. 概要设计 B. 详细设计 C. 可行性分析 D. ...

  8. 微型计算机中的外存储器 现在普遍采用,2010澳门特别行政区全国计算机等级考试二级笔试试卷VB最新考试试题库(完整版)...

    2010澳门特别行政区全国计算机等级考试二级笔试试卷VB最新考试试题库(完整版) 1.LINUX是一种( B ) A.数据库管理系统 B.微机操作系统 C.字处理系统 D.鼠标器驱动程序 2.动态RA ...

  9. iOS开发面试题整理

    前言 本文借鉴整理了iOS高级开发常见的面试题,并且分博客一一分析,希望能和大家一起进步学习. 欢迎大家关注我的 Github?以及相关博客 Github.io 简书 大家的鼓励是我前进的动力? iO ...

  10. iOS经典面试题总结--内存管理

    内存管理 1.什么是ARC? ARC是automatic reference counting自动引用计数,在程序编译时自动加入retain/release.在对象被创建时retain count+1 ...

最新文章

  1. 单片机简单的计算器c语言程序,求一个 89C51 简易计算器的c语言程序 只要加减乘除就行!...
  2. 数据挖掘原理与算法:对森林火灾影响因素的分析
  3. oracle用户名密码过期引起的网站后台无法登录
  4. scott登录查询常用语句
  5. 硬盘快速格式化和格式化的区别
  6. 强强联合,OpenCV搭载飞桨模型,帮你轻松玩转深度学习
  7. java一行交换,在C / C ++,Python,PHP和Java中一行交换两个变量
  8. javascript 刷新div_用 JavaScript 实现寻路算法 —— 编程训练
  9. Hive大数据-认识Hive知识结构_以及概念介绍---大数据之Hive工作笔记0001
  10. LeetCode刷题(21)
  11. 【转】Asp.NET大文件上传组件开发总结(二)---提取文件内容
  12. 给网友良少的一封回信,希望能结识更多的志同道合者!
  13. 启发式算法、寻路算法A*算法
  14. lib包含# #pragma comment
  15. 地图导航软件测试大乐,敢问路在何方?四款手机地图软件横向测评!
  16. python读取邮箱邮件_Python读取我的outlook邮箱并分析邮件
  17. linux ssh互信配置
  18. 手游本地化不得不看的10条规则
  19. 通过Adblock关闭网站上百度分享的方法
  20. 席卷全球的大数据发展趋势需要什么?

热门文章

  1. ftp文件服务器备份,ftp文件服务器备份
  2. Fibonacci Additions (区间加优化)
  3. 记一段随风而逝的感情
  4. Linux系统管理命令之accton的使用
  5. [分享]来自CSDN的精华网址
  6. php编程输出心形图案_利用php输出不同的心形图案
  7. CityEngine学习资料——split分割
  8. 多元相关性分析_NAR:宏基因组网络分析工具MetagenoNets
  9. java mars2_Mars-java 2.2.2 发布,不需要容器的 Java Web 开发框架
  10. 在 JavaScript 中如何克隆对象?