为什么说重写了drawRect:后会增加内存开销
2020/7/10 23:09:28
本文主要是介绍为什么说重写了drawRect:后会增加内存开销,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言
很多博客都会写显示重写了drawRect:
会增加额外的内存开销,但很少有写具体原因的,下面我就从源码来解释这个“常识”
UIKit框架是闭源的,但是可以根据微软的
winObjc
提供的源码来看
详解
首先给drawRect:
方法打个断点:
根据已有知识,在Runloop有个系统监听了beforeWaiting
状态(还监听了个exit
状态不常用),每次到beforeWaiting
就会回调__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
,里面调用C++的CA::Transaction::commit()
方法把UI变动打包提交给GPU来渲染,我们常用的layoutSubviews:
和drawRect:
只是这个函数实现的一部分(等同于苹果暴露了接口给你写)
看断点第一个用到OC语法的方法是[CALayer _display]
,我们来看winObjc
的源码:
代码有点长,我省略了日志等信息
- (void)display { if (priv->savedContext != NULL) { CGContextRelease(priv->savedContext); priv->savedContext = NULL; } //这里的contents就是一个layer的back-store if (priv->contents == NULL || priv->ownsContents || [self isKindOfClass:[CAShapeLayer class]]) { if (priv->contents) { if (priv->ownsContents) { CGImageRelease(priv->contents); } priv->contents = NULL; } // Update content size, even in case of the early out below. int widthInPoints = ceilf(priv->bounds.size.width); int heightInPoints = ceilf(priv->bounds.size.height); int width = (int)(widthInPoints * priv->contentsScale); int height = (int)(heightInPoints * priv->contentsScale); // 这里计算back-store的大小,也就是需要的内存块大小 priv->contentsSize.width = (float)width; priv->contentsSize.height = (float)height; // 下面就是决定drawRect: 为什么会额外增加内存消耗的原因了 // 先来看object_isMethodFromClass这个方法,这个方法的实现: // static BOOL object_isMethodFromClass(id object, SEL selector, const char* className) { // return class_getMethodImplementation(objc_getClass(className), selector) == // class_getMethodImplementation(object_getClass(object), selector); //} // 这个方法本质是判断object和className对应的类对象里的selector实现是不是同一个,换句话来说判断object有没有重写selector方法,如果重写了就返回YES // 所以 如果下面三个方法都没有重写(`drawRect:` `drawLayer:in:` `displayLayer`),那么hasDrawingMethod就是false,display方法直接return bool hasDrawingMethod = false; if (priv->delegate != nil && (!object_isMethodFromClass(priv->delegate, @selector(drawRect:), "UIView") || !object_isMethodFromClass(priv->delegate, @selector(drawLayer:inContext:), "UIView") || [priv->delegate respondsToSelector:@selector(displayLayer:)])) { hasDrawingMethod = true; } if (!object_isMethodFromClass(self, @selector(drawInContext:), "CALayer")) { hasDrawingMethod = true; } if (!hasDrawingMethod) { return; } // 如果有重写方法,就进行下面的代码 unsigned int tries = 0; do { // Create the contents // 这个方法就是具体的创建内存块的方法了,也就是增加内存消耗的部分,下面总之就是会调用你实现的那三个方法或之一 woc::StrongCF<CGContextRef> drawContext{ woc::MakeStrongCF( CreateLayerContentsBitmapContext32(width, height, priv->contentsScale)) }; _CGContextPushBeginDraw(drawContext); if (priv->_backgroundColor != nullptr && CGColorGetPattern(priv->_backgroundColor) != nullptr) { CGContextSaveGState(drawContext); CGContextSetFillColorWithColor(drawContext, priv->_backgroundColor); CGRect wholeRect = CGRectMake(0, 0, width, height); CGContextFillRect(drawContext, wholeRect); CGContextRestoreGState(drawContext); } // UIKit and CALayer consumers expect the origin to be in the top left. // CoreGraphics defaults to the bottom left, so we must flip and translate the canvas. CGContextTranslateCTM(drawContext, 0, heightInPoints); CGContextScaleCTM(drawContext, 1.0f, -1.0f); CGContextTranslateCTM(drawContext, -priv->bounds.origin.x, -priv->bounds.origin.y); _CGContextSetShadowProjectionTransform(drawContext, CGAffineTransformMakeScale(1.0, -1.0)); [self drawInContext:drawContext]; if (priv->delegate != 0) { if ([priv->delegate respondsToSelector:@selector(displayLayer:)]) { [priv->delegate displayLayer:self]; } else { [priv->delegate drawLayer:self inContext:drawContext]; } } _CGContextPopEndDraw(drawContext); woc::StrongCF<CFErrorRef> renderError; if (CGContextIwGetError(drawContext, &renderError)) { switch (CFErrorGetCode(renderError)) { case kCGContextErrorDeviceReset: NSTraceInfo(TAG, @"Hardware device disappeared when rendering %@; retrying.", self); ++tries; continue; default: { FAIL_FAST_MSG("Failed to render <%hs %p>: %hs", object_getClassName(self), self, [[static_cast<NSError*>(renderError.get()) debugDescription] UTF8String]); break; } } } CGImageRef target = _CGBitmapContextGetImage(drawContext); priv->ownsContents = TRUE; priv->savedContext = CGContextRetain(drawContext); priv->contents = CGImageRetain(target); break; } while (tries < _kCALayerRenderAttempts); if (!priv->contents) { NSTraceError(TAG, @"Failed to render layer %@", self); } } else if (priv->contents) { priv->contentsSize.width = float(CGImageGetWidth(priv->contents)); priv->contentsSize.height = float(CGImageGetHeight(priv->contents)); } } 复制代码
总结
根据源码便可以知道,不光是drawRect:
,只要是显式在自定义类里写了drawRect:
drawLayer:in:
displayLayer
任一一个都会增加内存消耗
这篇关于为什么说重写了drawRect:后会增加内存开销的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值