Question

I'm trying to init instances (person1, person2) of a class called XYZperson with a special classmethod (either +(insatncetype)iniPersonDefault: or +(instancetype)initWithFirstName...) in order to init those instances with a firstName and a lastName and dateOfBirth (Do not worry about the date of birth being an NSString object here, I'll change that once it works)

For convenience encapsulating those previous object "variables" in an array as shown in the following code.

I'm doing so because I understood that it is not possible to deal with instance variables in class methods so no @property in the interface file:

.m File:

#import "XYZPerson.h"

@implementation XYZPerson

// init methods

+(instancetype)initPersonDefault
{
    return [self initWithFirstName:@"John" LastName:@"Doe" DateOfBirth:@"1234"];
}

+(id)initWithFirstName:(NSString *)aFristName
              LastName:(NSString *)aLastName
           DateOfBirth:(NSString *)aDateOfBirth
{
    if (self == [XYZPerson class])
    {
        NSString *firstName2 = aFristName;
        NSString *lastName2 = aLastName;
        NSString *dateOfBirth2 = aDateOfBirth;
        NSArray *info =@[firstName2,lastName2,dateOfBirth2];

        //return  [NSString stringWithFormat:@" %@ %@ born in %@", info[0], info[1], info[2] ];
        //        return (NSString *)lastName2;
        //        return (NSString *)dateOfBirth2;
        return (NSArray *)info;
    }
    return [[self alloc]init];
}

// Other methods

-(void)saysomething : (NSString *)greeting
{
    NSLog(@"%@ ", greeting);
}

-(void)sayHello: (NSArray *)infoP
{
    NSString *results =[[infoP valueForKey:@"description"] componentsJoinedByString:@" "];
    [self saysomething:results];
}

.h File:

#import <Foundation/Foundation.h>

@interface XYZPerson : NSObject

-(void)saysomething : (NSString *) greeting;

-(void)sayHello: (NSArray *)infoP;

+ (id)initWithFirstName:(NSString *)aFristName
               LastName:(NSString *)aLastName
            DateOfBirth:(NSString *)aDateOfBirth;

+(instancetype)initPersonDefault;

@end

As soon as I try to access the object variables with the instance methods, I get confused, here is the main.m:

#import <Foundation/Foundation.h>
#import "XYZPerson.h"
#import "XYZShoutingPerson.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        XYZPerson *person1 = [XYZPerson initPersonDefault];
        XYZPerson *person2 = [XYZPerson initWithFirstName:@"Henry" LastName:@"V" DateOfBirth:@"1234"];

        NSLog(@" %@ et %@", [[person1 valueForKey:@"description"] componentsJoinedByString:@" "],
                            [[person2 valueForKey:@"description"] componentsJoinedByString:@" "] );

        [person1 sayHello: person1];
    }
    return 0;
}

Here is my console:

2014-05-11 12:30:28.258 fonctiontest[4317:303] John Doe 1234 et Henry V 1234
2014-05-11 12:30:28.278 fonctiontest[4317:303] -[__NSArrayI sayHello:]: unrecognized selector sent to instance 0x102617a20
2014-05-11 12:30:28.279 fonctiontest[4317:303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI sayHello:]: unrecognized selector sent to instance 0x102617a20'

...

More precisely, I get a warning saying :

Incompatible pointer types sending 'XYZPerson *' to parameter of type 'NSArray *'

So my guess is that person1 has a complex status, it is a instance of the class XYZPerson and it also contains an array. When passing it to pointer infoP in the -(void)sayHello : (NSArray *)infoP method, it doesn't work. I can't figure out what to do for calling this last method on the array in person1? any idea?

I'm a beginner so if the whole logic is messed up, please let me know how.

Was it helpful?

Solution

You can't access instance methods or variables from a class method using self, e.g. [self saySomething:greeting] would not work from within a class method. However, you can certainly access instance members from a class method if that class method is sent an object instance as a parameter, or if it creates a new instance.

It seems that you want to implement class factory methods to create instances of XYZPerson. I suggest reading the documentation on these, as it is an important concept in Objective-C to understand. Actually, I would suggest reading the Defining Classes and Working with Objects sections of Programming with Objective-C now (and eventually the entire document should be read), so you can understand how alloc/init works and how to properly define and work with your objects.

Here is a suggested class definition for your .h file, annotated with notes:

@interface XYZPerson : NSObject

// 1
+ (instancetype)person;

// 2
- (id)initWithFirstName:(NSString *)aFirstName
               lastName:(NSString *)aLastName // 3
            dateOfBirth:(NSString *)aDateOfBirth;

// 4
@property (copy, nonatomic) NSString *firstName;
@property (copy, nonatomic) NSString *firstName;
@property (copy, nonatomic) NSString *dateOfBirth;

- (void)saysomething: (NSString *) greeting;

// 5
- (void)sayHello;

@end

Notes:

  1. Renamed initPersonDefault to just person, to follow one of the naming conventions for class factory methods. Methods starting with init should always be instance methods
  2. Changed initWithFirstName... to an instance method (+ to -)
  3. Fixed capitalization of the selector
  4. The revised init method is going to return an instance of XYZPerson, not NSArray. The values it was given are stored as properties of the XYZPerson instance
  5. Not sure what the implementation of sayHello is, but now that we have properties defined for name/DOB, the sayHello method can access all this data via the self pointer

Implementation:

@implementation XYZPerson

+ (instancetype)person
{
    // 1
    return [[self alloc] initWithFirstName:@"John"
                                  lastName:@"Doe"
                               dateOFBirth:@"1234"];
}

- (id)initWithFirstName:(NSString *)aFirstName
               lastName:(NSString *)aLastName
            dateOfBirth:(NSString *)aDateOfBirth
{
    // 2
    if (self = [super init]) {
        // 3
        self.firstName = aFirstName;
        self.lastName = aLastName;
        self.dateOfBirth = aDateOfBirth;
    }
    return self; // 4
}

- (void)sayHello
{
    // 5
    NSLog(@"Hello, I am %@ %@", self.firstName, self.LastName);
}

...
@end
  1. Class factory methods typically use [[self alloc] init...] to create an object that is an istance of that class, and then pass given or default data to the init method as needed to initialize that object, then return that instance. Note here that since this is a class method, self refers to the XYZPerson class, not an XYZPerson object, thus [self alloc] is sort of like [XYZPerson alloc] (ignoring the possibility of subclasses), which is how we are creating an returning an XYZPerson instance
  2. The if (self = [super init]) check is an important pattern, it calls the init method in the base class (NSObject here) and ensures it did not return nil
  3. Here we are using the properties defined in the .h file. We are storing copies of first name/last name/DOB with the instance of XYZPerson that is being initialized
  4. Returning an XYZPerson object, not NSArray
  5. Sample implementation of sayHello. This method no longer needs an argument because it can access the name and DOB properties via self

Main.m:

#import <Foundation/Foundation.h>
#import "XYZPerson.h"
#import "XYZShoutingPerson.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {

        // 1
        XYZPerson *person1 = [XYZPerson person];
        // 2
        XYZPerson *person2 = [[XYZPerson alloc] initWithFirstName:@"Henry" LastName:@"V" DateOfBirth:@"1234"];

        // 3
        NSLog(@" %@ et %@", person1.firstName, person1.lastName, person1.dateOfBirth);

        // 4
        [person1 sayHello];
    }
    return 0;
}
  1. Using the renamed factory method here
  2. Use [[XYZPerson alloc] init...] to properly create an instance of XYZPerson and use its initializer. Note the similarity between this code and the implementation of the +person method above
  3. Now you can access firstName, lastName, dateOfBirth using dot notation. You'll know it's working properly because Xcode will auto-complete the property names after you type the dot
  4. sayHello is a message sent to the person1 instance. This invokes the sayHello method on the person1 object, which is how that method has access to firstName, etc., without passing an additional argument here

A note about the errors you got in your original post: since you are returning an NSArray rather than XYZPerson object from that init method, you get the unrecognized selector sent to instance error when sending the sayHello message to the person1 variable, because person1 is pointing to an NSArray which has no implementation of sayHello.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top