-
-
事件的传递
- 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中。
为什么是队列而不是栈?因为队列的特点是FIFO,即先进先出,先产生的事件先处理才符合常理,所以把事件添加到队列。
- UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)。
3.主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。
寻找最合适的View [hitTest:withEvent:] 过程
1.首先判断自己是否能接受触摸事件
2.判断触摸点是否在自己身上[pointInside:withEvent:]
3.子控件数组中从后往前遍历子控件,重复前面的两个步骤
4.如果没有符合条件的子控件,那么就认为自己最合适处理
不能接收触摸事件的三种情况:
不允许交互:userInteractionEnabled = NO、隐藏hidden、透明度小于等于0.01
@implementation WSWindow // 什么时候调用:只要事件一传递给一个控件,那么这个控件就会调用自己的这个方法 // 作用:寻找并返回最合适的view // UIApplication -> [UIWindow hitTest:withEvent:]寻找最合适的view告诉系统 // point:当前手指触摸的点 // point:是方法调用者坐标系上的点 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ // 1.判断下窗口能否接收事件 if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil; // 2.判断下点在不在窗口上 // 不在窗口上 if ([self pointInside:point withEvent:event] == NO) return nil; // 3.从后往前遍历子控件数组 int count = (int)self.subviews.count; for (int i = count - 1; i >= 0; i--) { // 获取子控件 UIView *childView = self.subviews[i]; // 坐标系的转换,把窗口上的点转换为子控件上的点 // 把自己控件上的点转换成子控件上的点 CGPoint childP = [self convertPoint:point toView:childView]; UIView *fitView = [childView hitTest:childP withEvent:event]; if (fitView) { // 如果能找到最合适的view return fitView; } } // 4.没有找到更合适的view,也就是没有比自己更合适的view return self; } // 作用:判断下传入过来的点在不在方法调用者的坐标系上 // point:是方法调用者坐标系上的点 //- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event //{ // return NO; //} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); } @end
- 事件的响应
1、如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
2、在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息[touches方法],则其将事件或消息传递给window对象进行处理
3、如果window对象也不处理,则其将事件或消息传递给UIApplication对象
4、如果UIApplication也不能处理该事件或消息,则将其丢弃
-
-
KVO 实现原理
1、当观察某对象A时,KVO动态机制会动态创建一个A类的子类NSKVONotifying_A,并且重写这个子类的setter方法、class方法、delloc、_isKVO方法
2、当修改对象A的属性时,会调用Foundation的NSSetXXXValueAndNotify函数。
willChangeValueForKey:
调用父类原来的setter方法
didChangeValueForKey
内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:) -
NotificationCenter实现原理
在NSNotificationCenter内部保存了两张表:一张保存添加观察者时传入了NotificationName的情况,一种保存添加观察者时没有传入NoficationName的情况。-
Named Table
在named table中,NotificationName作为表的key,但因注册观察者的时可传入一个object参数用于接收指定对象发出的通知,所以value是另一张表object为key,observers为value。又一个通知可注册多个观察者,所以observes是用链表实现的。 -
Nameless Table
Nameless Table比Named Table要简单很多,因为没有NotificationName作为key,直接用object作为key。相较于Named Table要少一层table嵌套。
-
Named Table
-
HTTPS的连接建立流程
- 客户端把TLS版本号、支持的加密算法和随机数C发送给服务器
- 服务器会把商定的加密算法、证书、随机数S发送给客户端
- 客户端会对服务器的证书进行验证。通过后会用服务器公钥来生成一个预主秘钥,并通过预主秘钥和随机数C、S来组装成会话秘钥
- 客户端会用服务器公钥将预主秘钥加密发送给服务器
- 服务器接收到加密信息后,用私钥解密得到预主秘钥。并通过预主秘钥和随机数C、S来组装会话秘钥。至此,服务器和客户端都已经知道了用于此次会话的秘钥。
- 客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。同理,服务器收到客户端发送来的密文,用服务器密钥对其进行对称解密,得到客户端发送的数据。
-
TCP三次握手
序列号seq:用来标记数据段的顺序
确认号ack:期待收到对方下一个报文段的第一个数据字节的序号
确认ACK:仅当ACK=1时,确认号字段才有效
同步SYN:连接建立时用于同步序号。
第一次握手:客户端进入SYN_SENT状态,并发送Syn消息给服务器,SYN标志位被置为1,同时会带上客户端分配好的Seq序列号
第二次握手:服务器在收到Syn消息后,会进入SYN_RCVD状态,同时返回Ack消息给客户端。这一步包含两部分,一是回复客户端的Syn消息,其中ACK=1,确认号设置为客户端的Seq值+1;另是主动发送服务器的Syn消息给客户端,SYN标志位被置1,Seq号是服务器对应的序列号
第三次握手:客户端在收到消息之后,首先会将状态变为ESTABLISHED,同时回复ACK消息给服务器。ACK状态被设置为1,确认号被设置成服务器的序列号+1。服务器在收到这个Ack消息之后,会进入ESTABLISHED状态,TCP的全双工连接建立完成。established:[ɪˈstæblɪʃt] -
TCP四次挥手
终止FIN:用来释放一个连接。
第一次挥手:客户端发出连接释放报文,并且停止发送数据。FIN=1,seq为客户端的序列号,客户端进入FIN-WAIT-1(终止等待1)状态。
第二次挥手:服务器收到连接释放报文,发出确认报文,ACK=1,确认号为客户端的Seq值+1。此时,服务端进入了CLOSE-WAIT(关闭等待)状态。客户端进入FIN_WAIT_2 (终止等待2)状态,继续等待服务器的连接释放报文。
第三次挥手:当服务器确定数据已发送完成,就向客户端发送连接释放报文,FIN=1,Seq号是服务器的序列号,服务器进入了LAST-ACK(最后确认)状态,等待客户端的确认。
第四次挥手:客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,确认号=服务器的序列号+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。经过2∗MSL(最长报文段寿命)的时间后,才进入CLOSED状态。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。 -
线程和进程的区别
1>进程是系统进行资源分配和调度的一个独立单位,线程是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。线程基本不拥有系统资源,拥有自己的栈空间,它与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程。同一个进程中的多个线程之间可以并发执行。
2>进程和线程的主要差别在于他们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,线程只是一个进程中的不同执行路径,有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,但在进程切换时,耗费资源较大,效率要差一些。 -
并行和并发的区别
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
它们最关键的点就是:是否是『同时』。
并发是轮流处理多个任务,并行是同时处理多个任务 -
RunLoop项目应用
- NSTimer滑动停止和子线程定时执行(performSelector:withObject:afterDelay)
- 创建常驻线程
- 监听应用卡顿睡眠之前和唤醒后的耗时
-
- 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
- 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。
- 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
-
- 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不可剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
-
Masonry实现动画
如果其约束还没有生成的时候需要动画的话,就请先强制刷新后才写动画,否则所有没生成的约束会直接跑动画。
因为布局的动画frame的变动是相对父视图的,所以父视图执行layoutIfNeed才会重新布局对应的UI。如果用view 执行layoutIfNeed只会从新布局view上子视图,不会产生动画的哟 -
mian函数之后启动优化
- 第一类,如崩溃、统计SDK等,必须第一时间启动,仍然把它留在 didFinishLaunchingWithOptions 里启动。
- 第二类,如推送、定位SDK、用户信息配置等,这些功能在用户进入 APP 主体的之前是必须要加载完的,把他放到广告页面启动。
- 第三类,如分享、登录、支付SDK等,由于启动时不是必须的,所以可以放在第一个界面的 viewDidAppear 方法里,这里完全不会影响到启动时间。
-
启动速度监控
- 定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时【Time Profiler】。定时间隔设置得长了,会漏掉一些方法,从而导致检查出来的耗时不精确;
而定时间隔设置得短了,抓取堆栈这个方法本身调用过多也会影响整体耗时,导致结果不准确。 - 对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时。在原方法开始执行时换成执行其他你指定的方法,或者在原有方法执行前后执行你指定的方法,来达到掌握和改变指定方法的目的。
- 定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时【Time Profiler】。定时间隔设置得长了,会漏掉一些方法,从而导致检查出来的耗时不精确;
-
Auto Layout的生命周期
Auto Layout有一整套布局引擎系统叫Layout Engine,用来统一管理布局的创建、更新和销毁。每个视图在得到自己的布局之前,Layout Engine会将视图、约束、优先级、固定大小通过计算转换成最终的大小和位置。当发生Constraints Change时,Layout Engine会重新计算布局,获取到布局后调用superview.setNeedLayout(),然后进入Deffered Layout Pass。Deffered Layout Pass主要作用是做容错处理,假如有些视图在更新约束时没有确定或缺失布局声明的话,会先在这里做容错处理。然后Layout Engine会从上到下调用layoutSubviews(),通过Cassowary算法计算子视图的位置,算出来后将子视图的frame从Layout Engine里拷贝出来,之后的流程就和手写布局一样完成绘制、渲染。1.Constraints Change包括添加、删除、更新、Activating、Deactivating、改变约束优先级。
2.总体流程就是Constraints Change ->Layout Engine重新计算布局->触发superview.setNeedLayout()->Deffered Layout Pass容错处理->触发layoutSubviews()->绘制、渲染
网友评论