When to retain and release CGLayerRef? - xcode

I have a question similar to this one:
CGLayerRef in NSValue - when to call retain() or release()?
I am drawing 24 circles as radial gradients in a view. To speed it up I am drawing the gradient into a layer and then drawing the layer 24 times. This worked really well to speed up the rendering. On subsequent drawRect calls some of the circles may need to be redrawn with a different hue, while others remain the same.
Every time through drawRect I recalculate a new gradient with the new hue and draw it into a new layer. I then loop through the circles, drawing them with the original layer/gradient or new layer/gradient as appropriate. I have a 24 element NSMutableArray that stores a CGLayerRef for each circle.
I think this is the answer provided in the question I linked above, however it is not working for me. The second time through drawRect, any circle that is drawn using the CGLayerRef that was stored in the array causes the program to crash when calling CGContextDrawLayerAtPoint. In the debugger I have verified that the actual hex value of the original CGLayerRef is stored properly into the array, and in the second time through drawRect that the same hex value is passed to CGContextDrawLayerAtPoint.
Further, I find that if I don't CGLayerRelease the layer then the program doesn't crash, it works fine. This tells me that something is going wrong with the memory management of the layer. It's my understanding that storing an object into an NSArray will increment it's reference count, and it won't be deallocated until the array releases it.
Anyway, here is the relevant code from drawRect. Down at the bottom you can see that I commented out CGLayerRelease. In this configuration the app doesn't crash although I think this is a resource leak. If I uncomment that release then the app crashes the second time though drawRect (between the first and second calls one of the circles has it's led_info.selected property cleared, indicating that it should use the saved layer rather than the new layer:
NSLog(#"ledView drawing hue=%4f sat=%4f num=%d size=%d",hue_slider_value,sat_slider_value,self.num_leds,self.led_size);
rgb_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:1.0];
end_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:0.0];
NSArray *colors = [NSArray arrayWithObjects:
(id)rgb_color.CGColor, (id)end_color.CGColor, nil];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,(__bridge CFArrayRef) colors, NULL);
CGLayerRef layer = CGLayerCreateWithContext(context, (CGSize){self.led_size,self.led_size}, /*auxiliaryInfo*/ NULL);
if (layer) {
CGContextRef layer_context = CGLayerGetContext(layer);
CGContextDrawRadialGradient(layer_context, gradient, led_ctr,self.led_size/8,led_ctr, self.led_size/2,kCGGradientDrawsBeforeStartLocation);
} else {
NSLog(#"didn't get a layer");
}
for (int led=0;led<[self.led_info_array count];led++) {
led_info=[self.led_info_array objectAtIndex:led];
// the first time through selected=1 and led_info.cg_layer=nil for all circles,
// so this branch is taken.
if (led_info.selected || led_info.cg_layer==nil) {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, layer);
CGContextAddRect(context, led_info.rect);
led_info.cg_layer=layer;
// the second time through drawRect one or more circles have been deselected.
// They take this path through the if/else
} else {
CGPoint startPoint=led_info.rect.origin;
// app crashes on this call to CGContextDrawLayerAtPoint
CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
}
}
// with this commented out the app doesn't crash.
//CGLayerRelease(layer);
Here is the declaration of led_info:
#interface ledInfo : NSObject
#property CGFloat hue;
#property CGFloat saturation;
#property CGFloat brightness;
#property int selected;
#property CGRect rect;
#property CGPoint center;
#property unsigned index;
#property CGLayerRef cg_layer;
- (NSString *)description;
#end
led_info_array is the NSMutableArray of ledInfo objects, the array itself is a property of the view:
#interface ledView : UIView
#property float hue_slider_value;
#property float sat_slider_value;
#property unsigned num_leds;
#property unsigned led_size;
#property unsigned init_has_been_done;
#property NSMutableArray *led_info_array;
//#property layerPool *layer_pool;
#end
The array is initialized like this:
self.led_info_array = [[NSMutableArray alloc] init];
Edit: since I posted I have found that if I put retain/release around the assignemt into the NSMutableArray then I can also leave in the original CGLayerRelease and the app works. So I guess this is how it is supposed to work, although I'd like to know why the retain/release is necessary. In the objective C book I am reading (and the answer to the question linked above) I thought assigning into NSArray implicitly did retain/release. The new working code looks like this:
if (led_info.selected || led_info.cg_layer==nil) {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, layer);
CGContextAddRect(context, led_info.rect);
if (led_info.cg_layer) CGLayerRelease(led_info.cg_layer);
led_info.cg_layer=layer;
CGLayerRetain(layer);
} else {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
}
You can probably tell that I'm brand new to Objective C and iOS programming, and I realize that I'm not really sticking to convention regarding case and probably other things. I'll clean that up but right now I want to solve this memory management problem.
Rob, thanks for the help. I could use a little further clarification. I think from what you are saying that there are two problems:
1) Reference counting doesn't work with CGLayerRef. OK, but it would be nice to know that while writing code rather than after debugging. What is my indication that when using "things" in Objective C/cocoa that resource counting doesn't work?
2) You say that I'm storing to a property, not an NSArray. True, but the destination of the store is the NSArray via the property, which is a pointer. The value does make it into the array and back out. Does resource counting not work like this? ie instead of CGLayerRef, if I were storing some NSObject into NSArray using the code above would resource counting work? If not, then would getting rid of the intermediate led_info property and accessing the array directly from within the loop work?

You're not storing the layer directly in an NSArray. You're storing it in a property of your ledInfo object.
The problem is that a CGLayer is not really an Objective-C object, so neither ARC nor the compiler-generated (“synthesized”) property setter will take care of retaining and releasing it for you. Suppose you do this:
CGLayerRef layer = CGLayerCreateWithContext(...);
led_info.cg_layer = layer;
CGLayerRelease(layer);
The cg_layer setter method generated by the compiler just stores the pointer in an instance variable and nothing else, because CGLayerRef isn't an Objective-C object reference. So when you then release the layer, its reference count goes to zero and it's deallocated. Now you have a dangling pointer in your cg_layer property, and when you use it later you crash.
The fix is to write the setter manually, like this:
- (void)setCg_layer:(CGLayerRef)layer {
CGLayerRetain(layer);
CGLayerRelease(_cg_layer);
_cg_layer = layer;
}
Note that it's important to retain the new value before releasing the old one. If you release the old one before retaining the new one, and the new one happens to be the same as the old one, you might deallocate the layer right in the middle!
UPDATE
In response to your edits:
Reference counting works with CGLayerRef. Automatic reference counting (ARC) doesn't. ARC only works with things that it thinks are Objective-C objects ARC does not automatically retain and release a CGLayerRef, because ARC doesn't think a CGLayerRef is a reference to an Objective-C object. An Objective-C object is (generally speaking) an instance of a class declared with #interface, or a block.
The CGLayer Reference says that CGLayer is derived from CFType, the basic type for all Core Foundation objects. (As far as ARC is concerned, a Core Foundation object is not an Objective-C object.) You need to read about “Ownership Policy” and “ Core Foundation Object Lifecycle Management” in the Memory Management Programming Guide for Core Foundation.
The “destination of the store” is an instance variable in your ledInfo object. It's not “the NSArray via the property”. The value doesn't ”make it into the array and back out.” The array gets a pointer to your ledInfo object. The array retains and releases the ledInfo object. The array never sees or does anything with the CGLayerRef. Your ledInfo object is responsible for retaining and releasing any Core Foundation objects it wants to own, like the layer in its cg_layer property.
As I mentioned, if ledInfo doesn't retain the layer (with CFRetain or CGLayerRetain) in its cg_layer setter, it risks the layer being deallocated, leaving the ledInfo with a dangling pointer. Do you understand what a dangling pointer is?

Related

How do I prevent duplicate code inside 2 drawRect: methods?

I have a few lines of drawing code that are duplicated in two different subclasses. When I move this drawing code to its own class and then call it from within drawRect: it is called but it is never drawn to the screen. What is the right way prevent duplicating code in two different drawRect: methods?
Details: I'm making a custom control by subclassing NSTableView and NSTableCellView. My drawing code needs to be in drawRect: in both of these subclasses.
I created a subclass of NSObject that declares one method. Here is the implementation:
#implementation TNLChartDrawingExtras
- (void)drawDividersInRect:(NSRect)rect startingAtDate:(NSDate *)startDate withZoomFactor:(NSNumber *)zoomFactor {
float pos = 0;
NSDate *currentDate = [startDate copy];
while (pos < rect.size.width) {
//draw the vertical divider
NSBezierPath *linePath = [NSBezierPath bezierPathWithRect:NSMakeRect(pos, 0.0, 1.0, rect.size.height)];
[[NSColor colorWithCalibratedWhite:0.85 alpha:0.5] set];
[linePath fill];
//increment the values for the next day
currentDate = [NSDate dateWithTimeInterval:86400 sinceDate:currentDate]; // add one day to the current date
pos = pos + (86400.0/ [zoomFactor floatValue]);
}
}
In my NSTableView subclass I define a property for this object. Then in awakeFromNib I create an instance of this class:
- (void)awakeFromNib {
self.extras = [[TNLChartDrawingExtras alloc] init];
}
In drawRect: I send this message:
- (void)drawRect:(NSRect)dirtyRect {
// more code here...
[self.extras drawDividersInRect:viewBounds startingAtDate:chart.startDate withZoomFactor:self.zoomFactor];
}
The code is executed but the lines it is supposed to draw don't appear. If I put the code from drawDividersInRect:... in the drawRect: method, it works fine.
My original solution (described in the question) may have worked if I had continued to debug it. However, I think the more important question is what is the right way to approach this problem. Here I solve it by adding category on NSView to the project:
I'm trying to add custom drawing code to both NSTableView and NSTableCellView. Both are subclasses of NSView so I created a category of NSView and added my custom drawing method there. Now I can call my drawing method from both subclasses.
Without see any of your code, it sounds like you are in need of a protocol, which is that same thing as an interface in the java language. Protocols are a series of methods that a group of a few unrelated classes may need to used. For example, in a drawing program like PhotoShop, Rects, Ovals, and Images are all valid objects that can be stored as layers in a .psd document, however, they all share traits like the ability to change object properties in a particular way. An example would be adjusting an objects' opacity or rescale an objects size, etc. Methods that access the objects properties for scaling or functions that can be shared between unrelated objects types call for protocols.
They are essentially .h files that list out the methods. The .m file that defines implementation of the code can store a tag in it's .h file.
// example of a class that acts as a protocol implementor
#interface LayerObject: NSObject <Resizable>
The tag says, "I am a member of the protocol named X, you can find one/some of the methods of protocol X in my .m file." All you would have to do is import the protocol to the desired classes using the following syntax:
// Declare protocol
#protocol Resizable;
// List methods wanted from protocol
- id resizeRect: id layerObject;
to gain the methods defined in the protocol.
Here is a website that describes protocols through an example:
http://agilewarrior.wordpress.com/2012/03/19/simple-objective-c-protocol-example/
Another solution would be to create a class hierarchy that uses an abstract class to put the given drawRect method you are working in. From here you could define the two subclass you are working on as a subclass of the abstract class in which they would inherit the drawRect method code, keeping you from repeating the code in two separate classes.

Iterate over NSTableview or NSArrayController to get data

I have an NSTableview which s bound to a NSArrayController. The Table/Arraycontroller contains Core Data "Person" entities. The people are added to the NSTableview by the GUI's user.
Let's say a person entity looks like
NSString* Name;
int Age;
NSString* HairColor;
Now I want to iterate over what is stored in the array controller to perform some operation in it. The actual operation I want to do isn't important I don't really want to get bogged down in what I am trying to do with the information. It's just iterating over everything held in the NSArraycontroller which is confusing me. I come from a C++ and C# background and am new to Cocoa. Let's say I want to build a NSMutableArray that contains each person from nsarraycontroller 1 year in the future.
So I would want to do something like
NSMutableArray* mutArray = [[NSMutableArray alloc] init];
foreach(PersonEntity p in myNsArrayController) // foreach doesn't exist in obj-c
{
Person* new_person = [[Person alloc] init];
[new_person setName:p.name];
[new_person setHairColor:p.HairColor];
[new_person setAge:(p.age + 1)];
[mutArray addObject:new_person];
}
I believe the only thing holding me back from doing something like the code above is that foreach does not exist in Obj-c. I just don't see how to iterate over the nsarraycontroller.
Note: This is for OSX so I have garbage collection turned on
You're looking for fast enumeration.
For your example, something like
for (PersonEntity *p in myNsArrayController.arrangedObjects)
{
// Rest of your code
}
You can also enumerate using blocks. For example:
[myNsArrayController enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop)
{
PersonEntity *p = object;
// Rest of your code
}];
There's pro's and cons to both approaches. These are discussed in depth in the answer to this question:
Objective-C enumerateUsingBlock vs fast enumeration?
You can find a great tutorial on blocks in Apple's WWDC 2010 videos. In that they say that at Apple they use blocks "all the time".

Remove Interpolation of CALayer's contents property

Re-asking the question:
When you add an animation for the contents key, a CATransitionAnimation is apparently being triggered that fades the original contents property to the first value in the animation's values array, resulting in a .25 second fade. And it looks bad! I have suppressed every animatable property using all the methods discussed here (returning null animations through a delegate, into the actions dictionary, using CATransaction), but none of these seem to be targeting this particular transition animation.
I have been looking into what property could possibly be responsible for this, but cannot figure it out.
I need to suppress the transition animation that is occurring when you add an animation to the contents key.
As I'm at such a loss, I will put the keyframe animation that is being added for you to see. I figure maybe I am doing something wrong here? Just a note, that array is just an array of 6 CGImageRefs (the frames of the animation).
+ (CAKeyframeAnimation *)moveLeftAnimation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
animation.values = [NSArray arrayWithArray:[Setzer walkingLeftSprite]];
animation.duration = 0.5f;
animation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.2],
[NSNumber numberWithFloat:0.4],
[NSNumber numberWithFloat:0.6],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0],
nil];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
return animation;
}
Also, I have an animation that handles the position key, in the sprite's action dictionary:
+ (CABasicAnimation *)moveAnimation {
CABasicAnimation *moveAnimation = [CABasicAnimation animation];
moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
moveAnimation.duration = 0.5f;
return moveAnimation;
}
I am thinking maybe this transition is occurring when you change the layer's position? I don't know...
Please help! This is driving me NUTS!
You can do something along the lines of what I describe in this answer, where I disable the implicit animations for various layer properties by setting the appropriate values in the actions dictionary on that layer.
In your case, I believe something like
NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
[NSNull null], #"contents",
nil];
layer.actions = newActions;
[newActions release];
should prevent the implicit animation of a layer's contents property until you explicitly animate it.
To prevent any animation, you could set an object as the delegate of your CALayer and then implement the ‑actionForLayer:forKey: delegate method and return a null object:
- (id<CAAction>)actionForLayer:(CALayer*)layer forKey:(NSString*)key
{
if(layer == yourLayer)
{
if([key isEqualToString:#"contents"])
{
return (id<CAAction>)[NSNull null];
}
}
return nil;
}
Here are a few notes on how this puzzle was solved, and how everyone's answers provided a piece of the puzzle.
To restate the problem: when I added a keyframe animation to the #contents key of a CALayer, there appeared to be a .25 second fade transition between the original contents property and the first frame of the keyframe animation. This looked bad, and I wanted to get rid of it.
At first, I thought surely that by using a CATransaction I could suppress this implicit transition animation, as that is what Apple's docs lead you to believe. Using a transaction, I suppressed in every possible way you could imagine, and yet it was still happening. Then I tried returning NULL animations for every animatable property via a dictionary. No luck. Then I did the same thing, but with a delegate. Still no luck.
What I didn't mention is that at the same time the animation was being added and the layer was being moved, two sublayers beneath it were being removed from the their superlayers. I tried adding custom animations for the onOrderOut key, but to no avail. Then I stumbled upon another question here on StackOverflow, about adding a custom animation for the onOrderOut key. It turns out, quite simply, that you can't, and that if you wan to implement some other animation for when a sublayer is removed, you have to use the delagate method animationDidStop. How can I use custom animations for onOrderOut in Core Animation?
So at this point I was convinced that this ghost image had nothing to do with the actual layer in question, the sprite itself. To test this, I just didn't add the sublayers that went beneath it. Sure enough, there was no lingering ghost when I moved the sprite around. It looked perfect. Then I added the layers beneath there was the ghost. It was almost like the sprite's contents were drawn into the layers beneath it, so that when they were removed, there was a sort of imprint.
Instead of removing the sublayers, I just tried hiding them. Bingo. It was perfect. The same fade transition occurred, but there was no imprint of the sprite left. I still don't understand why this is so.
Then, because I still needed to remove those layers, I implemented the animationDidStop delegate method for the sprite's various movement animations to remove them.
This is the original:
This is the new version:
So while I don't understand, technically, why there appears to be an imprint, I am all but certain that it concerns what goes on behind the scenes when you remove a sublayer. Also, for interest sake, I still wanted that sublayer to be hidden on animation start, so I just set it to hidden and provided my own transition animation.
So thanks to everyone for their help. This is a strange use case, I know, but if you are ever thinking of making a 2d sprites-based Final Fantasy Tactics ripoff, then hopefully my pain will be to your benefit!
class AnimationLayer: CALayer {
override class func defaultAction(forKey event: String) -> CAAction? {
let transition = CATransition()
transition.duration = 0
return transition
}
}

Can't get subview animation to appear even after alloc :)

I have a subview loaded into an UIView. In the subview's .m file I have the following:
- (void)startAnimation {
// Array to hold png images
imageArray = [[NSMutableArray alloc] initWithCapacity:22];
animatedImages = [[UIImageView alloc] initWithImage:viewForImage];
// Build array of images, cycling through image names
for (int i = 1; i < 22; i++){
[imageArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"image%d.png", i]]];
}
animatedImages.animationImages = [NSArray arrayWithArray:imageArray];
// One cycle through all the images takes 1 seconds
animatedImages.animationDuration = 2.0;
// Repeat forever
animatedImages.animationRepeatCount = 0;
// Add subview and make window visible
[viewForMovie addSubview:animatedImages];
// Start it up
animatedImages.startAnimating;
NSLog(#"Executed");
}
Please be noted that I have in the .h file:
UIImageView *animatedImages;
NSMutableArray *imageArray;
UIView *viewForMovie;
#property(nonatomic,retain)IBOutlet UIView *viewForMovie;
and in the .m file:
#synthesize viewForMovie;
and I have connected viewForMovie to a UIView in IB. I've been on this for several hours now and have tried many variations I've found on the web but cannot get it to work. There are no errors and the other GUI graphics in the subview appear very nicely....but the animation just doesn't appear over top where it should. Also the NSlog reports that the method has in fact been called from the parent. Can anyone see any blaring issues? Thx.
PS: I'm pretty new at this.
Based on the code shown and the behavior you see so far, here are my suggestions:
Make sure the viewForMovie IBOutlet is connected properly in Interface Builder. If it's not connected properly (and so nil), nothing will appear. If you didn't mean to make it an IBOutlet in the first place, then you'll need to manually create it and add it as a subview to self before using it.
Not sure why you have the viewForMovie UIView in the first place. Is this subview's class (let's call it MySubview) a subclass of UIView? You can just show the animation in self instead of adding another subview inside it. Are you going to add more uiviews to this subview besides the viewForMovie?
To get rid of the "may not respond to" warning, declare the startAnimation method in the MySubview.h file (under the #property line):
-(void)startAnimation;
The fact that the warning says "UIView may not respond" also tells you that the parent view has declared newView as a UIView instead of MySubview (or whatever you've named the subview class). Change the declaration in the parent from UIView *newView; to MySubview *newView;.
In the initWithImage, what is "viewForImage"? Is it a UIImage variable or something else?
If all of the images are the same size and fit in the subview as-is, you don't need to set the frame--the initWithImage will automatically size the UIImageView using the init-image dimensions.
Double check that the images you are referencing in the for-loop are named exactly as they are in the code and that they have actually been added to the project.
Finally, you should release the objects you alloc in startAnimation. At the end of the method, add:
[imageArray release];
[animatedImages release];
The only item, however, that I think is actually preventing the animation from appearing right now is item 1.

Is this the right way to add items to NSCombobox in Cocoa?

I'm Delphi programmer and very new to Cocoa.
at first I tried this :
-(void)awakeFromNib
{
int i;
NSString *mystr;
for (i=1;i<=24;i++)
{
[comboHour addItemWithObjectValue:i];
}
}
But it didn't work. Then I tried to search on Google but no luck.
After experimenting about 30 min, I come with this:
-(void)awakeFromNib
{
int i;
NSString *mystr;
for (i=1;i<=24;i++)
{
mystr = [[NSString alloc]initWithFormat:#"%d",i];
[comboHour addItemWithObjectValue:mystr];
//[mystr dealloc];
}
}
My questions are:
Is this the right way to do that ?
Do I always need to alloc new
NSString to change its value from
integer ?
When I uncomment [mystr dealloc],
why it won't run ?
Does it cause memory leak to alloc
without dealloc ?
Where can I find basic tutorial like
this on internet ?
Thanks in advance
Do I always need to alloc new NSString to change its value from integer ?
Generally yes; however, there are more convenient ways to create strings (and many other types of objects) than using alloc and init (see autorelease pools below)
You can pass any Objective-C object type to addItemWithObjectValue:, including NSString and NSNumber objects. Both classes have a number of convenient class methods you can use to create new instances, for example:
for (int i = 0; i < 24; ++i)
{
[comboHour addItemWithObjectValue:[NSNumber numberWithInt:i]];
}
When I uncomment [mystr dealloc], why it won't run ?
Never call dealloc. Use release instead.
Cocoa objects are reference counted, like COM objects in Delphi. Like COM, you call release when you're finished with an object. When an object has no more references it is automatically deallocated.
Unlike COM, Cocoa has "autorelease pools", which allows you to, for example, create a new NSString instance without having to worry about calling release on it.
For example: [NSString stringWithFormat:#"%d", 123] creates an "autoreleased" string instance. You don't have to release it when you're done. This is true of all methods that return an object, except new and init methods.
Does it cause memory leak to alloc without dealloc ?
Yes, unless you're using garbage collection.
Where can I find basic tutorial like this on internet ?
See Practical Memory Management
The correct way is:
-(void)awakeFromNib
{
int i;
for (i=1;i<=24;i++)
{
NSString *mystr = [[NSString alloc]initWithFormat:#"%d",i];
[comboHour addItemWithObjectValue:mystr];
[mystr release];
}
}
You can use NSNumber instead of NSString, which might be preferable depending on your context.
You do need to create a new object everytime, because addItemWithObjectValue: is expecting an object rather than a primitive.
You can create a new object (e.g. `NSString), via two methods:
Using alloc/init, like how you did it initially. Such initializations require the release of the object once it isn't required anymore in the allocation scope, using release rather than dealloc.
Using stringWithFormat: factory methods that use auto release pool to release themselves "automatically". The code would look like:
-(void)awakeFromNib
{
int i;
for (i=1; i <= 24; i++) {
NSString *s = [NSString stringWithFormat:#"%d", i];
[comboHour addItemWithObjectValue:s];
}
}
However, it is recommended not to use such construction within loops.
For memory issues, check out the Memory Management Programming Guide for Cocoa
Based on the code you posted and your stated experience level, I recommend going through Apple's Currency Converter tutorial if you haven't already. It's the standard Cocoa tutorial every beginner should read. Fundamentals like interacting with IBOutlets are covered.

Resources