3

I get no errors from this code:

 NSArray* animals = [NSArray arrayWithObjects:
                    [[Dog alloc] init],
                    [[Cat alloc] init],
                    nil];

for (Dog* dog in animals) {
    if ([dog respondsToSelector:@selector(bark)]) {
        [dog bark];
    }

}

Here are the Dog and Cat classes:

//
//  Dog.h
//  Test1

#import <Foundation/Foundation.h>

@interface Dog : NSObject

- (void) bark;

@end

...

//
//  Cat.h
//  Test1
//

#import <Foundation/Foundation.h>

@interface Cat : NSObject

@end

...

//
//  Cat.m
//  Test1
//

#import "Cat.h"

@implementation Cat

@end

And, I only get a warning if I do this:

Dog* myDog = [[Cat alloc] init];
7stud
  • 46,922
  • 14
  • 101
  • 127
  • The array iterator returns objects of type `id` which can be assigned to anything (`Dog`) in your case. – rmaddy Jun 07 '15 at 01:46
  • @rmaddy, Okay, thanks. Next, why can I implement a bark() method in the Cat class and then do this: `Dog* myDog = [[Cat alloc] init]; [myDog bark];` Same reason? init() in NSObject returns an id type? Yep: `- (instancetype)init`. How about posting your comment as the answer? – 7stud Jun 07 '15 at 01:53
  • The assignment of a `Cat` object to a `Dog` variable shouldn't compile (or at least give a warning which shouldn't be ignored). But despite `myDog` being declared as a `Dog` variable, it actually points to a `Cat` object at runtime so you can call any `Cat` method without issue. – rmaddy Jun 07 '15 at 01:57
  • 1
    Best practice is not to mix instances of different types in in an NSArray. – zaph Jun 07 '15 at 02:48

1 Answers1

2

It appears you got most your questions answered in the comments but I thought I would write up an answer to to fill in any missing gaps.

NSArray takes in an id and you can pass in any NSObject subclass you want and the complier will allow it. This is NOT RECOMMENDED but can be done.

NSArray* animals = [NSArray arrayWithObjects:
                    [[Dog alloc] init],
                    [[Cat alloc] init],
                    @"This is a string",
                    @(42),
                    nil];

Even cooler is that when you pull the items out of an array they come out as an id. This gets casted to whatever variables you are using.

Dog *dog = animals[0];
Cat *cat = animals[1];

The compiler will not freak out if you do this.

NSString *aString = animals[0];
NSLog(@"%@", aString);
NSLog(@"%@", [aString lowercaseString]);

This is what you get in the log...

2015-06-07 08:55:53.715 DogsAndCats2[5717:4029814] <Dog: 0x7fe790c71b80>
2015-06-07 08:55:53.715 DogsAndCats2[5717:4029814] -[Dog lowercaseString]: unrecognized selector sent to instance 0x7fe790c71b80

You are casting an id to a NSString which is allowed however, you are trying to call a method that does not exist on a Dog and will crash on the second NSLog. Note the first log says it is still a Dog even though it is casted as a NSString.

Now if we add a property to your Dog.h we can see some neat stuff in the for loop.

Dog.h

#import <UIKit/UIKit.h>

@interface Dog : NSObject

@property (nonatomic, strong)NSString *dogName;

-(void)bark;

@end

Now when we loop consider the following.

for (id thing in animals)
{
    if ([thing respondsToSelector:@selector(bark)])
    {
        //You are allowed to send messages to id
        [thing bark];
        [thing setDogName:@"Lassie"];

        //compiler error because it doesn't know it is a Dog class
        //thing.dogName = @"Lassie";

    }

    if ([thing isKindOfClass:[Dog class]])
    {
        [thing bark];

        //this is safe because we checked the class first and now we can cast it as so
        Dog *dog = (Dog *)thing;
        dog.dogName = @"Lassie";
    }

}

Hopefully that answers more of your questions and isn't too confusing.

Skyler Lauren
  • 3,792
  • 3
  • 18
  • 30
  • Thanks for the response. `thing.dogName = @"Lassie"; //compiler error because it doesn't know it is a Dog class`. Hmmm...I thought that was semantic sugar for `[thing setDogName:@"Lassie"]`. – 7stud Jun 07 '15 at 16:19
  • They do indeed mean the same thing, but it is just what the compiler will and won't allow. You can use the method directly on id as long as something that is imported has that method. `[thing addSubview:self.view]; ` The compiler will allow but will crash hard. `addSubView:` comes from UIKit and the complier knows that some objects can use that and id is "some object" to the compiler. – Skyler Lauren Jun 07 '15 at 16:33
  • @7stud you might also find this link helpful. http://stackoverflow.com/a/1208421/851041 – Skyler Lauren Jun 07 '15 at 20:38