不少博客都會寫顯示重寫了drawRect:
會增長額外的內存開銷,但不多有寫具體緣由的,下面我就從源碼來解釋這個「常識」canvas
UIKit框架是閉源的,可是能夠根據微軟的
winObjc
提供的源碼來看app
首先給drawRect:
方法打個斷點: 框架
根據已有知識,在Runloop有個系統監聽了beforeWaiting
狀態(還監聽了個exit
狀態不經常使用),每次到beforeWaiting
就會回調__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
,裏面調用C++的CA::Transaction::commit()
方法把UI變更打包提交給GPU來渲染,咱們經常使用的layoutSubviews:
和drawRect:
只是這個函數實現的一部分(等同於蘋果暴露了接口給你寫)函數
看斷點第一個用到OC語法的方法是[CALayer _display]
,咱們來看winObjc
的源碼:oop
代碼有點長,我省略了日誌等信息ui
- (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
任一一個都會增長內存消耗spa