在iOS上,setNeedsDisplay真的不会导致drawRect被调用…除非CALayer的显示或drawInContext终于调用drawRect?

我不太了解CALayer的displaydrawInContext如何与视图中的drawRect相关联。

如果我有一个每隔1秒设置[self.view setNeedsDisplay]的NSTimer,那么drawRect每1秒调用一次,如drawRect的NSLog语句所示。

但是,如果我inheritance一个CALayer并将其用于视图,如果我使display方法为空,那么现在drawRect永远不会被调用。 更新:display每1秒调用一次,如NSLog语句所示。

如果我删除那个空的display方法并添加一个空的drawInContext方法, drawRect再也不会被调用。 更新:但是drawInContext每隔1秒调用一次,如NSLog语句所示。

究竟发生了什么? 看起来display可以select性地调用drawInContextdrawInContext可以select性地调用drawRect (怎么样?),但是这里的真实情况是什么?


更新:有更多的线索回答:

我将CoolLayer.m代码更改为以下内容:

 -(void) display { NSLog(@"In CoolLayer's display method"); [super display]; } -(void) drawInContext:(CGContextRef)ctx { NSLog(@"In CoolLayer's drawInContext method"); [super drawInContext:ctx]; } 

所以,如果在视图中的位置(100,100)处有一个月亮(如Core Graphics绘制的圆圈),现在我将其更改为位置(200,200),自然,我将调用[self.view setNeedsDisplay] ,现在,CALayer将根本没有caching新的视图图像,因为我的drawRect指出现在应该如何显示月亮。

即使如此,入口点是CALayer的display ,然后CALayer的drawInContext :如果我在drawRect设置一个断点,调用堆栈显示:

在这里输入图像说明

所以我们可以看到CoolLayer的display先input,然后进入CALayer的display ,然后是CoolLayer的drawInContext ,然后是CALayer的drawInContext ,即使在这种情况下,新图像也不存在这样的caching。

最后,CALayer的drawInContext调用委托的drawLayer:InContext 。 委托是视图( drawLayer:InContext或UIView)…和drawLayer:InContext是UIView中的默认实现(因为我没有覆盖它)。 最后是drawLayer:InContext调用drawRect

所以我猜两点:为什么它进入CALayer即使没有caching的图像? 因为通过这种机制,图像被绘制在上下文中,最后返回display ,CGImage从这个上下文创build,然后它被设置为新的图像caching。 这是CALayer如何caching图像。

另一件我不太确定的事情是:如果[self.view setNeedsDisplay]总是触发drawRect被调用,那么CALayer中的caching图像什么时候可以使用? 可能是…在Mac OS X上,当另一个窗口覆盖一个窗口,现在顶部窗口被移走。 现在我们不需要调用drawRect来重绘所有东西,但是可以在CALayer中使用caching的图像。 或者在iOS上,如果我们停止应用程序,做一些其他的事情,并回到应用程序,那么可以使用caching的图像,而不是调用drawRect 。 但是如何区分这两种“脏”呢? 一个是“未知的脏” – 月亮需要根据drawRect逻辑的指示重新绘制(它也可以使用随机数作为坐标)。 其他types的肮脏是它被掩盖或使消失,现在需要重新显示。

当一个图层需要显示并且没有有效的后备存储时(可能是因为图层收到了一个setNeedsDisplay消息),系统将display消息发送给图层。

-[CALayer display]方法大致如下所示:

 - (void)display { if ([self.delegate respondsToSelector:@selector(displayLayer:)]) { [[self.delegate retain] displayLayer:self]; [self.delegate release]; return; } CABackingStoreRef backing = _backingStore; if (!backing) { backing = _backingStore = ... code here to create and configure the CABackingStore properly, given the layer size, isOpaque, contentScale, etc. } CGContextRef gc = ... code here to create a CGContext that draws into backing, with the proper clip region ... also code to set up a bitmap in memory shared with the WindowServer process [self drawInContext:gc]; self.contents = backing; } 

所以,如果你重写display ,除非你调用[super display] ,否则不会发生。 而且,如果在displayLayer:中实现displayLayer:必须以某种方式创build自己的CGImage ,并将其存储在图层的contents属性中。

-[CALayer drawInContext:]方法大致如下所示:

 - (void)drawInContext:(CGContextRef)gc { if ([self.delegate respondsToSelector:@selector(drawLayer:inContext:)]) { [[self.delegate retain] drawLayer:self inContext:gc]; [self.delegate release]; return; } else { CAAction *action = [self actionForKey:@"onDraw"]; if (action) { NSDictionary *args = [NSDictionary dictionaryWithObject:gc forKey:@"context"]; [action runActionForKey:@"onDraw" object:self arguments:args]; } } } 

就我所知, onDraw操作没有logging。

-[UIView drawLayer:inContext:]方法大致如下所示:

 - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)gc { set gc's stroke and fill color spaces to device RGB; UIGraphicsPushContext(gc); fill gc with the view's background color; if ([self respondsToSelector:@selector(drawRect:)]) { [self drawRect:CGContextGetClipBoundingBox(gc)]; } UIGraphicsPopContext(gc); } 

UIView的更新过程基于dirty状态,意味着如果外观没有变化,视图不可能被重画。

这是开发人员参考中提到的内部实现。

实现drawInContext或display或drawRect告诉操作系统当视图变脏(needsDisplay)时,你想调用哪一个。 select一个你想调用一个肮脏的视图,并实现,不要把任何代码,你依赖执行在其他人。