Pointer to an object before sending message - objective-c

I apologize if the title is somewhat misleading, I didn't know how to describe the problem. So I came across an online tutorial today and I encountered a line of code that got me asking a few questions.
For example this line of code
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:100]; <--- What is this part?
What is it doing here? Does it return an instance of UIImageView(or converting it maybe)? I didn't know you could doing something like this.
Many thanks.

[cell viewWithTag:100] is a method call (message send). The pointer cell is the object pointer, and viewWithTag is the method of that object that is being called, passing as a parameter the numeric value 100.
The method call returns a UIView pointer which is cast into an UIImageView pointer (presumably because the programmer knows that's the correct type) before assigning to recipeImageView.

Related

In Objective-C, why is casting needed when assigning to a variable whose type is specified?

This use of type casting comes up a lot, e.g. in a button tap action:
UIButton *button = (UIButton *)sender;
My question is why the explicit cast "(UIButton *)" is necessary. Doesn't assigning the value of "sender" to variable "button" already effectively cast it as a pointer to UIButton?
It tells the compiler that you know what you're doing: "I know sender technically isn't a UIButon* but I promise that it always will be at run-time."
As Stephen said, it is not really required.
But it is a quesiton of style and good business practice.
BTW, when you omit it, you get a warning, not an error.
Just for getting rid of the warning you could simply do:
someObject = (id) anotherObject;
That will work with references to any Object of any class. You see that there is hardly any casting acutally done.
You can allways assign to superclasses without cast:
objectOfAClass = objectOfSubclassOfA;
Bottom line is, it helps you avoiding mistakes by forcing you to think a very brief moment about what you are actually doing there.
In objective-c it is common to write action methods like
- (void)didAction:(id)sender
This method can be called by any object(eg. UIButton, UIControl etc.), and expected set the sender self.
Ever object is an id. So in that method you have a variable in type of id. If you are sure it is a button then you can cast to UIButton.
Another way is, if you are sure that the method will be called only by a UIButton instance then you can change your action method to
- (void)didAction:(UIButton *)senderButton
If you do it so, you do not need to cast.
Please note that, for that kind castings you are responsible, that nothing goes wrong. If you unsure you can always check the class of object with
- (BOOL)isKindOfClass:(Class)aClass
method.

How to properly use makeObjectsPerformSelector: Getting error unrecognized selector

Let me start off by saying I am new to Objective C.
I am getting the error
atusMenuApp[24288:303] -[__NSCFConstantString createListItem]: unrecognized selector sent to instance 0x100002450
Here is my code:
selector = [NSMutableArray arrayWithObjects: #"nvda", #"aapl", #"goog", nil];
[selector makeObjectsPerformSelector:#selector(createListItem:) withObject:self];
- (void)createListItem:(NSString *)title {
//do some stuff
}
Now I have done plenty of looking around and it seems like the biggest reason for this issue is the addition of or lack of the :however I do believe I properly have that in place. Maybe I do not understand the use of makeObjectsPerformSelector very well as after look up the doc on it I found:
Sends to each object in the array the message identified by a given selector, starting with the first object and continuing through the array to the last object.
Any help would be great, Thanks!
[Only if you read the documentation (or thought a bit about why a method is named this way and not that), or even made the effort trying to understand the error message...]
The makeObjectsPerformSelector:withObject: method of NSArray does what it suggests it does: it makes the objects of the array perform the selector, that can have an optional argument. So
[selector makeObjectsPerformSelector:#selector(createListItem:) withObject:self];
will send the createListItem: message to every single NSString object in the selector array and pass in self as its argument. It won't perform the selector on self passing in the object. I. e., what you have is equivalent to
for (NSString *obj in selector) {
[obj createListItem:self];
}
Obviously, instead of this, you want the following:
for (NSString *obj in selector) {
[self createListItem:obj];
}
You don't even need that nasty method for this. A nice fast enumeration for loop will do it.
First you make an array of NSStrings. Then, you send them all the message createListItem. That's all fine and dandy, but NSString doesn't have any method called createListItem; just because you've defined an instance method called createListItem doesn't mean every instance of every class can use it. Only the class who's implementation file has the definition will be able to handle the message. For instance, I can't make a list of Car instances, then define the method fly in another class called Helicopter's implementation and expect to be able to call fly on an instance of Car; only Helicopter can use it. I recommend you read a good book on Objective-C and further familiarize yourself with classes, instances and instance methods.
You misunderstood the method.
It will call the method createListItem: with argument self over every object of the NSArray.
So the resulting call would be something like:
[#"nvda" createListItem:self];
...
Clearly that method doesn't exist for a NSString and there goes your exception.
If you need to apply a method of self to every object inside your array, simply loop through it.

setTag: in Objective-C

How do you call setTag: for id type objects?
Also, if you don't want your compiler complaining and you know the type of the object, you can cast it.
[(UILabel *)objById setTag:5];
id is not exactly a class. It's a generic type which is used in Objective C for arbitrary object. E.g., method taking any object could look like this
-(void) doIt(id parameter);
So, saying that your object has type id adds no information.
But if you're absolutely certain your object responds to setTag, you can just do it: [object setTag:123].
Otherwise, just lookup documentation for the object's classs.
setTag should be assumed to be a method that belongs to UIView subclasses. If you are certain that the object in question is indeed a UIView subclass, you would call the method as normal: [someIdInstance setTag:5];
Based on your question, I'd presume you may not understand the significance of the id type. An object of type id may realistically be a pointer to any type of object. You have no guarantee that setTag: may in fact be defined for that object, which is typically why code that deals with id pointers performs steps like this:
id foo = [self getSomePointer];
if([foo respondsToSelector:#selector(setTag:)]) {
[foo setTag:4];
}
The code checks to see if the object even has the method before attempting to call it.
usually it goes like
object-type.tag = value;
like
button.tag = 50;

myView.frame.origin.x = value; does not work - But why?

I know that I can't use this:
myView.frame.origin.x = 25.0;
and that I have to use this instead:
CGRect myFrame = myView.frame;
myFrame.origin.x = 25.0;
myView.frame = myFrame;
And I'm doing it all the time, but I don't know why I must do it that way. I would like to fill that gap in my understanding. Can someone explain ?
Nowadays Xcode gives you "Expression not assignable". Some time ago you got a compile error "Lvalue required as left operand of assignment".
There are two distinct dot syntaxes being used here. They look the same, but they do different things depending on what they are operating on and what is being done with it:
The first myView.frame is a shorthand for [myView frame], a method call that returns a CGRect struct by value.
myFrame.origin.x is accessing ordinary struct members in the traditional C fashion.
The second myView.frame is again a shorthand, but because the statement is an assignment it translates to calling a different method, [myView setFrame:myFrame].
In your single-line top example, you get a copy of the rect and set its x, but never copy it back to the view. You have to explicitly differentiate between the method calls, the dot syntax sugar can't magic them into a single call.
The reason this does not work is due to the mixing of two syntaxes.
First you have "." as a shortcut for calling the accessor functions (an Objective-C feature).
So
a.b becomes [a getB];
a.b = 5 becomes [a setB:5];
And then theres "." as direct struct member access (pure C). So
a.b really is a.b;
a.b really is a.b = 5;
Combining this in a set-value-case like this, doesn't work.
Because ...
If you could call
myView.frame.origin.x = 25.0;
The "myView.frame" part equals [myView getFrame] and you get a copied CGRect frame (a C struct)
The "myView.frame.origin" gives you a CGPoint origin (also a struct) of the copied CGRect
The "myView.frame.origin.x = 25.0" gives you a CGFloat x of the origin and now you want to assign something to it and here comes the problem...
You try to set a variable of a struct of a struct, which is ok, but there is no pointer from the UIView to the struct, so it is copied instead. So you copy and then you set and then you expect that the set action is somehow forwarded through the initial get to the UIView, well and this just doesn't work.
Of course you could wonder why Apple hasn't just created a shortcut, so that in the end your copied frame is automatically reinjected into a auto appended setFrame call, I guess you just have to live with how it is.
So remember, it would work if you'd get a pointer to the frame, but you don't, you get a copied struct instead.
So if you expect myView.frame.origin.x = 25.0 to work you indirectly expect your call to be automagically translated into some sort of
[myView setFrame:[myView getFrame:frame].origin.x = 25.0].
Well I guess you can admit that this looks wrong.
Also imagine if you'd get a direct pointer to the CGRect frame and you'd change something through that pointer, how would the UIView know that it's size has changed and that it has to update itself ? If on the other hand a [myView setFrame:newFrame] call is made, then UIView can do all the necessary readjusting itself.
A CGRect is a struct, which is something from standard C. A CGRect is not an Objective C object, so when you assign to one of its members, no setter method is called. Without a setter method being called, UIKit will not be able to know that anything has changed, and so will not be able to update the screen display.
Edit: as has been pointed out, the assignment will be to a copy of the struct.
When you manipulate the data directly, no accessor is called, so the UI cannot update itself - or inform any other component that wants to know about changes.
Edit: As pointed out by walkytalky, you will get a copy of the data, so changing it doesn't have any effect on the original anyway. The following example will show this:
UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(50,50,100,100)];
NSLog(#"%f", aView.frame.origin.x); // will give 50
aView.frame.origin.x = 17; // operates on a copy of the rect only
NSLog(#"%f", aView.frame.origin.x); // will still give 50

How do I set an uninitialized value in Objective C?

This question might be off base, but here's the problem I'm having:
I'm trying to run an example from the iPhone SDK, and I'm running into an access violation. I've traced down some behaviour that I think is suspicious.
Here's what the class looks like:
#interface TableViewCell : UITableViewCell {
#private
UILabel *_earthquakeLocationLabel;
UILabel *_earthquakeDateLabel;
UILabel *_earthquakeMagnitudeLabel;
UIImageView *_magnitudeImageView;
}
When I set a breakpoint in
- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier
And look at "self" in the debugger, it shows that _earthquakeDateLabel is "0x12", _earthquakeMagnitudeLabel is 0x1000, and the other two are zero. If I try to assign to either of the nonzero ones, I get an access violation.
I'm guessing what's happening is that these have bogus values in them, and when I try to assign to them, that tries to decrement a reference on the bogus value and blows up. But as I said, I'm fairly new to Objective C, so I may be off base.
So my question is, is there anything special about initializing these values that I should be doing? Or any way to assign to the value when it has a bogus value in it?
Some additional information:
So if I assign nil to _earthquakeDateLabel and _earthquakeMagnitudeLabel in initialize, that fixes the problem. But I don't understand why the object is created with values in those fields; I expect them to be nil. The object is being created on the heap:
TableViewCell *cell = [[[TableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
It sounds like the self pointer is bogus. The values 0x12 and 0x1000 are integer constants; they're definitely NOT pointers to valid memory addresses. The fact that you are seeing those values indicates that something is wrong, and attempting to manipulate them in any way (reading or writing them) will result in badness. In this case, you're getting an access violation because you're trying to write to memory addresses 0x12 and 0x1000, which are invalid addresses.
How are you creating the TableViewCell objects? Are you doing TableViewCell *myCell = [[TableViewCell alloc] initWithFrame:...]? That is the correct way.
Now I'm going to invoke my psychic debugger: my guess is that you're forgetting to declare your TableViewCell object with a pointer, i.e. you're declaring it as TableViewCell myCell instead of TableViewCell *myCell (note the presence of the asterisk). This will create the object on the stack instead of the heap, and as a result, it will have garbage in its data members, instead of zeros. Objective-C objects, when properly allocated, will have all of their data members initialized to 0 (or NULL, nil or false, depending on the data type) after a successful call to alloc.