Why does calling a property on NSObject pointer gives build errors? - objective-c

I have an NSMutableArray which returns me some object.
The object which I added had properties name,age.
now when I use these properties on the Object returned (obj.name or obj.age ),
Compiler says, no such member, use (->) instead of (.)
I understand that NSObject wont have these members and hence it wont understand the property.
But If i use setters, and getters as method ([obj name] or [obj age]) syntax instead of this properties, I dont get any errors.
But using property means calling a setter or getter only ?
ad Objective C is suppose to be dynamic language, right ?

Do you cast the returned object to your object type (MyObject)?
You should do something like:
((MyObject*)[mutableArray objectAtIndex:0]).age = 20;
The reason you're not getting any errors when using [[mutableArray objectAtIndex:0] name] syntax is that you're calling a method on the returned object (which is of type id), and id s tend to not choke in the compile-time if you call a (yet) non-existant method on them. At the run-time, [mutableArray objectAtIndex:0] might resolve to type MyObject an in that case, the message [obj name] has a proper implementation (IMP). If it doesn't resolve to MyObject, your app will crash.
And note that the reason you're not even getting a compile-time warning is that Xcode knows that there is at least 1 class in your codebase that implements the method name, and it trusts you with calling this method only on instances of that class. if you do something like ((MyObject*)[mutableArray objectAtIndex:0]).ageeeeee = 20;, it'll give you a warning as there's a very good chance that it'll crash (no class in your app implements the method ageeeeee statically).
The type id does not have a property name, and that's why you can't use dot syntax.
Actually, this incident shows perfectly why ObjC is called a dynamic language!

That's right - dot syntax is not supported in such case.
You need to cast a pointer to the actual class:
((MyObject*)[array objectAtIndex: 0]).name = #"Bill";

Related

Whats the difference between setting a property in an object through dot notation vs setter?

Looks like there is some subtle difference between setting a property through dot notation vs setter.
In my objective-C code, I have a property that can be set to either an instance of class A or class B. So I declare it as #property id delegate
Both class A and class B have same properties and methods. Just the implementation (functionality) is different if you call methodM1 on one vs the other.
I see that following lines don't work (using property directly through dot notation)
self.delegate = [[Class A alloc] init];
self.delegate.property1 = #"ABCD" ; //Does not work, get an error that property1 not defined on id
But following line works
[self.delegate setPropert1:#"ABCD"] ; //works
What is the difference? Is it that in the setter case, it knows that there is at one class that responds to setProperty1 method so compiler allows it? if yes, it looks like the answer to Q below (which was also my original understanding) is incorrect. I.e dot notation does not get replaced by setter.
Difference between setting a property directly and using its setter?
The problem is that self.delegate is typed as id. You can send any message to an id, so it is legal to say
[self.delegate setProperty1:#"ABCD"];
The compiler just lets go of all control and lets you crash the program if this message doesn't work out, because this is an id.
But for properties there is no such loosening of control. To use a property, you must have an actual type for which that property is defined. Thus you could say
((ClassA*)self.delegate).property1 = // ...
Or
((ClassB*)self.delegate).property1 = // ...
But you can't say
self.delegate.property1 = // no, because self.delegate is an `id`
However, this is just a matter of notation. Even if you could say self.delegate.property1 this would be just another way of saying [self.delegate setProperty1:], so nothing is lost just because you can't use that notation.

Xcode doesn't recognize properties after assigning to id

I have an NSMutableArray of two different objects inside. I am trying to recognize the first object of the array and assign it properly. Here is the example code with my idea of
// I should declare a variable here, firstly I thought about "id someObject;"
// and assigning to it in if statement.
id someObject;
if ([[someArray objectAtIndex:0] isKindOfClass:[firstOpponent class]]) {
someObject = (firstOpponent*)[someArray objectAtIndex:0];
} else {
someObject = (secondOpponent*)[someArray objectAtIndex:0];
}
[someObject method]; // this is OK
someObject.position; // property 'position' not found on object of type '__strong id'
With the idea of declaring "id someObject;" before if statement there is some problem with properties. I read that it isn't possible, thats why I'm asking for other solutions.
Your if statement achieves nothing in respect of typing.
In Objective-C a cast on a reference type, such as (firstOpponent *), does nothing at runtime; it simply allows the compiler to produce better error messages.
In your code you cast, which tells the compiler the type of the reference, and then you immediately assign to a variable of type id - which is the most general/least specific object reference type - and the compiler now knows nothing about the contents of someObject other than it contains a reference to some object.
So your code is equivalent to:
id someObject;
someObject = someArray[0];
[someObject method]; // this is OK
someObject.position; // compile time error
When calling a method on a reference typed as id, in your case someObject, the compiler does no checking and simply allows the method call. At runtime a check is done to verify the actual object references supports the method, and if not a runtime error will occur and the application will be aborted.
However the compiler will only call a property on a object whose type it knows. This is because it needs to know the type to determine what method call to translate the property access into. In general, but not always, the property access:
object.property // read a value
object.property = value // write a value
translate into the method calls:
[object property]
[object setProperty:value]
So you can access the property by doing the translation yourself and writing one of the second pair. If at runtime the reference object does not support the property then you will get an error and your application will abort.
Another option is to define a protocol, say OpponentProtocol, which declares the methods and properties all opponent classes should implement, and then have both your opponent classes implement it. You may then declare:
id<OpponentProtocol> someObject;
where the type means "any object reference as long as it implements the protocol OpponentProtocol". With such a type the compiler knows how to translate a property access into the appropriate method call, so you can access OpponentProtocol properties on someObject.
HTH
You should create a protocol that your two classes can confirm to and use id < MY_PROTOCOL > instead of just id so the compiler knows what the class is capable of responding to.

Dot syntax with 'id' properties

Recently i asked question about accessing properties of id object assigned to custom class. Solution is casting or using setter/getter methods.
But, i can't understand one thing, why i can't use dot syntax?
Compare that two lines of code:
myClass.var
[myClass var]
Result is identical, and in fact, it similar. But, if u do:
id obj = myClass;
obj.var; // Error - trying to get value
obj.var = 5; // Error - trying to set value
Its not real error message, i just want to show you that warnings are appear and it crash a build.
But, if u do:
id obj = myClass;
[myClass setVar:5]; // No error - setter
[myClass var]; // No error - getter
No warning, app work fine.
How this could happen? Yes, i can use casting or getting/setting values like described above, but why dot syntax not work?
id is "any kind of object", which means it doesn't have any property named var so you can't access it using dot notation, because var it's not a member of class id.
Now, if you use the 'messaging' pattern [myClass var] you are sending a message and since message dispatching in objective-c is a runtime functionality and dynamic typed, it won't complain at compile time, because the compiler doesn't know whether myClass responds to the selector var (in this case 'has a property named var') or not until it actually tries to dispatch the message to the object.
Hope it helps.

How is type safety possible for an object pointer of id?

I have the following class (picked out of a Apple example):
#interface LeTemperatureAlarmService : NSObject <CBPeripheralDelegate>
#property (readonly) CBPeripheral *servicePeripheral;
#end
and in a different class's method I use the following code:
NSMutableArray *connectedServices = [[NSMutableArray alloc] init];
... // Adding some myService objects to connectedServices
for (id service in connectedServices) {
if ([service servicePeripheral] == parameter) {
...
}
}
Now the thing which drives me crazy is the part where I can send servicePeripheral to service.
As far as I understand how id works, it's basically a pointer which can be literally point to any object. My NSMutableArray is an untyped array which can hold any type of object in it, even mixed, so I don't have to be careful what I put in.
So how can it be that I can use [service servicePeripheral] even though I never specified the type of service? And how does Xcode know that and even suggest that method in code completion?
Objective-C works different in the respect of method invocation than say C++. The compiler doesn't have to know, because it's not done at compile time, methods are invoked at runtime. Specifically, methods are send to objects, instead of called on them. You send the servicePeripheral method to the object and the runtime takes care of calling the right function. This also makes it possible for you to send methods to nil without crashing (it will return false/0/NULL)
Types in Objective-C are mostly used for compile time safety, which you lose with your approach. The compiler can't warn you that the types don't match, for instance, your array can very well contain NSString instances or anything, and the compiler can't help you there since you tell it that you expect id (aka anything, really) and servicePeripheral is a perfectly valid and known method. You can add type safety by checking the class of the object at runtime using isKindOfClass:, for example like this:
for (id service in connectedServices) {
if ([service isKindOfClass:[LeTemperatureAlarmService class]] && [service servicePeripheral] == parameter) {
...
}
}
So how can it be that I can use [service servicePeripheral] even though I never specified the type of service?
It is exactly because you declared service as an id. This tells the compiler to turn off all static type-checking and permit you to send any message to service. That is what id is: it is the universal recipient (any message can be sent to it, any object value can be assigned to it) and the universal donor (it can be assigned to any object variable).
And you are perfectly right to be wary of this, since it can cause you to crash later. It is not (as your question title has it) "type safety". It is type unsafety! The compiler will happily let you say (for example) [service count] (because service is typed as an id), but you will crash later when the app runs, because this object does not respond to the count message.
So don't do that! Use explicit types so the compiler can help you in advance.

Should I be casting when returning id from an objective-c method or not?

For the Objective-C gurus:
Suppose I have a simple method like so:
-(id)getValue{ return [NSNumber numberWithDouble:5.0]; }
Now, suppose within some other method I call the (id)getValue method like so:
NSNumber* myValue = [self getValue];
or what if I call it like this instead:
NSNumber* myValue = (NSNumber*)[self getValue];
The question is: Obviously these lines are equivalent but one of them utilizes an explicit cast. So what is the correct or best-practice way of doing this. It seams to me the cast is unnecessary since when it is placed in the pointer myValue, it will be type-safe at this point anyways (which is something I want) so the cast is basically pointless.
Let me just add that I'm sure people will point out: Why don't you just return (NSNumber*) from the getValue method but in my case I want to have the flexibility to return whatever I want much like the built in NSDictionary class returns id when you call: objectForKey because it allows you to place any type of NSObject or subclass inside of it. In other words my getValue method will not always be returning an NSNumber. Also consider this example is contrived because I am just concerned about whether to cast or not.
Thank you in advance,
-Ralph
The only reason to cast objects is to make the compiler happy. (Sometimes it also helps readability.) For example, you have to cast when making a property access directly on an object you're getting out of an array or dictionary:
((Foo *)[myArray objectAtIndex:0]).bar;
If you don't do the cast, the compiler can't do the property lookup, and will complain.
When you're getting an object from a method that returns id, it's impossible for the compiler to know what its actual type is. There isn't really any "type-safety", because id is a generic pointer; all the compiler can and will enforce is that the method says it returns some Objective-C object. It is perfectly happy to assign a generic pointer to any typed pointer.* (This is actually an advantage for containers, obviously.) Since the type of the variable to which you're assigning already documents the actual return type, I'd say there's no need for the cast.
As an aside, you shouldn't be calling your method getX. That has a specific meaning in Cocoa; methods which "get" something pass in a pointer to a pointer, which is then filled by the method. See -[NSArray getObjects:range:] as an example.
*The type will be enforced at run-time, of course, in the sense that sending messages to which the object does not respond will cause an error.