1

I make class factories like so,

@implementation Universe {
    NSString *foo;
}

+ (instancetype)universeWithMeaning:(NSString *)meaning
{
    return [[self alloc] initUniverseWithMeaning:meaning];
}

- (id)initUniverseWithMeaning:(NSString *)meaning
{
    if (self = [super init]) {
        foo = meaning;
    }
    return self;
}

- (void)showMeaning
{
    NSLog(@"%@", foo);
}

@end

And create object like this,

Universe *universe = [Universe universeWithMeaning:@"42"];
[universe showMeaning];     // Prints 42

This works great, but the method signature of initUniverseWithMeaning: is the same as that of universeWithMeaning:, except that it's an instance method which allows it to save instance variables to the created object.

Is there a way to this without having to implement the initUniverseWithMeaning: instance method?

I know its necessary to be inside of an instance method to be able to access instance variables, so I've been experimenting with blocks. My idea was to pass a block containing instance variable assignations to the class method which would somehow execute it in the instance context.

Implementation,

@implementation Cat {
    NSString *lives;
}

+ (Cat *)newCat:(void(^)(void))cat
{
    cat();    // **Problem 1**
}

- (void)showLives
{
    NSLog(@"%@", lives);
}

@end

Usage,

Cat *cat = [Cat newCat:^void (void) {
    self.lives = 9;    // **Problem 2**
}];

[cat showLives];       // I'd like this to print 9

Problem 1: How to create a Cat object and execute cat() inside it?

Problem 2: How to make self refer to the object in the block's execution environment?

Anyway, this is more of a curiosity than anything else, it's would only be practically useful to save me from writing alloc (I would just need to include a method prototype for initUniverseWithMeaning: in the .h file.)

paulvs
  • 11,963
  • 3
  • 41
  • 66
  • do you know [properties](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html)? – vikingosegundo May 25 '14 at 22:33
  • I guess I've never tried to access instance vars from inside a class method, but I don't know that you can't. Have you tried simply `Universe* newObj = [Universe new]; newObj ->foo = meaning;`? – Hot Licks May 25 '14 at 22:43
  • I just tried now and it works, I need to read up about that notation for accessing variables. – paulvs May 25 '14 at 23:02
  • @vikingosegundo I do know about properties, but I don't think that using them would make any difference here. – paulvs May 25 '14 at 23:05
  • I don't get what you want. – vikingosegundo May 25 '14 at 23:20
  • @vikingosegundo, I wanted to create an instance of a class using a class method that accepts a parameter and assigns it to an instance variable. The `->` notation that @Bryan Chen posted works perfectly. – paulvs May 26 '14 at 02:17
  • I dont understand why you aren't using private properties. – vikingosegundo May 26 '14 at 02:27
  • @vikingosegundo After reading about class extensions I realize I understood very little about how properties, ivars, public and private methods work. Thanks for following up about this. I thought that a class extension contains variables that are private (only accessible within the .m file and only within `-` instance methods). But after reading all the comments I experimented and to my surprise this was not true. I can access class extension properties (using `.` syntax) and class extension ivars (using `->` syntax) from within a `+` class method. I've still got a long way to go after all. – paulvs May 26 '14 at 02:52
  • an ivar should be accessible simply by it's name. – vikingosegundo May 26 '14 at 05:23
  • You should never assume you have anything less then "a long way to go after all". – nhgrif May 26 '14 at 12:11

2 Answers2

2

For your problem 1 and 2, you can try this

@interface Cat ()
@property (strong) NSString *lives;
@end

@implementation Cat

+ (Cat *)newCat:(void(^)(Cat *me))cat
{
    Cat *newcat = [[self alloc] init];
    cat(newcat);
    return newcat;
}

- (void)showLives
{
    NSLog(@"%@", lives);
}

@end

Cat *cat = [Cat newCat:^(Cat *me) {
    me.lives = 9;
}];

[cat showLives];       // print 9

but I can't see much use of it... Isn't this simpler?

Cat *cat = [Cat new];
cat.lives = 9;
[cat showLives];

For your real problem

Is there a way to this without having to implement the initUniverseWithMeaning: instance method?

+ (instancetype)universeWithMeaning:(NSString *)meaning
{
    Universe *universe = [[self alloc] init];
    if (universe) universe->foo = meaning;
    return universe;
}
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • Thanks for the reply. In the first section of your answer, I suppose you mean that the `lives` instance variable is put in the .h file so that it is accessible in the block instantiation `me.lives = 9;`. The problem with this is that it makes `lives` a public variable, and I don't want any of the variables public. – paulvs May 25 '14 at 22:40
  • @PaulVon I mean make it public property. But if you want to make it private, it is private. No code outside the class can use it (without some hack code). You can't have magical block that can access some private property/variable – Bryan Chen May 25 '14 at 22:43
  • I ran your last block of code and it worked (unexpectedly for me!) I even see now that using the `->` notation is the suggested help from Xcode. Would you mind telling me if there is any downside to using this technique? – paulvs May 25 '14 at 22:48
  • I still don't understand how you example with the blocks would work if the instance variables were private. – paulvs May 25 '14 at 22:57
  • `self->foo` is used to access instance variable `foo`, and `self.foo` is used to access property. they are different thing (which modify the same underlying variable) – Bryan Chen May 25 '14 at 23:41
1

The first example you've posted is the correct way of creating Objective-C factory methods.

An Objective-C factory method is nothing more than a class method wrapper around an instance level init method. Generally speaking, every factory method should have a paired init method that takes the same number and type of arguments.

fooWithBar:(NSString *)bar should be paired with initWithBar:(NSString *)bar, etc.

An exception might come in when you have an init method that takes arguments, but you've create a handful of factory methods with default arguments for this method. For example:

- (instancetype)initWithString:(NSString *)string;

+ (instancetype)fooWithString:(NSString *)string {
    return [[self alloc] initWithString:string];
}

+ (instancetype)fooWithBar {
    return [[self alloc] initWithString:@"bar"];
}

Now, you can create the object with in the method, then modify it, and return the modified object.

For example:

+ (instancetype)fooWithString:(NSString *)string {
    Foo *f = [[self alloc] init];
    f.str = string;
    return f;
}

But honestly, it's just better to have an initWithString: method.

Every class should have a designated initializer and every object of that class should go through the designated initializer.

nhgrif
  • 61,578
  • 25
  • 134
  • 173