14. RunLoop的基础知识
- RunLoop模式有哪些?
答 : iOS中有五种RunLoop模式
NSDefaultRunLoopMode (默认模式,有事件响应的时候,会阻塞旧事件)
NSRunLoopCommonModes (普通模式,不会影响任何事件)
UITrackingRunLoopMode (只能是有事件的时候才会响应的模式)
还有两种系统级别的模式
一个是app刚启动的时候会执行一次
另外一个是系统检测app各种事件的模式
- RunLoop的基本执行原理
答 : 原本系统就有一个runloop在检测App内部的行为或事件,当输入源(用户的直接或者间接的操作)有“执行操作”的时候, 系统的runloop会监听输入源的状态, 进而在系统内部做一些对应的相应操作。 处理完成后,会自动回到睡眠状态, 等待下一次被唤醒,
RunLoop和线程的关系
RunLoop的作用就是用来管理线程的, 当线程的RunLoop开启之后,线程就会在执行完成任务后,进入休眠状态,随时等待接收新的任务,而不是退出。
为什么只有主线程的
runloop
是开启的程序开启之后,要一直运行,不会退出。 说白了就是为了让程序不死
如何保证一个线程永远不死(常驻线程)
// 先创建一个线程用于测试
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(play) object:nil];
[thread start];
// 保证一个线程永远不死
[[NSRunLoop currentRunLoop] addPort:[NSPort port] -forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
// 在合适的地方处理线程的事件处理
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:NO];
15. weak属性?
1. 说说你理解weak属性?
1.实现weak后,为什么对象释放后会自动为nil?
runtime 对注册的类, 会进行布局,
对于 weak 对象会放入一个 hash 表中。
用 weak 指向的对象内存地址作为 key,
Value是weak指针的地址数组。
当释放的时候,其内部会通过当前的key找到所有的weak指针指向的数组
然后遍历这个数组把其中的数据设置为nil。
稍微详细的说:在内部底层源码也同时和当前对象相关联得SideTable, 其内部有三个属性, 一个是一把自旋锁,一个是引用计数器相关,一个是维护weak生命得属性得表
**SideTable**这个结构体一样的东西,可以花半个小时看一眼。
延伸
objc中向一个nil对象发送消息将会发生什么?
首先 在寻找对象化的isa指针时就是0地址返回了, 所以不会有任何错误, 也不会错误objc在向一个对象发送消息时,发生了什么?
- 首先是通过obj 的isa指针找到对应的class
- 先去操作对象中的缓存方法列表中objc_cache中去寻找 当前方法,如果找到就直接实现对应IMP
- 如果在缓存中找不到,则在class中找到对用的Method list中对用foo
- 如果class中没有找到对应的foo, 就会去superClass中去找
- 如果找到了对应的foo, 就会实现foo对应的IMP
缓存方法列表, 就是每次执行这个方法的时候都会做如此繁琐的操作这样太过于消耗性能,所以出现了一个objc_cache,这个会把当前调用过的类中的方法做一个缓存, 当前method_name作为key, method_IMP作为Value,当再一次接收到消息的时候,直接通过objc_cache去找到对应的foo的IMP即可, 避免每一次都去遍历objc_method_list
如果一直没有找到方法, 就会专用消息转发机制,机制如下
// 动态方法解析和转发
上面的例子如果foo函数一直没有被找到,通常情况下,会出现报错,但是在报错之前,OC的运行时给了我们三次补救的机会
- Method resolution
- Fast forwarding
- Normal forwarding
1. Runtime 会发送 +resolveInstanceMethod: 或者 +resolveClassMethod: 尝试去 resolve(重启) 这个消息;
2. 如果 resolve 方法返回 NO,Runtime 就发送 -forwardingTargetForSelector: 允许你把这个消息转发给另一个对象;
3. 如果没有新的目标对象返回, Runtime 就会发送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。你可以发送 -invokeWithTarget: 消息来手动转发消息或者发送 -doesNotRecognizeSelector: 抛出异常。
消息转发详情如下图:
[图片上传失败...(image-3df667-1596096855494)]
16.UIView和CALayer是什么关系?
- 两者最明显的区别是 View可以接受并处理事件,而 Layer 不可以;
- 每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,并且 UIView 的尺寸样式都由内部的 Layer 所提供。两者都有树状层级结构,layer 内部有 SubLayers,View 内部有 SubViews.但是 Layer 比 View 多了个AnchorPoint
- 在 View显示的时候,UIView 做为 Layer 的 CALayerDelegate,View 的显示内容由内部的 CALayer 的 display
CALayer 是默认修改属性支持隐式动画的,在给 UIView 的 Layer 做动画的时候,View 作为 Layer 的代理,Layer 通过 actionForLayer:forKey:向 View请求相应的 action(动画行为)
- layer 内部维护着三分 layer tree,分别是 presentLayer Tree(动画树),modeLayer Tree(模型树), Render Tree (渲染树),在做 iOS动画的时候,我们修改动画的属性,在动画的其实是 Layer 的 presentLayer的属性值,而最终展示在界面上的其实是提供 View的modelLayer
16. @synthesize 和 @dynamic 分别有什么作用
- @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
- @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
- @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
17. static有什么作用?
static关键字可以修饰函数和变量,作用如下:
**隐藏**
通过static修饰的函数或者变量,在该文件中,所有位于这条语句之后的函数都可以访问,而其他文件中的方法和函数则不行
**静态变量**
类方法不可以访问实例变量(函数),通过static修饰的实例变量(函数),可以被类 方法访问;
**持久**
static修饰的变量,能且只能被初始化一次;
**默认初始化**
static修饰的变量,默认初始化为0;
18. objc在向一个对象发送消息时,发生了什么?
- objc_msgSend(recicver, selecter..)
19. runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
1. runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。
2. runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。runloop在第一次获取时被创建,在线程结束时被销毁。
3. 对于主线程来说,runloop在程序一启动就默认创建好了。
4. 对于子线程来说, runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被开启,不然定时器不会回调。
20. 如何手动触发一个value的KVO
键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就
会记录旧的值。而当改变发生后, didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。
引申 0 如何给系统KVO设置筛选条件?
- 举例:取消Person类age属性的默认KVO,设置age大于18时,手动触发KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"age"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (void)setAge:(NSInteger)age {
if (age >= 18) {
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}else {
_age = age;
}
}
引申 1.通过KVC修改属性会触发KVO么?直接修改成员变量呢 ?
- 会触发KVO。即使没有声明属性,只有成员变量,只要accessInstanceVariablesDirectly返回的是YES,允许访问其成员变量,那么不管有没有调用setter方法,通过KVC修改成员变量的值,都能触发KVO。这也说明通过KVC内部实现了willChangeValueForKey:方法和didChangeValueForKey:方法
- 直接修改成员变量不会触发KVO。直接修改成员变量内部并没有做处理只是单纯的赋值,所以不会触发。
引申 kvc的底层实现?
- 赋值方法setValue:forKey:的原理
(1)首先会按照顺序依次查找setKey:方法和_setKey:方法,只要找到这两个方法当中的任何一个就直接传递参数,调用方法;
(2)如果没有找到setKey:和_setKey:方法,那么这个时候会查看accessInstanceVariablesDirectly方法的返回值,如果返回的是NO(也就是不允许直接访问成员变量),那么会调用setValue:forUndefineKey:方法,并抛出异常“NSUnknownKeyException”;
(3)如果accessInstanceVariablesDirectly方法返回的是YES,也就是说可以访问其成员变量,那么就会按照顺序依次查找 _key、_isKey、key、isKey这四个成员变量,如果查找到了,就直接赋值;如果依然没有查到,那么会调用setValue:forUndefineKey:方法,并抛出异常“NSUnknownKeyException”。
- 取值方法valueForKey:的原理
(1)首先会按照顺序依次查找getKey:、key、isKey、_key:这四个方法,只要找到这四个方法当中的任何一个就直接调用该方法;
(2)如果没有找到,那么这个时候会查看accessInstanceVariablesDirectly方法的返回值,如果返回的是NO(也就是不允许直接访问成员变量),那么会调用valueforUndefineKey:方法,并抛出异常“NSUnknownKeyException”;
(3)如果accessInstanceVariablesDirectly方法返回的是YES,也就是说可以访问其成员变量,那么就会按照顺序依次查找 _key、_isKey、key、isKey这四个成员变量,如果找到了,就直接取值;如果依然没有找到成员变量,那么会调用valueforUndefineKey方法,并抛出异常“NSUnknownKeyException”。
21. ViewController生命周期
按照执行顺序排列:
1. initWithCoder:通过nib文件初始化时触发。
2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
3. loadView:开始加载视图控制器自带的view。
4. viewDidLoad:视图控制器的view被加载完成。
5. viewWillAppear:视图控制器的view将要显示在window上。
6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9. viewDidAppear:视图控制器的view已经展示到window上。
10. viewWillDisappear:视图控制器的view将要从window上消失。
11. viewDidDisappear:视图控制器的view已经从window上消失。
22.网络协议
- TCP三次握手和四次挥手?
三次握手
1.客户端向服务端发起请求链接,首先发送SYN报文,SYN=1,seq=x,并且客户端进入SYN_SENT状态
2.服务端收到请求链接,服务端向客户端进行回复,并发送响应报文,SYN=1,seq=y,ACK=1,ack=x+1,并且服务端进入到SYN_RCVD状态
3.客户端收到确认报文后,向服务端发送确认报文,ACK=1,ack=y+1,此时客户端进入到ESTABLISHED,服务端收到用户端发送过来的确认报文后,也进入到ESTABLISHED状态,此时链接创建成功
- 哎!
- 嗯
- 给你
为什么需要三次握手:
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。假设这是一个早已失效的报文段,但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。
四次挥手
1.客户端向服务端发起关闭链接,并停止发送数据
2.服务端收到关闭链接的请求时,向客户端发送回应,我知道了,然后停止接收数据
3.当服务端发送数据结束之后,向客户端发起关闭链接,并停止发送数据
4.客户端收到关闭链接的请求时,向服务端发送回应,我知道了,然后停止接收数据
- 哎!
- 嗯
- 关了
- 好的
为什么需要四次挥手:
因为TCP是全双工通信的,在接收到客户端的关闭请求时,还可能在向客户端发送着数据,因此不能再回应关闭链接的请求时,同时发送关闭链接的请求
引申
-
HTTP和HTTPS有什么区别?
- HTTP协议是一种使用明文数据传输的网络协议。
- HTTPS协议可以理解为HTTP协议的升级,就是在HTTP的基础上增加了数据加密。在数据进行传输之前,对数据进行加密,然后再发送到服务器。这样,就算数据被第三者所截获,但是由于数据是加密的,所以你的个人信息让然是安全的。这就是HTTP和HTTPS的最大区别。
-
HTTPS的加密方式?
Https采用对称加密和非对称加密结合的方式来进行通信。
-
Https不是应用层的新协议,而是Http通信接口用SSL和TLS来加强加密和认证机制。
- 对称加密: 加密和解密都是同一个钥匙
- 非对称加密:密钥承兑出现,分为公钥和私钥,公钥加密需要私钥解密,私钥加密需要公钥解密
HTTP和HTTPS的建立连接的过程?
HTTP
- 建立链接完毕以后客户端会发送响应给服务器
- 服务端接受请求并且做出响应发送给客户端
- 客户端收到响应并且解析响应给客户
HTTPS
- 在使用HTTPS是需要保证服务端配置了正确的对应的安全证书
- 客户端发送请求到服务器
- 服务端返回公钥和证书到客户端
- 客户端接受后,会验证证书的安全性,如果通过则会随机生成一个随机数,用公钥对其解密, 发送到服务端
- 服务端接受到这个加密后的随机数后,会用私钥对其进行揭秘,得到真正的随机数,然后调用这个随机数当作私钥对需要发送的数据进行对称加密。
- 客户端接收到加密后的数据使用私钥(之前生成的随机值)对数据进行解密,并且解析数据呈现给客户
HTTP协议中GET和POST的区别
GET在特定的浏览器和服务器对URL的长度是有限制的。 但是理论上是没有限制的
POST不是通过URL进行传值,理论上不受限制。
GET会把请求参数拼接到URL后面, 不安全,
POST把参数放到请求体里面, 会比GET相对安全一点, 但是由于可以窥探数据, 所以也不安全, 想更安全用加密。
GET比POST的请求速度快。原因:Post请求的过程, 会现将请求头发送给服务器确认,然后才真正的发送数据, 而Get请求 过程会在链接建立后会将请求头和数据一起发送给服务器。 中间少了一步。 所以get比post 快
post的请求过程
三次握手之后 第三次会把post请求头发送
服务器返回100 continue响应
浏览器开始发送数据
服务器返回200 ok响应
- get请求过程
- 三次握手之后 第三次会发送get请求头和数据
- 服务器返回200 ok响应