iOS视图绘制流程

225 阅读2分钟

不管我们是通过UIView间接创建的还是通过CALayer直接创建的layer对象,最终都会调用display方法完成绘制,绘制的时机是通过runloop观察者回调统一处理的,当然我们也可以通过调用setNeedsDisplay以及displayIfNeeded主动绘制。

dislay方法大概是这么调用的:
- (void)display {
    // 如果delegate实现了displayLayer:就调用displayLayer:方法
    if ([self.delegate respondsToSelector:@selector(displayLayer:)]) {
        [self.delegate displayLayer:self];
    } else {
        // 没实现displayLayer:就会创建一个context(backing store),并且调用drawInContext:方法
        // 这也就能理解为啥实现了displayLayer:方法后就不会调用drawLayer:inContext:方法了
        CGContextRef ctx = CGContextCreate(); // 这个CGContextCreate方法是我写的伪代码
        [self drawInContext:ctx];
    }
}
附上苹果该方法部分说明原文:

If the layer has a delegate object, this method attempts to call the delegate’s displayLayer: method, which the delegate can use to update the layer’s contents. If the delegate does not implement the displayLayer: method, this method creates a backing store and calls the layer’s drawInContext: method to fill that backing store with content.

接下来看看drawInContext:方法
- (void)drawInContext:(CGContextRef)ctx {
    // 调用delegate的drawLayer:inContext:方法
    if ([self.delegate respondsToSelector:@selector(drawLayer:inContext:)]) {
        [self.delegate drawLayer:self inContext:ctx];
    }
}
附上苹果该方法部分说明原文:

The default implementation of this method does not do any drawing itself. If the layer’s delegate implements the  drawLayer:inContext: method, that method is called to do the actual drawing

我们再来看看CALayerDelegate的两个方法:
  1. displayLayer:方法
- (void)displayLayer:(CALayer *)layer {
    // 我们可以在这里实现异步绘制
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
        CGSize size = CGSizeMake(100, 100);
        UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
        [UIColor.redColor setFill];
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 100, 100)];
        [path fill];
        UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        dispatch_async(dispatch_get_main_queue(), ^{
            layer.contents = (__bridge id)img.CGImage;
        });
    });
}
  1. drawLayer:inContext:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    // 如果没实现displayLayer:方法,我们也可以在这里绘制,只是不能异步绘制了
    // 在这里不需要自己生成context了,直接用传过来的ctx绘制
    CGContextSetFillColorWithColor(ctx, UIColor.redColor.CGColor);
    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 100, 100));
    CGContextClosePath(ctx);
    CGContextFillPath(ctx);
}
到此就完成了绘制流程的讲解,不知道你们有没有想过UIView的drawRect又是什么时候被调用的呢?
我们直接断点到drawRect:方法,通过LLDB的bt命令看看他是如何被调用的:

WeChat0790c6be7803a17d877e3f5f6c09e82a.png

通过调用栈可以看到UIView默认作为内置CALayer的delegate实现了drawLayer:inContext:方法,然后UIView的drawRect:方法就是通过该方法调用的。

总结:一定要多去看苹果官方文档,少看点网上的博客,包括我这篇,网上的文章质量良莠不齐就算了,还严重不系统,这样我们学的知识都是支离破碎的,本来很简单的知识看多了这些文章反而会感觉越难,切记切记!!!