欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS于海老师课程观看笔记(一)---UI视图相关

程序员文章站 2022-04-13 23:13:01
...

简介:

于海老师的《资深大牛带你深度剖析ios面试》算是看完了,于海老师思路清晰、语言流畅、把握要点,把很多难点问题可以很好的讲通,还有很多偏角旮旯自己不知道或没有掌握的知识也是从这上面学习到的,看的人有种拍案叫绝的感觉。整体来看,这套课程是难得的精品课程。
当然,也有不足之处,所讲知识都是直戳要害,没有进行发散,对新手不是很友好,不过也是,这门课本身就是面向中高级iOS程序员的。
有些地方自己知道的,可以加深了解,串联知识点。
有些地方自己并没听说过,自己想弄明白需要下功夫查些资料。
看完,不等于学完,不做笔记,俩月、半年后不看,还是会忘记。
短短15小时课程,做笔记的话,每一节课的知识量都是很多,因此,自己花时间对视频课内容进行整理,与君共勉。


UI视图部分

iOS于海老师课程观看笔记(一)---UI视图相关

1.UITableView

1.1 重用机制

iOS于海老师课程观看笔记(一)---UI视图相关

屏幕向上滑动,A1消失,A7即将出现,则A7可利用A1所在的内存地址,从而达到重用机制。

1.2 数据源同步

iOS于海老师课程观看笔记(一)---UI视图相关

在一个tableView列表中,在主线程进行了删除操作,而在子线程进行了加载更多操作。如此一来,可能会造成数据源对照不上问题。

问题是,我觉得loadMore并不会造成数据源问题。
比如原有数据10条,存储在一个SourceMuttableArray里面
删除第2条数据,是对原有数据进行操作。
loadMore是加载更多数据,假入加载了新的10条数据,加载完毕后会放入SourceMuttableArray里面。
虽然是对同一个数组进行操作,但是老数据与新数据并没有相互影响。

此处如若改成reloadData刷新数据比较恰当。
如果是刷新操作,老数据删除,然而新的请求并不知道,刷新后被删除的数据又出现了,从而出现问题。

解决方案一:并发访问、数据拷贝

iOS于海老师课程观看笔记(一)---UI视图相关

在主线程删除操作的时候,进行记录删除操作,然后在子线程数据返回的时候,再同步删除操作。从而达到数据同步。
这种,相当于手动将从服务器数据加载到本地的数据进行了修改。

大致相当于这种操作:

-void)DeleteData
{
	BOOL isHaveDeleteData = YES;
	int c = indexPath.row;
}

- (void)reloadData
{
	//数据请求下来了,存储在临时数组tempDataArray
	if(isHaveDeleteData){
		[self.tempDataArray removeObjectAtIndex:tempDataArray];
		self.sourceDataArray = [NSMuttableArray arrayWithArray:self.tempDataArray];
	}
}

解决方案二:串行访问
iOS于海老师课程观看笔记(一)---UI视图相关

利用GCD,创建串行队列,将子线程的返回的数据与主线程的删除数据操作,都放在串行队列中。


UIView和CALayer

iOS于海老师课程观看笔记(一)---UI视图相关

UIView里面的layer属性,其实就是CALayer类型。
CALayer里面有一个contents,负责显示
contents里面有一个backing store是一个bitmap位图,最终进行显示。

UIView为CALayer提供显示的内容,以及负责处理触摸等事件,参与响应链
CALayer通过contents负责显示内容

该机制体现了设计原则中的:单一设计原则

事件传递与视图响应链

问:当用户点击了C2位置,是怎样一个事件传递过程以及响铃过程?

iOS于海老师课程观看笔记(一)---UI视图相关
事件传递,有两个关键的方法:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    判断哪一个视图响应,就返回哪一个UIView
    若当前视图无法响应事件,则返回nil
	若当前视图可以响应事件,但无子视图可以响应事件,则返回自身作为当前视图层次中的事件响应者
	若当前视图可以响应事件,同时有子视图可以响应,则返回子视图层次中的事件响应者
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    判断触摸点是否在当前View上
}

事件传递流程图
iOS于海老师课程观看笔记(一)---UI视图相关

– (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event的内部实现图
iOS于海老师课程观看笔记(一)---UI视图相关
用代码表现出来大致是:
iOS于海老师课程观看笔记(一)---UI视图相关

问:如何将一个方形的Button,只有圆形区域可以响应点击事件,而圆形外部的区域无法响应点击事件?

iOS于海老师课程观看笔记(一)---UI视图相关
iOS于海老师课程观看笔记(一)---UI视图相关

老师程序中还重写了- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event方法,感觉没必要,只重新pointInside即可满足

事件响应链流程

iOS于海老师课程观看笔记(一)---UI视图相关
UIView继承UIResponder,UIResponder里面有四个响应方法

//手指触碰屏幕,触摸开始
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指在屏幕上移动
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指离开屏幕,触摸结束
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//触摸结束前,某个系统事件中断了触摸,例如电话呼入
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

UIView的继承关系
UIView > UIResponder > NSObject

UIButton的继承关系
UIButton > UIControl > UIView > UIResponder > NSObject

UIControl里面有四个方法:

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event; // touch is sometimes nil if cancelTracking calls through to this.
- (void)cancelTrackingWithEvent:(nullable UIEvent *)event; 

iOS于海老师课程观看笔记(一)---UI视图相关

事件响应链

问:如果最后到AppDelegate都没有接收事件,会发生什么?

该事件会被抛弃,什么也不会发生。


问UIButton添加一个addTarget事件(UIControl),一个手势识别器UIGestureRecognizer,还有UIResponder下面的touchBegin监听,UIButton会如何响应?

也就是UIGestureRecognizer、UIResponder、UIControl三者的关系

首先,我们先了解几个知识点:


系统类的UIControl(UIButton、UISwitch等)的响应优先级比其父视图上的手势识别器高
而自定义的UIControl,响应优先级比其父视图上的手势识别器低。

意思就是,如果一个UIView有一个手势识别器事件,UIView上有一个UIButton,UIButton有一个addTarget事件,点击UIButton,UIButton会先响应addTarget事件。

自定义YZButton,在里面做如下操作:

#import "YZButton.h"

@implementation YZButton
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    NSLog(@"%s", __func__);
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    NSLog(@"%s", __func__);
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    NSLog(@"%s", __func__);
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];
    NSLog(@"%s", __func__);
}

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event
{
    NSLog(@"%s", __func__);
    return [super beginTrackingWithTouch:touch withEvent:event];
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event
{
    NSLog(@"%s", __func__);
    return [super continueTrackingWithTouch:touch withEvent:event];
}

- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event
{
    [super endTrackingWithTouch:touch withEvent:event];
    NSLog(@"%s", __func__);
}

- (void)cancelTrackingWithEvent:(nullable UIEvent *)event
{
    [super cancelTrackingWithEvent:event];
    NSLog(@"%s", __func__);
}
@end

然后

YZButton *btn = [[YZButton alloc] init];
btn.frame = CGRectMake(0, 0, 200, 200);
btn.backgroundColor = [UIColor redColor];
[self.view addSubview:btn];

[btn addTarget:self action:@selector(actionClick) forControlEvents:UIControlEventTouchUpInside];

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapClick)];
[self.view addGestureRecognizer:tap];

打印结果:
-[YZButton beginTrackingWithTouch:withEvent:]
-[YZButton touchesBegan:withEvent:]
-[YZButton endTrackingWithTouch:withEvent:]
actionClick
-[YZButton touchesEnded:withEvent:]

手势识别器UIGestureRecognizer比UIResponder具有更高的事件响应优先级,也就是
手势识别器UIGestureRecognizer > UIResponder(touchBegin)

YZButton *btn = [[YZButton alloc] init];
btn.frame = CGRectMake(0, 0, 200, 200);
btn.backgroundColor = [UIColor redColor];
[self.view addSubview:btn];

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapClick)];
[btn addGestureRecognizer:tap];

打印结果:
-[YZButton beginTrackingWithTouch:withEvent:]
-[YZButton touchesBegan:withEvent:]
tapClick
-[YZButton cancelTrackingWithEvent:]
-[YZButton touchesCancelled:withEvent:]

怎么从结果发现,好像是UIResponder比UIGestureRecognizer先调用呢???
我们搞一个自定义的YZTapGestureRecognizer,其继承UITapGestureRecognizer

#import "YZTapGestureRecognizer.h"

@implementation YZTapGestureRecognizer
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    NSLog(@"%s", __func__);
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    NSLog(@"%s", __func__);
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    NSLog(@"%s", __func__);
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];
    NSLog(@"%s", __func__);
}
@end

然后,执行

YZButton *btn = [[YZButton alloc] init];
btn.frame = CGRectMake(0, 0, 200, 200);
btn.backgroundColor = [UIColor redColor];
[self.view addSubview:btn];

YZTapGestureRecognizer *tap = [[YZTapGestureRecognizer alloc] initWithTarget:self action:@selector(tapClick)];
[btn addGestureRecognizer:tap];

打印结果:
-[YZTapGestureRecognizer touchesBegan:withEvent:]
-[YZButton beginTrackingWithTouch:withEvent:]
-[YZButton touchesBegan:withEvent:]
-[YZTapGestureRecognizer touchesEnded:withEvent:]
tapClick
-[YZButton cancelTrackingWithEvent:]
-[YZButton touchesCancelled:withEvent:]

从结果可以看出,确实是
手势识别器UIGestureRecognizer > UIResponder(touchBegin)


重点来了

三个方法全部打开

YZButton *btn = [[YZButton alloc] init];
btn.frame = CGRectMake(0, 0, 200, 200);
btn.backgroundColor = [UIColor redColor];
[self.view addSubview:btn];

[btn addTarget:self action:@selector(actionClick) forControlEvents:UIControlEventTouchUpInside];

YZTapGestureRecognizer *tap = [[YZTapGestureRecognizer alloc] initWithTarget:self action:@selector(tapClick)];
[btn addGestureRecognizer:tap];

打印结果:
-[YZTapGestureRecognizer touchesBegan:withEvent:]
-[YZButton beginTrackingWithTouch:withEvent:]
-[YZButton touchesBegan:withEvent:]
-[YZTapGestureRecognizer touchesEnded:withEvent:]
tapClick
-[YZButton cancelTrackingWithEvent:]
-[YZButton touchesCancelled:withEvent:]

结果分析:
首先是UIGestureRecognizer的手势识别器先接收事件
然后是UIResponder的touchBegin
由于[YZButton cancelTrackingWithEvent:]和[YZButton touchesCancelled:withEvent:]被调用,等于YZButton已经取消事件的接收,也就不会触发UIController的actionClick方法
也就是说:手势识别器调用完毕后,直接调用了touchesCancelled,使得actionClick不会触发

手势识别器UIGestureRecognizer > UIResponder(touchBegin)>UIController

更多学习有关事件传递与响应链
iOS触摸事件全家桶


图像显示原理

iOS于海老师课程观看笔记(一)---UI视图相关
iOS于海老师课程观看笔记(一)---UI视图相关
iOS于海老师课程观看笔记(一)---UI视图相关
Layout:UI布局、文本计算
Display:绘制,drawRect方法
Prepare:图片编解码
Commit:提交位图

iOS于海老师课程观看笔记(一)---UI视图相关


UI卡顿、掉帧的原因

iOS于海老师课程观看笔记(一)---UI视图相关

在规定时间内(16.7ms)内,CPU和GPU没有做好准备,在VSync来临时,图片不能显示,造成卡顿、掉帧
人的大脑眼睛在1秒钟内接收至少60帧的画面,就是流畅的效果,也就是60FPS
1s/60 = 1000ms/60 约等于16.7ms

滑动优化方案

可以分别从CPU和GPU两方面着手

CPU
对象创建、跳转、销毁。放在子线程
预排版(布局计算、文本计算)放在子线程
预渲染(文本等异步绘制、图片编解码等)

GPU:
纹理渲染:减少离屏渲染
视图混合:尽可能的减少多层的View混合

UIView的绘制原理

iOS于海老师课程观看笔记(一)---UI视图相关

[UIView setNeedsDisplay]这行代码执行调用,并不会立马进行UI绘制

[UIView setNeedsDisplay]会立刻调用layer的同名函数setNeedsDisplay

在当前runloop即将进行休眠的时候(kCFRunLoopBeforeWaiting),setNeedsDisplay才真正调用[CALayer display],然后进行UIView的绘制

接下来,我们看下系统绘制流程异步绘制流程
系统绘制流程
iOS于海老师课程观看笔记(一)---UI视图相关

异步绘制流程
异步绘制,就是异步在画布上绘制内容。
在子线程中绘制Core Graphic对象,最后再回到主线程中设置layer.contents内容。
iOS于海老师课程观看笔记(一)---UI视图相关iOS于海老师课程观看笔记(一)---UI视图相关


离屏渲染

iOS于海老师课程观看笔记(一)---UI视图相关

离屏渲染何时触发?

1圆角:
view.layer.cornerRadius = 5.0;和view.layer.masksToBounds = YES;必须同时执行才会触发。

2图层蒙版
3阴影
4光栅化
光栅化概念:将图转化为一个个栅格组成的图象。
光栅化特点:每个元素对应帧缓冲区中的一像素。

为何要避免离屏渲染

离屏渲染会增加GPU的工作量,使得GPU处理时间变长,从而使得在屏幕显示中16.7ms中没有完成工作,造成UI卡顿、掉帧现象。
增加的工作量包括:
创建新的渲染缓冲区
上下文切换


layoutsubviews在什么时候调用

一般baidu出来的答案如下,然而说明并不够透彻,在此补充说明:
1、init初始化不会触发layoutSubviews
2、addSubview会触发layoutSubviews
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化,xy改动不管,wh改动管
4、滚动一个UIScrollView会触发layoutSubviews
5、旋转Screen会触发父UIView上的layoutSubviews事件
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件

首先:layoutSubviews 字面意思是: 布局子控件,也就是说改变子控件会调用父类该方法;
1、init初始化不会触发layoutSubviews,
这点确实不会调用;
2、addSubview会触发layoutSubviews,
如果添加的子控件没有Frame,不会调用;
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化;
还有一个前提是该View 已经被添加到父控件, 此时View和其父控件的layoutSubviews都会调用;
也就包含了6 的情况
4、滚动一个UIScrollView会触发layoutSubviews ,因滚动UIScrollView,其子控件肯定对应会刷新,也就肯定会被调用;
这点会调用;
5、旋转Screen会触发控制器对应UIView上的layoutSubviews事件
做一点更正;
总结:改变子控件就会调用父类的方法;

参考:
layoutSubviews 调用时机