Search code examples
objective-ctypestype-safetymessage-passing

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?


Solution

  • 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) {
            ...
        }
    }