Objective-C: What is a lazy class?
Asked Answered
L

3

11

Looking at the Objective-C runtime library source code, particularly at objc-runtime-new.mm, I saw some functions and even comments which referred to lazy and non-lazy classes. It seems that classes that don't have a +load method are called lazy classes, but I'm not sure of that and most likely that is not right. After searching on Google, I didn't found anything about lazy classes on Objective-C.

So, what is a lazy class in Objective-C? Does Obj-C have this feature? Is it related to the presence of a +load method in a class' implementation? At the file linked above, the runtime system calls a function called _getObjc2NonlazyClassList in order to get a list of non-lazy classes from an image. Why isn't there a _getObjc2LazyClassList function too?

Lipography answered 9/3, 2013 at 20:38 Comment(4)
might be a class loaded from a plugin. maybe check the documentation on plugins/dyld/dynamically loaded codeAngus
Are you sure it means that the class is lazy and not just the list?Hump
I am in agreement with @JoshCaswell, given that later on in the runtime source there are references to lazily loading in the classes. It almost certainly refers to the list, rather than a class being 'lazy'.Bairam
@JoshCaswell @Bairam I also thought it was a class list the should be loaded in later, I'm not sure. But at some point of the code I found the comment "// Realize non-lazy classes (for +load methods and static instances)". Following this comment, there is a call to _getObjc2NonlazyClassList which seems to get a list of lazy classes (I'm not sure yet), not a lazy class list.Lipography
L
15

I found the answer: It's all about a class implementing or not a +load method.

All the classes implemented in a given image file have a reference in a list stored in the "__DATA, __objc_classlist, regular, no_dead_strip" binary's section. This list allows the runtime system to keep track of all the classes stored in such file. However, not all of the classes need to be realized when the program starts up. That's why when a class implements a +load method, it also has a reference in a list stored in the "__DATA, __objc_nlclslist, regular, no_dead_strip" section.

So, _getObjc2NonlazyClassList retrieves the list of classes that do implement a +load method and are so called non-lazy. _getObjc2ClassList retrieves a list of all the classes in a image file, including the classes that don't have a +load method (and are called lazy) and the non-lazy ones. Non-lazy classes must be realized when the program starts up. Lazy classes, on the other hand, don't need to be realized immediately. This may be delayed until the class receives a message for the first time, for example (that's the reason for them to be considered "lazy").

The same is true for categories, by the way.

Lipography answered 10/3, 2013 at 2:45 Comment(2)
Just note that for categories, you must use the all_load linker flag if those categories are in an external library.Capsulate
This may be nearly correct, but I've seen MachO images that contain a __objc_nlclslist, but do not contain an __objc_classlist section.Pylorus
R
6

"Lazy" is used in two different contexts.

The first, when critiquing a class design, argues that a class is ineffectual -- that it doesn't do enough to justify its existence. People also call this kind of class "thin." This is probably not what you mean here.

Second, lazy evaluation and lazy instantiation mean that the class only does the work of evaluating a property or initializing itself when actually needed.

For example, suppose we have a class that makes an Employee object.

 @implementation Employee
 - (id) initWithID: (IdentificationCode*) ident
 {
    self =[super init]
    if (self) {
         _records=[self retrieveEmployeeRecordsFor: ident];
         _identification=ident;
         }
    return self;
}

This is fine, but retrieving all the records from a database might be slow. And sometimes we don't need to do the work. For example:

- (BOOL) isFounder
{
     if (indent.number<10) return YES;
     return NO;
}

If we're instantiating an Employee simply to find out if they're a Founder, we don't need to look up their records at all!

 .....
 if ([thisEmployee isFounder]) {
      [self sendCandyTo: thisEmployee.identification];
      }

On the other hand, sometimes we need them:

- (NSArray*) payments
{
    return [self.records retrievePayStubs];
    }

So, if we're constructing an Employee just to call isFounder, we waste a database lookup. But we can't just skip that, because payments needs it.

What we do is take the database lookup out of the constructor and put it in a load method.

- (void) load
{
    if (records) return;
    self.records=[self retrieveEmployeeRecordsFor: ident];
}

- (NSArray*) payments
{
    [self load];
    return [self.records retrievePayStubs];
    }

Now, we only load the employee records when we actually need them. If they've already been loaded, we don't do any extra work (aside from one method call). If we never need the payment records, then we don't need to do the work at all.

The class only works when it has to -- and waits 'til the last minute to do the work. It's "lazy!"

Robbinrobbins answered 9/3, 2013 at 23:56 Comment(1)
Thanks for answering, Mark. That's what I imagined. So, a class is considered lazy because of its behavior. But something still bothers me: the existence of the _getObjc2NonlazyClassList function which seems to load classes which are stored as non-lazy in the image file. It's like there is an aspect that makes a class non-lazy at compile time. Or perhaps it is only nomenclature: all the classes loaded from an image file are considered non-lazy...Lipography
A
0

Here's a pattern base on a couple of DataImporter & a DataManager the first initializing is data property only when the second request the property

@interface DataImporter: NSObject
    @property (readonly) NSString* data;
@end
@implementation DataImporter
    @synthesize data;
    -(id)init {
        if (!(self = [super init])) { return nil; }
        _NSLog(@"%@", @"Importing...");
        data = @"DATA";
        _NSLog(@"%@", @"Importation completed!");
        return self;
    }
@end

@interface DataManager: NSObject {
        @private
        DataImporter* _importer;
    }
    -(NSString*)loadData;
@end
@implementation DataManager
    -(id)init {
        if (!(self = [super init])) { return nil; }
        _NSLog(@"DataManager initialized%@",@"!");
        return self;
    }
    -(NSString*)loadData {
        if (!_importer) {
            _importer = [DataImporter new];
        }
        return _importer.data;
    }
@end


DataManager* dm = [DataManager new];    //>> DataManager initialized
NSString* data = [dm loadData];         //>> Importing
                                        //>> Importation completed!
_NSLog(@"%@", data);                    //>> DATA)
Azotemia answered 18/12, 2022 at 11:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.