iOS近期知识总结

1.APP之间通信方式有哪些

  • URL Scheme
    (在info.plist中配置LSApplicationQueriesSchemes,指定目标app的scheme,然后在目标app的info.plist中配置好URL types)场景:分享,支付等
  • Keychain
    本质是sqlite数据库,独立每个app的沙盒之外,即使删除app,keychain中的信息还存在
    特点:不同app之间需要通过keychain access groups才行,同一个teamID
    场景:用户登录信息保存,同一平台上多个app之间实现统一账户登录
  • UIPasteboard
    系统剪切板 场景:淘宝口令
  • UIDocumentInteractionController 系统级别,同设备上app之间的共享文档,以及文档预览,打印,发邮件和复制等
  • local socket
    app1在本地端口port1234建立socket tcp的bind和listen,APP2在同一端口port1234 发起tcp的connect连接
    场景:某个App1具有特殊的能力,比如能够跟硬件进行通信,在硬件上处理相关数据。而App2则没有这个能力,但是它能给App1提供相关的数据,这样APP2跟App1建立本地socket连接,传输数据到App1,然后App1在把数据传给硬件进行处理。
    缺陷:需要后台运行,因此,系统在任意时刻只有一个app在前台运行,那么就需要通信的另一方具备后台运行权限,类似导航,音乐,蓝牙通信类

2.json解析及第三方库分析

  • 苹果原生NSJSONSerialization
    原生有一些解析未进行容错处理
    //如:如何被解析的JSON数据如果既不是字典也不是数组(比如是NSString), 那么就必须使用这

    1
    2
    3
    NSJSONReadingAllowFragments
    NSData *data = [test dataUsingEncoding:NSUTF8StringEncoding]
    id obj = [NSJSONSerialization JSONObjectWithData: data options:NSJSONReadingAllowFragments error:nil];
  • SBJSON 、JSONModel
    字典转模型框架
    a.Mantle 需要继承自MTModel
    b.JSONModel 需要继承自JSONModel
    c.MJExtension 不需要继承,无代码侵入性(运行时)
    注意点:json解析中,jsonkit解析空数据时会变成NSNull类型,既不是nil也是string。解决方案可以使用NullSafe
    使用afnetwork时可以设置self.removesKeysWithNullValues = YES;

3.AFNetwork3.x源码解析

源文件简而言之:AFHTTPSessionManager -> 简单的HTTP请求封装
AFURLSessionManager -> 网络通信核心,负责调配NSURLSession启动网络任务,设置session代理的转发、操作回调线程执行、组装参数及解析参数的调配等
AFNetworkReachabilityManager -> 网络状态监控
AFSecurityPolicy -> 网络安全策略
AFURLRequestSerialization/AFURLResponseSerialization -> 数据序列化和反序列化
工具包AF UIKit -> 图片下载及缓存策略,网络请求时状态栏上的菊花出现时机等
追问:self.operationQueue.maxConcurrentOperationCount = 1 AF3.x中操作队列并发线程数设置为1的目的?

1)众所周知,AF2.x所有的回调是在一条线程,这条线程是AF的常驻线程,而这一条线程正是AF调度request的思想精髓所在,所以第一个目的就是为了和之前版本保持一致。
2)因为跟代理相关的一些操作AF都使用了NSLock。所以就算Queue的并发数设置为n,因为多线程回调,锁的等待,导致所提升的程序速度也并不明显。反而多task回调导致的多线程并发,平白浪费了部分性能。
而设置Queue的并发数为1,(注:这里虽然回调Queue的并发数为1,仍然会有不止一条线程,但是因为是串行回调,所以同一时间,只会有一条线程在操作AFUrlSessionManager的那些方法。)至少回调的事件,是不需要多线程并发的。回调没有了NSLock的等待时间,所以对时间并没有多大的影响。(注:但是还是会有多线程的操作的,因为设置刚开始调起请求的时候,是在主线程的,而回调则是串行分线程。)

做了什么:
1.各种请求方式request的拼接
2.对共用参数(session级别)和一些私用参数(task级别)的分离(代理转发的架构模式
3.自定义的https认证处理
4.请求到数据后,做了各种数据格式的解析,支持自定义解析
5.成功和失败的回调处理
6.提供了UIKit的扩展(图片下载、缓存展示,菊花展示)

4.野指针是什么,iOS 开发中什么情况下会有野指针?

野指针是不为 nil,但是指向已经被释放的内存的指针。
__unsafe_unretain或者assign的指针,对象释放后会出现野指针。
一般情况下oc使用了weak指针,在对象销毁时指针会置nil
Student *stu = [[Student alloc] init];
[stu setAge:10];
[stu release];这里已经释放内存
[stu setAge:10];—》报错

5.简述runtime机制

OC是运行时语言,其中最重要的是消息机制,实现动态调用。
执行某个方法,实际是在运行时向对象发消息。
这里涉及到iOS中NSObject的对象模型,Class isa指针,指向metaclass也就是静态的Class。一般一个Obj对象中的isa会指向普通的Class,这个Class中存储普通成员变量和对 象方法(“-”开头的方法),普通Class中的isa指针指向静态Class,静态Class中存储static类型成员变量和类方法(“+”开头的方 法)

  • 编译器将代码[obj makeText];转化为objc_msgSend(obj,@selector(makeText));
  • 正常消息发送:在objc_msgSend函数中,首先通过obj对象的isa指针获取它对应的class,优先在class的cache查找message方法,如果找不到再到methodLists查找;如果在class中没有找到,则到super class查找,一旦找到就执行它的实现IMP
  • 动态方法解析与消息转发:首先oc在运行时调用+resolveInstanceMethod:或+resolveClassMethod:方法,让你添加放到实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送过程。
  • 快速转发:如果目标对象实现-forwardingTargetForSelector:方法,系统就会在运行时调用这个方法,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理,否则就会继续Normal Fowarding
  • 正常转发:如果没有使用Fast Forwarding来消息转发,最后只有使用Normal Forwarding来进行消息转发。它首先调用methodSignatureForSelector:方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用-forwardInvocation:方法。
    场景:1.为@dynamic实现方法 2.代理模式实现 3.多重继承实现
    Objective-C 提供了 NSProxy 类可以用来做动态代理

6.runtime进阶理解—-category 和 class的载入过程

美团技术团队深入理解category

  • 使用场景:1.将类的实现分开在不同文件中 2.声明私有方法 3.模拟多继承 4.把framework的私有方法公开
  • 对比extension:1.extension是编译期决议,是类的一部分,一般用来隐藏类的私有信息;必须有一个类的源码才能为类添加extension 2.而category是运行期决议,无法添加实例变量(运行期,对象的内存布局已经确定,如果添加实例变量会破坏类的内部布局)
  • category的加载:1.category的方法没有“完全替换掉”原来类已经有的方法,如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA; 2.category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这就是category的方法会覆盖掉原来类的同名方法
  • category和load:1.附加category到类的工作先于+load方法的执行; 2.+load的执行顺序是先类,后category,而category的+load执行顺序是根据编译顺序决定; 3.虽然对于+load的执行顺序是这样,但是对于覆盖掉的方法,则会先找到最后一个编译的category里的对应方法。
  • category其实并不是完全替换掉原来类的同名方法,只是category在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法
  • category可以配合关联对象实现给类添加实例变量;其中所有的关联对象都由AssociationsManager管理;runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作。

7.https单向验证和双向验证过程

单向验证过程:整个https验证的流程了。简单总结一下:

就是用户发起请求,服务器响应后返回一个证书,证书中包含一些基本信息和公钥。
用户拿到证书后,去验证这个证书是否合法,不合法,则请求终止。
合法则生成一个随机数,作为对称加密的密钥,用服务器返回的公钥对这个随机数加密。然后返回给服务器。
服务器拿到加密后的随机数,利用私钥解密,然后再用解密后的随机数(对称密钥),把需要返回的数据加密,加密完成后数据传输给用户。
最后用户拿到加密的数据,用一开始的那个随机数(对称密钥),进行数据解密。整个单向验证过程完成。
https单向验证和双向验证
SSL协议即用到了对称加密也用到了非对称加密(公钥加密),在建立传输链路时,SSL首先对对称加密的密钥使用公钥进行非对称加密,链路建立好之后,SSL对传输内容使用对称加密。

对称加密 :速度高,可加密内容较大,用来加密会话过程中的消息
公钥加密 :加密速度较慢,但能提供更好的身份认证技术,用来加密对称加密的密钥

8.串行vs并发 同步vs异步 队列vs线程

参考文章:iOS并发编程之Operation Queues
串行与并发的主要区别:允许同时执行的任务数量

同步vs异步:主要区别在于是否等待操作执行完成,即是否阻塞当前线程

队列vs线程:iOS中存在2种队列,串行队列和并发队列;串行队列中一次只能执行一个任务,并发队列则允许多个任务同时执行;iOS系统使用这些队列来进行任务调度,根据调度任务的需要和系统当前的负载情况动态地创建和销毁线程,而不需要我们手动管理。

在其他许多语言中,为了提高应用的并发性,我们往往需要自行创建一个或多个额外的线程,并且手动地管理这些线程的生命周期,这本身就已经是一项非常具有挑战性的任务了。此外,对于一个应用来说,最优的线程个数会随着系统当前的负载和低层硬件的情况发生动态变化。因此,一个单独的应用想要实现一套正确的多线程解决方案就变成了一件几乎不可能完成的事情。而更糟糕的是,线程的同步机制大幅度地增加了应用的复杂性,并且还存在着不一定能够提高应用性能的风险。

在iOS中,与直接创建线程的方式不同,我们只需定义好要调度的任务,然后让系统帮我们去执行这些任务就可以了。
当然在以下三种场景下,我们应该直接使用线程:

  • 用线程以外的其他方式都不能实现我们的特定任务
  • 必须实时执行一个任务,因为虽然队列会尽可能快地执行我们提交的任务,但并不能保证实时性
  • 需要对在后台执行的任务有更多的可预测行为

gcd中注意项:dispatch_barrier_async和dispatch_barrier_sync异同

dispatch_barrier_sync和dispatch_barrier_async的共同点:
1、都会等待在它前面插入队列的任务(1、2、3)先执行完
2、都会等待他们自己的任务(0)执行完再执行后面的任务(4、5、6)
dispatch_barrier_sync和dispatch_barrier_async的不共同点:
在将任务插入到queue的时候,dispatch_barrier_sync需要等待自己的任务(0)结束之后才会继续程序,然后插入被写在它后面的任务(4、5、6),然后执行后面的任务
而dispatch_barrier_async将自己的任务(0)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(4、5、6)插入到queue。
所以,dispatch_barrier_async的不等待(异步)特性体现在将任务插入队列的过程,它的等待特性体现在任务真正执行的过程。

9.dispatch_barrier_async使用Barrier Task方法Dispatch Barrier解决多线程并发读写同一个资源发生死锁

GCD使用
Dispatch Barrier确保提交的闭包是指定队列中在特定时段唯一在执行的一个。在所有先于Dispatch Barrier的任务都完成的情况下这个闭包才开始执行。轮到这个闭包时barrier会执行这个闭包并且确保队列在此过程不会执行其它任务。闭包完成后队列恢复。需要注意dispatch_barrier_async只在自己创建的队列上有这种作用,在全局并发队列和串行队列上,效果和dispatch_sync一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//创建队列
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
//改变setter
- (void)setCount:(NSUInteger)count forKey:(NSString *)key
{
key = [key copy];
//确保所有barrier都是async异步的
dispatch_barrier_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = @(count);
}
});
}
- (void)dispatchBarrierAsyncDemo {
//防止文件读写冲突,可以创建一个串行队列,操作都在这个队列中进行,没有更新数据读用并行,写用串行。
dispatch_queue_t dataQueue = dispatch_queue_create("com.starming.gcddemo.dataqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dataQueue, ^{
[NSThread sleepForTimeInterval:2.f];
NSLog(@"read data 1");
});
dispatch_async(dataQueue, ^{
NSLog(@"read data 2");
});
//等待前面的都完成,在执行barrier后面的
dispatch_barrier_async(dataQueue, ^{
NSLog(@"write data 1");
[NSThread sleepForTimeInterval:1];
});
dispatch_async(dataQueue, ^{
[NSThread sleepForTimeInterval:1.f];
NSLog(@"read data 3");
});
dispatch_async(dataQueue, ^{
NSLog(@"read data 4");
});
}

10.内存管理中,dealloc和release的区别是什么; block的实现机制

release 使对象的内存计数-1;dealloc释放的时候调用,清除

僵尸对象:系统在回收对象时,可以不将其真的回收, 而是把它转化为僵尸对象,通过环境变量 NSZombieEnabled 可开启此功能系统会修改对象的 isa 指针,令其指向特殊的僵尸类, 从而使改对象变为僵尸对象.僵尸类能够相应所有的选择子, 相应方式为:打印一条包含消息内容及其接受者的消息,然后终止应用程序

大量对象创建时循环遍历导致内存峰值,可以使用autoreleasepool
以”自动释放池块”降低内存峰值

自动释放池排布在栈中, 对象收到 autorelease 消息后, 系统将其放入最顶端的池里要合理运用自动释放池, 可降低应用程序的内存封值
@autoreleasepool 这种新式写法能创建出更为轻便的自动释放池
常见的例子就是 下边的 加上@autoreleasepool应用程序在执行循环的时候内存峰值就会降低
如:

1
2
3
4
5
6
7
8
NSArray *dataArr = [NSArray array];
NSMutableArray *personArrM = [NSMutableArray array];
for (NSDictionary *recode in dataArr) {
@autoreleasepool{
LLPerson *person = [[LLPerson alloc]initWithRecode:recode];
[personArrM addObject:person];
}
}

block对象:块本身也是对象,在存放块对象内存区域中, 首个变量是指向 Class 对象的指针,该指针叫做 isa, 其余内存里含有块对象正常运转所需的各种信息, 在内存布局中,最重要的就是 invoke 变量,这就是函数指针,指向块的实现代码, 函数原型只要要接受一个 void 型的参数, 此参数代表块.刚才说过, *块其实就是一种代替函数指针的语法结构 , 原来使用函数指针是需要用不透明的 void 指针来传递状态 而改用块之后, 则可以把原来用标准 C 语言特性所编写的代码封装成简明且易用的接口.

descriptor 变量是指向结构体的指针, 每个块里都包含此结构体,其中声明了块对象的总体大小,还声明了 copy 和 dispose 这两个辅助函数所对象的函数指针, 辅助函数在拷贝及丢弃块对象时运行, 其中会执行一些操作, 比方说 前者要保留捕获的对象, 而后者则将之释放

块还会把它所捕获的所有变量都拷贝一份, 这些拷贝放在 descriptor 变量后边,捕获了多少变量,就要占据多少内存空间, 请注意, 拷贝的并不是对象变量,而是指向这些对象的指针变量, invoke 函数为何需要把块对象作为参数传进来呢? 原因就在于,执行块的时候 要从内存中把这些捕获到的变量读出来

相关阅读整理:iOS Block详解

iOS Block用法和实现原理

11.使用GCD实现高效代码加锁

同步派发:create串行同步队列,读写操作安排在同一个队列中,保证数据同步(都使用dispatch_sync)
异步派发: 写操作异步操作(dispatch_async),弊端:比之前还慢,因为异步派发时需要拷贝block块

优化:不使用串行队列,改为并发队列,并且使用栅栏(barrier)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)someString
{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString
{
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
})
}

12.KTVHTTPCache原理

唱吧播放mp4文件,avplayer+KTVHTTPCache缓存播放

KTVHTTPCache结构及工作流程图

工作流程描述

值得注意的几个点:
1.使用CocoaHTTPServer设置本地http代理服务器,hook到播放器资源加载请求
2.模块化设置,各个类职责单一明显
3.缓存策略中的分片加载数据,区分本地数据和网络数据,最小化网络下载请求
4.对于锁屏后server socket失效的处理方式:做URL映射时先去ping一下本地的server,如果ping不通,则重启本地server(注:这里ping本地server的逻辑实现可以看看,使用NSCondition和NSURLSessionDataTask配合调用实现

13.显示动画(uiview动画)与隐示动画(calayer动画)

  • UIView关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖-actionForLayer:forKey:方法,或者直接创建一个显式动画。
  • 对于单独存在的图层,我们可以通过实现图层的-actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐式动画。

你改变CALayer的一个可做动画的属性,它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值;这一切都是默认的行为,你不需要做额外的操作,这就是隐式动画。
隐式动画的原因是我们没有指定动画的类型,只是改变了一个属性,然后Core Animation自己决定怎样去做动画,何时去做动画。

系统什么时候处理隐式动画:Core Animation在每个run loop周期中自动开始一次新的事务,任何在一次run loop循环中的属性的改变都会被集中起来,然后做一次0.25秒的动画。

要想理解隐式动画,需要清楚几个概念:事务(CATransaction)、完成块(+setCompletionlock:)、图层行为(actions)以及呈现与模型

呈现与模型:mvc模式,core animation相当于控制器,负责根据图层行为和事务的设置去不断的更新屏幕上这些属性的状态;calayer相当于模型,连接用户界面的虚构的类,存储了视图如何显示和动画的数据模型;呈现图层:模型图层的复制,呈现图层上的属性值代表了当前屏幕显示的外观效果的属性的值,可以使用-presentationLayer方法来获取当前屏幕上属性的真正显示的值。

理解离屏渲染:

当layer属性的混合体被指定在未预合成之前不能直接在屏幕中绘制时,屏幕外渲染就被唤醒。layer必须在被显示之前在一个屏幕外上下文中被渲染。
layer的以下属性将会触发屏幕外绘制:1.圆角(当和maskToBounds一起使用时) 2.图层蒙板 3.阴影

14.内联函数

1.inline函数避免了普通函数的,在汇编时必须调用call的缺点:取消了函数的参数压栈,减少了调用的开销,提高效率.所以执行速度确比一般函数的执行速度要快;
2.集成了宏的优点,使用时直接用代码替换(像宏一样);
3.避免了宏的缺点:需要预编译.因为inline内联函数也是函数,不需要预编译.
4.编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
5.可以使用所在类的保护成员及私有成员
inline
内联函数在iOS中的使用

  • 使用inline函数完全取代表达式形式的宏定义
  • 在内联函数内不允许用循环语句和 开关语句

如果方法的调用频率很大,那么可以考虑使用内联函数来提高性能

参考文章: