views:

88

answers:

3

I have a question about overriding auto-generated accessor methods. The following would not work (I believe) because each getter references the other getter. Is there a rule that accessor methods should not use other accessor methods, or do you just have to watch out for these situations individually?

-(UIImage *) image{

    if(image == nil){
        if(self.data == nil){
            [self performSelectorInBackground: @selector(loadImage) withObject: nil]
        }else{
            self.image = [UIImage imageWithData: self.data];
        }
    }

    return image;
}

-(NSData *) data {

    if(data == nil){
        if(self.image == nil){
            [self performSelectorInBackground: @selector(loadData) withObject: nil]
        }else{
            self.data = UIImageJPEGRepresentation(self.image, 0.85);
        }
    }

    return data;
}

I have to emphasize that the image use presented here is an example, and thoughts concerning what to do in this particular example are less important than in the general case.

+2  A: 

There's nothing that forbids it, but you're definitely writing some confusing code. Essentially, these two properties have a circular dependency. This is hard to both read and debug. It's not clear why you'd want to "load the data" before you "load the image", or why you'd also want to support "loading the image" before you "load the data", or really how the two properties are really two different things.

Shaggy Frog
The fact that it is circular is what my questions stems from.
Ryan Bavetta
+1  A: 

First, don’t be too smart for your own good. If you want to get over some bottleneck, first measure and make sure it’s really there. I believe that both UIImage and NSData do some internal lazy loading, so that your code might be essentially useless. Second, even if you really wanted to do something like that by hand, try splitting the caching code into a separate class, so that you don’t pollute the code of the main class.

There is no rule about accessors (at least no that I know of), because people don’t do much lazy loading in accessors. Sometimes I get myself caught by an endless loop caused by lazy [UIViewController loadView] in combination with [UIViewController view], but that’s about it.

zoul
A: 

what you're actually doing in this example could take a long time to load; it is best to ensure that it is thread safe.

furthermore, it would be even better if you made the data object a real data provider (typically using protocols), separate from the class, and to make the image container expose that it is handling a lazily loaded object (which may cause outcry from some people).

specifically, you may want to invoke a load data/image from the provider, call it from awakeFromNib (for example) - then the loader runs off and loads the data on a secondary thread (esp. if downloaded). give the data provider a callback to inform the view that the image is ready (typically using protocols). once the view takes the unarchived image, invalidate the data provider.

finally, if you're dealing with app resources, the 'system' will cache some of this for you sooo you'd be attempting to work against what's already optimised behind the scenes.

in short, it's usually ok (e.g. not lazy initialization) - but this specific design (as another poster said) has a circular dependency which should be minimized.

Justin
thanks, I meant the functions to not stop the thread - I have updated the example to spin off background threads (they'd probably issue callbacks or notifications upon completion)
Ryan Bavetta
pt1: regarding the update: it's actually best to have the data provider perform the background load. in your example, any simple member access could create a new thread (that is, until the data is ready). so it would be better to have the data provider keep track of the modes (and thread-safe), based on whether it is NotRequested, Fetching, Loaded - then just call the data provider to load the image when the image is not available. if it's NotRequested, begin loading in the background, ignore the request if Fetching, else assert, since the data should be passed off after fetching.
Justin
pt2: do lock callbacks/accesses. with the structure you use, you may end up creating one background request per access - which could mean that you're requesting multiple background loads and creating a new thread upon each access == you may end up with 12 (or more) threads telling the data provider to load the data.
Justin