Objective-C Multiple Initialisers

2019-04-18 17:00发布

问题:

I have a simple question about creating multiple initialisers within an objective-c class. Basically I have a class that represents a single row in my database (users). I currently have an initialiser which initialises the class based upon the users UserID (which is also the primary key within the database), when passed the UserID the class will them connect to a webservice parse the results and return an object initialised to the corresponding row in the database.

Within this database are a number of unique fields (username and emailaddress), I would also like to be able to initialise my object based upon these values. But I am unsure of how to have more than one initialiser, everything I have read states that I am free to have multiple initialisers, as long as each calls the designated initialiser. If someone could help me out with this, that would be great.

My initialiser code is as follows:

- (id) initWithUserID:(NSInteger) candidate {
    self = [super init];
    if(self) {
        // Load User Data Here
        NSString *soapMessage = [NSString stringWithFormat:
                                 @"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                                 "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
                                 "<soap:Body>\n"
                                 "<GetByUserID xmlns=\"http://tempuri.org/\">\n"
                                 "<UserID>%d</UserID>\n"
                                 "</GetByUserID>\n"
                                 "</soap:Body>\n"
                                 "</soap:Envelope>\n", candidate
                                 ];
        NSLog(@"%@",soapMessage);

        // Build Our Request
        NSURL *url = [NSURL URLWithString:@"http://photoswapper.mick-walker.co.uk/UsersService.asmx"];
        NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
        NSString *msgLength = [NSString stringWithFormat:@"%d", [soapMessage length]];

        [theRequest addValue: @"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
        [theRequest addValue: @"http://tempuri.org/GetByUserID" forHTTPHeaderField:@"SOAPAction"];
        [theRequest addValue: msgLength forHTTPHeaderField:@"Content-Length"];
        [theRequest setHTTPMethod:@"POST"];
        [theRequest setHTTPBody: [soapMessage dataUsingEncoding:NSUTF8StringEncoding]];

        NSError *WSerror;
        NSURLResponse *WSresponse;

        webData = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:&WSresponse error:&WSerror];

        xmlParser = [[NSXMLParser alloc] initWithData: webData];
        [xmlParser setDelegate: self];
        [xmlParser setShouldResolveExternalEntities: YES];
        [xmlParser parse];
    }
    return self;
}

Following from Laurent's comment, I have tried to implement my own solution, I would be grateful if you could inform me of any obvious gotcha's with this solution:

I am not totally sure I understand you're meaning, I have tried to implement my own solution. I would be grateful if you could let me know what you think:

- (id) init {
    self = [super init];
    if(self){
        // For simplicity I am going to assume that the 3 possible
        // initialation vectors are mutually exclusive.
        // i.e if userName is used, then userID and emailAddress
        // will always be nil
        if(self.userName != nil){
            // Initialise object based on username
        }
        if(self.emailAddress != nil){
            // Initialise object based on emailAddress
        }
        if(self.userID != 0){ // UserID is an NSInteger Type
            // Initialise object based on userID
        }
    }
    return self;
}
- (id) initWithUserID:(NSInteger) candidate {
    self.userID = candidate;
    return [self init];
}
- (id) initWithEmailAddress:(NSString *) candidate {
    self.emailAddress = candidate;
    return [self init];
}
- (id) initWithUserName:(NSString *) candidate {
    self.userName = candidate;
    return [self init];
}

Regards

回答1:

The designated initializer's definition is here. It is a strong requirement to ensure that your instances are consistent whatever the initializer you use. For a full reference, see the Objective-C Programming Guide: the initializer implementation is well documented.

Edit 2: Fix typo reported by @NSResponder

Edit:

I think calling init after setting the member is not reliable. Members may have weird values that will fail the test for initialization.

A better way to do it is to call the "init" method first (which will set default values for members) and then to set the member. This way, you have the same code structure for all your initializers:

- (id) init {
    self = [super init];
    if(self){
        self.userID = 0;
        self.userName = nil;
        self.emailAddress = nil;
    }
    return self;
}

- (id) initWithUserID:(NSInteger) candidate {
    self = [self init];
    if(self){
        self.userID = candidate;
    }
    return self;
}


回答2:

The way I tend to do this kind of thing is for the designated initializer to be the method that takes the most parameters, and the simpler initializers just send the more detailed -init messages. For example:

   // simple initializer
- (id) init
  {
  return [self initWithWidth: 1.0];
  }

  // not so simple initializer
- (id) initWithWidth:(float) aWidth
  {
  return [self initWithWidth:aWidth andColor:nil];
  }

  // designated initializer.  This is the one that subclasses should override.
- (id) initWithWidth: (float) aWidth andColor: (NSColor *) aColor 
  {
  if (self = [super init])
   {
   self.width = aWidth;
   self.color = aColor ? aColor : [[self class] defaultColor];
   }
  return self;
  }