Silver Challenge - changeColor: and setNeedsDisplay


#1

In chapter 6 we learned that if we want to display changes made to a view we need to send setNeedsDisplay to that view. Accordingly, I implemented setNeedsDisplay in my changeColor method in HypnosisViewController.m:

- (void)changeColor:(UISegmentedControl *)segment
{
    if (segment.selectedSegmentIndex == 0){
        [(id)self.view setCircleColor: [UIColor redColor]];
    }else if (segment.selectedSegmentIndex == 1){
        [(id)self.view setCircleColor:[UIColor greenColor]];
    }else if (segment.selectedSegmentIndex == 2){
        [(id)self.view setCircleColor:[UIColor blueColor]];
    }
    
    [(id)self.view setNeedsDisplay];
}

Later, I realized that the hypnosisView could change color even without the setNeedsDisplay method. Why is this? What is calling drawRect even though all I’ve done is change the circleColor property?

Side question: you’ll notice the “(id)” casting before all the self.view. I found that this was this only way for the method to know what type of object self.view is, and therefore let me call setCircleColor: on it. Is this good practice? Feels kinda hackish to me…


#2

I don’t have the 3rd Edition of the book but why are you typecasting, especially to id? Is self.view not an instance of UIView already?

Also, probably it is better invoke setNeedsDisplay inside the method setCircleColor:.


#3

Good question.

The (id) typecasting isn’t something I learned in the book. Without the (id) in this case, XCode will throw the issue - “No visible @interface for ‘UIView’ declares the selector 'setCircleColor:” It thinks that self.view is a standard UIView object and not the custom UIView subclass, HypnosisView. Typecasting to (id) makes the compiler double-check the class of self.view and discover that it’s a Hypnosis view, letting you send it the message “setCircleColor”.

I did some research and read that casting to (id) is generally bad practice. Why?

DON’T take my full word on this because I’m a novice, but from what I know…

The need to use (id) in this way exposes a wider problem with your code. Normally, a method like the changeColor: method I implemented above (one called on a delegate by a delegatOR - HypnosisView, in this case) should be declared in that ViewController’s delegate protocol. In that case, the delegating object would be also passed in as one of the arguments (in the same way tableView:(UITableView *)tableView is the first argument in all the UITableViewDelegate methods). What i should have done is wrote a HypnosisViewDelegate protocol with a method whose implementation would look like:

- (void)hypnosisView:(HypnosisView *)hypnosisView
         changeColor:(UISegmentedControl *)segment
{
    if (segment.selectedSegmentIndex == 0){
        [hypnosisView setCircleColor: [UIColor redColor]];
    }else if (segment.selectedSegmentIndex == 1){
        [hypnosisView setCircleColor:[UIColor greenColor]];
    }else if (segment.selectedSegmentIndex == 2){
        [hypnosisView setCircleColor:[UIColor blueColor]];
    }
}

But alas, writing our own delegate protocols is beyond the scope of the book at this point, so i stuck with (id).


#4

In HypnosisView.m, the setCircleColor-method looks like this (or at least mine does):

- (void)setCircleColor:(UIColor *)clr { circleColor = clr; [self setNeedsDisplay]; }

So as the poster above suggests, putting [self setNeedsDisplay] in the setCircleColor-method is a better idea - and also what you have probably done. You are actually calling [self setNeedsDisplay] twice.


#5

@henwen

Without seeing the rest of your code, I’ll have to guess some things that I had being an issue when I encountered similar situations.

In regards to setNeedsDisplay, I already had it implemented in the setCircleColor. Therefore it would always call the drawRect method whenever I called setCircleColor. (setNeedsDisplay calls drawRect)

The type casting to (id) and the error message you got occurred for me when I did the following in HypnosisViewController.

Since view is typed as UIView and HypnosisViewVariableName is typed HypnosisView, view had no knowledge of how to access setCircleColor which was declared in HypnosisView. The solution that I have seen and used was to declare a pointer to HypnosisView within HypnosisViewController. When changes to the view was needed, accessing the pointer would perform the changes.

*** As a guess *** I would say that typing it as (id) caused the compiler to consider a larger memory entry size corresponding to how much an id class would take instead of just the size that UIView would take. Something like typing an int into a float. It works, but there is always the chance that you will not get what you are expecting.


#6

@henwen - I don’t think the cast to Id causes the compiler to double check the type; I think the cast really does nothing other than stop the compiler from complaining - meaning you could send any message to any object of any type if you do this cast (with possible catastrophic effect). I imagine it also has no effect on the output that the compiler produces; it simply says to the compiler - “I know what I’m doing - trust me here and let it happen”.

As has been mentioned in previous chapters, Objective-C is not a statically typed language. My current understanding of what that means is that the type of the object is checked at runtime and so doesn’t actually have to be known at compile time. Every method call causes a run-time search through the object’s class hierarchy looking for the method you invoked every time it is called (this is very different to what happens in a strongly-typed language where the call to the correct method is ‘burnt in’ to the compiled code and no run-time search takes place).

I expect there are pro’s and con’s to this dynamic vs. statically typed environment; I guess one of the downsides would be performance in some cases (all that searching for methods at runtime) and another is that it could be more error prone - if you mistype the name of a method sent to an Id type your code will only fail at runtime. This puts a bigger burden on you as a developer to test every possible path through your code otherwise the first you will know about the error is after you have shipped your app and someone wants a refund!

For that reason I suspect that casting objects to the id type to avoid type-related issues is almost certainly not a good practice.
However, I do wonder whether I’m missing the point - as someone who has come from a statically typed background, perhaps I’m just pre-conditioned to think this is ‘bad’. Presumably there are situations where this dynamic approach provides flexibility that just isn’t available in a more static environment?