3

I am trying to use reflection to override the getter method for my properties and create lazy initialization. how can I do that?

// This should be a public method maybe a category to NSObject
- (void)overrideGetterMethod
{
   //somehow convert my original getter with lazyInitialize method
}

- (id)lazyInitialize
{
   if (!self)
   {
      self = [[self class] alloc] init];
   }
   return self;
}

Above is the implementation of the functionality, and below is the use of it

@property (nonatomic, retain) SomeObject *someObject;

- (void)viewDidLoad
{
   [super viewDidLoad];
   [someObject overrideGetterMethod];

   // So when I call the following method if the object is nil it get's instantiated automatically
   [self.someObject doSomething]
}
aryaxt
  • 76,198
  • 92
  • 293
  • 442
  • did you mean `-(id)lazyInitialize;` ? –  Jul 07 '11 at 18:14
  • Well, what have you got so far and what has you stumped? –  Jul 07 '11 at 18:15
  • I have not got anything yet, I don't even know where to start. The code I have just shows what I am trying to achieve – aryaxt Jul 07 '11 at 18:16
  • could you clarify a bit what you're trying to do? do you have object a that is extended in object b, and you want the property method of b to route to the lazyloader? – Blitz Jul 07 '11 at 18:20
  • I am making my code testable by creating lazyinitializer methods. So this way I can inject mocks into my classes. I am trying to avoid writing lazy initializers for every property, and create a general way to do this by simply calling a method – aryaxt Jul 07 '11 at 18:29
  • This is an interesting question, thank you. – Justin Jul 07 '11 at 18:31

3 Answers3

1

You seem to be adopting an odd pattern, and I can't help thinking that you want to achieve is probably better done by some higher level route, such as simply:

if(result) return result;

/* stuff to fill result and then return it here */

Branch prediction should give that a negligible runtime cost.

That caveat being stated, the things you're interested in are method_setImplementation (to set the IMP for a Method), class_getMethodImplementation (to get an IMP from a class and selector) and and class_getInstanceMethod (to get a Method from a class and selector). Using those, you can set a given method to respond to whatever selector or selectors you want at runtime.

NSObject provides some convenience methods for getting the IMP and the Method, but they don't end up being any shorter.

Tommy
  • 99,986
  • 12
  • 185
  • 204
1

Maybe this could be a start:

@interface NSObject (LazyKVC)
- (id)lazyValueForKey:(NSString *)key;
@end


@implementation NSObject (LazyKVC)

- (id)lazyValueForKey:(NSString *)key {
    id value = [self valueForKey:key];
    if (!value) {                                   
        // Now, look for the type of property named |key|.
        // To spare you the details of the @encode() string, use
        // [self typeOfPropertyNamed:key] - see link provided below

        NSString className = [NSString stringWithUTF8String:
                                 [self typeOfPropertyNamed:key]];

        Class propertyClass = NSClassFromString(className);

        if (propertyClass) {
            value = [[propertyClass alloc] init];
        }
    }
    return value;
}

@end    

Note:

  1. The code above uses - (const char *)typeOfPropertyNamed:(NSString *)name, available here.
  2. I omitted corner cases (dealing with errors, etc.).

Usage would be:

[foo lazyValueForKey:@"bar"];

I'm curious if you can make it work like that (I wrote this in the browser window directly...)

Note about (dependency) injection

In order for your method to be able to return the object you want at runtime (via injection) you would need - as a starting point - the following:

  1. a configuration file (to tell lazyValueForKey: where to initialize the injected object from, e.g. a plist)
  2. a convention: how will you name that configuration file so lazyValueForKey: will find it
  3. a method that will enable you to lookup and initialize an object from a configuration file (e.g. what fields and attributes will you include in your configuration file to describe objects).
  4. modify the lazyValueForKey: method to effectively de-serialize/read objects from the configuration file

Note that points 1-2-3 above could further lead you to abstract a dependency injection data source that would manage the configuration of your injected objects and provide you with them at runtime.

I am certainly missing other important points here, but I believe this is starting to get out of the scope of your original question, so I won't go any further with my answer. Implementing a dependency injection mechanism is no easy feat. However, you might find more info at this nice SO question.

Community
  • 1
  • 1
octy
  • 6,525
  • 1
  • 28
  • 38
  • this doesn't create the lazy initializer method. This initializes the object, and prevent me from trying to inject mocks – aryaxt Jul 07 '11 at 23:36
  • @aryaxt - it doesn't create the method, but it provides a generic approach for one; as for injecting mocks, lazyValueForKey: could be altered to return a mock object initialized from say, a configuration file - if said file exists in your bundle for example. Would something like that suit your needs? Maybe I'm totally off here, but I do believe it's possible to initialize an object graph using generic initializers via external configuration... – octy Jul 08 '11 at 02:03
  • Can you please explain how lazyValueForKey works and how I can use it to achieve Dependency Injections. Thanks – aryaxt Jul 08 '11 at 04:06
  • @aryaxt, I updated my answer. I also want to say this: Dependency Injection as I see it, should be non-intrusive - which is why I suggested an external, configurable component for it. Even if you decide to automatically generate (lazy) initializers, you still need to do that based on some kind configuration data that you will have to provide for yourself... Good luck! – octy Jul 11 '11 at 13:25
0

If I understood you correctly you want something like this:

-(SomeObject*) someObject {
    if( !mSomeObject ) {
        //initialize it
        mSomeObject = [[SomeObject alloc] init];
    }

    return mSomeObject;
}

If you want to do that automatically, then you're probably need to check key-value coding programming guide or as an option you can use method swizzling.

Max
  • 16,679
  • 4
  • 44
  • 57
  • Yes But I don't want to write the initializer for every property, I want it to be automated (by calling lazyInitialize method) – aryaxt Jul 07 '11 at 18:27