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

简介:

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

在这里插入图片描述
系统的UI传递机制是怎样的?
KVO的实现原理是怎样的?
简单说说消息传递机制和消息转发流程
当一个obj废弃的时候,指向它的weak指针为何会自动置位nil?
iOS如何进行内存管理的?
Block的实质是怎样的?使用Block为何容易产生循环引用?
简单说说怎样利用GCD实现高效的多读单写逻辑?
RunLoop为何能做到有事做事,没事休息?
怎样解决DNS劫持?
分别说说什么是桥接模式、责任链模式?
怎样设计一个图片缓存框架?
怎样设计一个网络框架?AFNetworing
请编写一个算法,查找一个字符串中,第一个只出现一次的字符。
AFNetworing大致是怎样实现的?


UI视图部分

在这里插入图片描述

1.UITableView

1.1 重用机制

在这里插入图片描述

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

1.2 数据源同步

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

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

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

比如:
下拉刷新,请求时长10s后给新数据
删除广告cell
10s后,新数据回来,删除的广告cell又回来了

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

在这里插入图片描述

有必要做数据拷贝的操作吗?

如果是刷新操作,我们会做[dataSourceArray removeAllObject];操作,拷贝数据的意义在哪?
如果是加载更多操作,数据加载更多的数据请求传入的值是page,跟原有数据没关
即使数据回来之后,将新请求的数据拼接到原有数据中后面,一般也是在success回调的主线程的中做的,也就是,子线程请求数据回来,对数据的操作在主线程,删除数据也是在主线程,那么,好像并不存在没删掉的情况

删除操作还是要记录的

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

大致相当于这种操作:

@property (assign, nonatomic) BOOL isHaveDeleteData;
@property (assign, nonatomic) NSInteger deleteRow;

-void)DeleteData
{
	self.isHaveDeleteData = YES;
	self.deleteRow = indexPath.row;
}

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

解决方案二:串行访问
在这里插入图片描述
利用GCD,创建串行队列,将子线程的返回的数据与主线程的删除数据操作,都放在串行队列中。
大概类似于:

- (void)testGCD
{
    dispatch_queue_t serialQueue = dispatch_queue_create(@"serialQueue", DISPATCH_QUEUE_SERIAL);
    
    //子线程数据请求
    [self loadMoreDataWithQueue:serialQueue];
    
    //做删除操作,放入队列里面
    dispatch_async(serialQueue, ^{
        //删除某一行数据操作
        //可以记录 删除的行数 或者 广告标识 或者 删除后的整体数据
    });
}

- (void)loadMoreDataWithQueue:(dispatch_queue_t)serialQueue
{
    //数据请求操作
    
    //假如10s后执行成功回调
    //在数据请求回调里面:做下面的操作
    dispatch_async(serialQueue, ^{
        //数据同步操作
        //因为之前已经有删除数据的记录,所以会将新数据进行同步
    });
}

按照老师视频课所讲,两种方法都有一些缺点:
并发访问,数据拷贝的方法:如果需要删除的数据很多的话,在子线程中会不断的拷贝数据,记录删除操作,会消耗大量的内存空间。

问题是,并不需要copy操作,因此,感觉没这个缺点

而串行访问的话:就是主线程在进行删除等操作的时候,需要等待子线程的耗时操作完成之后再刷新UI,在界面显示的就是数据没有及时加载出来,用户体验度会有所降低。

为啥要等子线程耗时操作完再刷新呢?
一般删除操作,都是本地删除,然后直接刷新数据,当然,删除操作会传给后台,但并不需要等删除操作请求成功后再刷新UI
那么
用户下拉刷新
删除操作,刷新UI
数据请求成功,同步数据,显示结果
如果有耗时的话,也是下拉刷新的耗时,跟本次所说的线程同步无关


UIView和CALayer

在这里插入图片描述

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

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

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

事件传递与视图响应链

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

在这里插入图片描述
事件传递,有两个关键的方法:

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

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

事件传递流程图
在这里插入图片描述

– (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event的内部实现图
在这里插入图片描述
用代码表现出来大致是:
在这里插入图片描述

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

在这里插入图片描述
在UIButton中,重写-pointInside:withEvent:方法
在这里插入图片描述

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

在这个基础上,被问过扩展型的问题:

有一个UIView(A),上面有一个UIButton(b),UIButton的大小和UIView的大小一样,现在,在UIButton中重写pointInside:withEvent:,将其点击区域扩大2倍,问,该方法能否实现想要的效果?

答:不可以

点击后,点击从上往下找合适的View,当点击是在UIView(A)上,最后由UIButton接收事件,响应。
当点击点是在UIButton的外部,其实也是UIView(A)的外部,此时UIView(A)里面的pointInside:withEvent:返回NO,即,触摸点没有在UIView(A)上面,也就不会UIView(A)里面的子类了,也就是不会走UIButton的重新后的pointInside:withEvent:。

也就是,触摸点不在父类上,则不会执行到子类。因此,重新子类pointInside:withEvent:方法也没有用。


2020-11-30更新

孟浪了,以上答案错误
正确答案是: 可以实现将UIButton扩大点击区域

做了两个实验:
首先,UIView大小为(100, 100, 100, 100),UIButton加到UIView上,大小为(0, 0, 50, 50),通过重写UIButton的-(BOOL)pointInside:withEvent:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *redView = [[UIView alloc] init];
    redView.frame = CGRectMake(100, 100, 100, 100);
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
    
    YZButton *btn = [[YZButton alloc] init];
    btn.frame = CGRectMake(0, 0, 50, 50);
    btn.backgroundColor = [UIColor yellowColor];
    [btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
    [redView addSubview:btn];
}

#import "YZButton.h"
@implementation YZButton
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    //获取自己的bounds
    CGRect bounds = self.bounds;
    //扩大原热区直径至100,可以暴露个接口,用来设置需要扩大的半径。
    CGFloat widthDelta = MAX(100, 0);
    CGFloat heightDelta = MAX(100, 0);
    //CGRectInset,负数是向外,正数是向里
    bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
    //某一点是否在某一区域上
    return CGRectContainsPoint(bounds, point);
}
@end

在这里插入图片描述

点击红色区域,有打印:

2020-11-30 10:09:32.393002+0800 test001[1807:50672] -[ViewController btnClick]

这个容易理解,UIButton的点击区域扩大了,扩大到了父控件的宽高

现在,将UIButton的frame变为UIView的宽高,也就是都是(100, 100, 100, 100),此时,按照题意,要将UIButton的点击区域扩大2倍,也就是(200, 200)

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *redView = [[UIView alloc] init];
    redView.frame = CGRectMake(100, 100, 100, 100);
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
    
    YZButton *btn = [[YZButton alloc] init];
    btn.frame = CGRectMake(0, 0, 100, 100);
    btn.backgroundColor = [UIColor yellowColor];
    [btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
    [redView addSubview:btn];
}


#import "YZButton.h"
@implementation YZButton
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    
    CGRect bounds = self.bounds;
    //扩大原热区直径至200,可以暴露个接口,用来设置需要扩大的半径。
    CGFloat widthDelta = MAX(200, 0);
    CGFloat heightDelta = MAX(200, 0);
    bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
    return CGRectContainsPoint(bounds, point);
}
@end

在这里插入图片描述

此时,点击UIButton外部的区域,是没有点击事件的,原因就是那个错误答案,点击point超出其父控件的点击区域,因此,不能响应事件。

然而,既然UIButton可以扩大点击区域,UIView也可以扩大点击区域,因此,重写UIView的-(BOOL)pointInside:withEvent:方法:

#import "YZView.h"
@implementation YZView
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGRect bounds = self.bounds;
    //扩大原热区直径至200,可以暴露个接口,用来设置需要扩大的半径。
    CGFloat widthDelta = MAX(200, 0);
    CGFloat heightDelta = MAX(200, 0);
    bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
    return CGRectContainsPoint(bounds, point);
}
@end

神奇的一幕出现了,点击UIButton的外部区域,也可以打印

总结:

之前的错误答案在于,只想到了父控件不能响应事件,没有想到可以同时扩大父控件的点击区域。因此,可以通过-(BOOL)pointInside:withEvent:方法来扩大UIButton的点击区域,只需要将其父控件也扩大即可。


事件响应链流程

响应者对象(UIResponder)

学习触摸事件首先要了解一个比较重要的概念-响应者对象(UIResponder)。

在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接受并处理事件,我们称之为“响应者对象”。
在这里插入图片描述
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 : UIResponder : NSObject
UIViewController : UIResponder
UIWindow : UIView//UIWindow也可以显示,因此继承UIView也正常
UIApplication : UIResponder

UIApplication和UIViewController继承UIResponder也对,因为他们都参与响应链

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; 

在这里插入图片描述

事件响应链

事件的产生和传递

  • 发送触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中
  • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
  • 主窗口会在视图层次结构中,找到一个最合适的视图来处理触摸事件
  • 找到合适的视图控制后,就会调用视图空间的touches方法来作具体的事件处理

触摸事件的传递是从父控件传递到子控件
也就是UIApplication->window->寻找处理事件最合适的view

事件的响应

响应者链的事件传递过程

如果当前view(第一响应者)可以处理,则当前view处理touch时间
如果当前view不可以处理,则传递给其父视图
循环给父视图处理,直至到控制器,以及到UIApplication处理

  1. 如果当前view是控制器的view,则传给控制器;如果当前view不是控制器的view,则传递给该view的父视图
  2. 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
  3. 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
  4. 如果UIApplication也不能处理该事件或消息,则给UIApplicationDelegate
  5. 如果UIApplicationDelegate也不能处理,将其丢弃

事件传递 从UIApplication出发
视图响应链 到UIApplicationDelagate结束

谁是上一个响应者?
  1. 如果当前这个view是控制器的view,那么控制器就是上一个响应者
  2. 如果当前这个view不是控制器的view,那么其父控制器就是上一个响应者
问:如何通过一个view,找到它所属的控制器UIController?

这道题一开始想的是,调用view.superView,最终可以找到UIController所属的view,现在的问题就是,如何通过UIController的所属view,找到其控制器。因为view是UIController的一个属性,因此,也可以理解为:如何通过属性,找到类对象

但是,结合响应链的传递,以及事件处理传递,我们可以知道,通过响应链,可以根据view找到所属控制器,然后调用控制器的某些方法,去处理事情。
也就是UIView继承的UIResponder跟控制器是否存在某种关系?

响应链中的事件传递规则:
每一个响应者对象(UIResponder对象)都有一个 nextResponder 方法,用于获取响应链中当前对象的下一个响应者。因此,一旦事件的最佳响应者确定了,这个事件所处的响应链就确定了。

对于响应者对象,默认的 nextResponder 实现如下:

  • UIView 若视图是控制器的根视图,则其nextResponder为控制器对象;否则,其nextResponder为父视图。
  • UIViewController 若控制器的视图是window的根视图,则其nextResponder为窗口对象;若控制器是从别的控制器present出来的,则其nextResponder为presenting view controller。
  • UIWindow nextResponder为UIApplication对象。
  • UIApplication 若当前应用的app delegate是一个UIResponder对象,且不是UIView、UIViewController或app本身,则UIApplication的nextResponder为app delegate。

根据第一条,UIView的nextResponder要不是控制器,要不是superView
因此,可以得出以下代码:

- (UIViewController *)findViewController:(UIView *)sourceView
{
    id target=sourceView;
    while (target) {
        target = ((UIResponder *)target).nextResponder;
        if ([target isKindOfClass:[UIViewController class]]) {
            break;
        }
    }
    return target;
}

参考:
通过UIView获取UIViewController
iOS 触摸事件 hitTest touches nextResponder


问:如果最后到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触摸事件全家桶


图像显示原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Layout:UI布局、文本计算
Display:绘制,drawRect方法
Prepare:图片编解码
Commit:提交位图

在这里插入图片描述

顶点着色器就是处理顶点相关的信息,片段着色器就是处理画面的颜色信息。


UI卡顿、掉帧的原因

在这里插入图片描述

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

滑动优化方案

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

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

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

UIView的绘制原理

在这里插入图片描述

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

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

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

接下来,我们看下系统绘制流程异步绘制流程
系统绘制流程
在这里插入图片描述

异步绘制流程
异步绘制,就是异步在画布上绘制内容。
在子线程中绘制Core Graphic对象,最后再回到主线程中设置layer.contents内容。
在这里插入图片描述在这里插入图片描述


离屏渲染

在这里插入图片描述

离屏渲染何时触发?

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 调用时机

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值