0

The title is the question's formulation - i.e. what are the patterns and anti-patterns of +initialize and +load class methods overriding?

Have you met particular examples? If yes - please describe.

P.S. There was some good Q&A on +load and +initialize here on StackOverflow but no one tells about the practical interest of these methods. Mechanisms were discussed.

nickolay
  • 3,643
  • 3
  • 32
  • 40
  • 4
    Anytime you need to initialize `static` variables that can't be initialized with compile-time constants. – rmaddy Jun 01 '15 at 23:41
  • `+load` is useful for [registering custom fonts bundled in dynamic frameworks](http://stackoverflow.com/a/30511202/4151918). –  Jun 02 '15 at 02:00
  • possible duplicate of [NSObject +load and +initialize - What do they do?](http://stackoverflow.com/questions/13326435/nsobject-load-and-initialize-what-do-they-do) – Darren Jun 02 '15 at 03:39
  • @rmaddy Could you bring an example of such a variable? Thx! – nickolay Jun 03 '15 at 18:31
  • @Darren Not really a duplicate. That other question covers how they work. This question wants to know when they should be used. – rmaddy Jun 03 '15 at 19:02
  • @DaddyM As requested, I have posted an answer showing an example. – rmaddy Jun 03 '15 at 19:10

4 Answers4

2

+load is useful for setting up stuff needed for categories because all the +load methods are guaranteed to be called once each when the binary is loaded (even if there are multiple +load methods for the same class, which normally would replace one another). Inheritance is actually irrelevant to its functioning.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • thanks for sharing that. I know that +load is a proper place to do method swizzling - that is usually done via categories. What else? – nickolay Jun 02 '15 at 23:13
0

I almost never use +load, but +initialize is useful for all sorts of things... setting up static variables, dynamically loading libraries for plugin architectures... anything you want to do one time like printing version info, setting up a global instance to do something specialized, like for logging, crash reporter, signal handler, etc...

edit:

to prevent multiple initialize calls from messing stuff up (which happens to superclasses when the child class is used after a superclass): you can make it reentrant (this is a common pattern):

+(void) initialize {
    static BOOL inited = NO;
    if(!inited)
    {
       /*dostuff*/
       inited=YES;
    }
}
Grady Player
  • 14,399
  • 2
  • 48
  • 76
  • but +initialize may be called multiple times? You mean you use "if"-block to prevent calling parent's +initialize ? – nickolay Jun 02 '15 at 23:15
  • Your edit looks like a solution but it brings new variable to static memory. Apple's one with if (self == [ClassName self]) ... looks more consistent. – nickolay Jun 03 '15 at 18:33
  • It's better to use `dispatch_once`. Because if your `+initialize` will be called at approximately the same time from two different threads for some reason (I'm not sure if it's possible with `+initialize`, but it's better to make it safe), one thread can pass the `if`, stop, then the second thread can pass the `if`, stop, and then they can "do stuff" together. – FreeNickname Jun 03 '15 at 19:38
  • 1
    Everybody, IGNORE MY COMMENT ABOVE :) @GradyPlayer, sorry, you method is fine in this case :) As @DaddyM correctly mentioned in comments to my answer, according to the docs, `+initialize` is called in a thread-safe manner (I wasn't sure that it is). So `if (!inited)` will work. – FreeNickname Jun 07 '15 at 17:00
0

A good use of the load method is to initialize global variables that can't be initialized at compile-time.

Here is a made up example:

SomeClass.h

extern NSString *SomeGlobalConstant;

// Followed by some class interface stuff

SomeClass.m

#import "SomeClass.h"

NSString *SomeGlobalConstant = nil;

static NSArray *someFileStaticArray = nil;

@implementation SomeClass

+ (void)load {
    if (self == [SomeClass class]) {
        SomeGlobalConstant = @"SomeAppropriateValue";

        someFileStaticArray = @[ @"A", @"B", @"C" ];
    }
}

// and the rest of the class implementation

@end
rmaddy
  • 314,917
  • 42
  • 532
  • 579
0

Another possible use of +initialize is for Method Swizzling. Which you shouldn't really use unless you're sure you know what you're doing. Read this SO question for more details.

It allows you to substitute an existing method implementation with your own, and still be able to call the original one. For instance, for faking NSDate in unit tests you could write something like this (note, that there are other ways to do this (OCMock, etc.), this is just an example). This code allows you to set a program-wide fake NSDate, which will be returned whenever [NSDate date] is called. And if no fake date is set, then the original implementation is used.

#import "NSDate+UnitTest.h"
#import "MethodSwizzling.h"

@implementation NSDate(UnitTest)

static NSDate *fakeCurrentDate = nil;

+(void)setFakeCurrentDate:(NSDate *)date
{
    fakeCurrentDate = date;
}

+(NSDate *)fakeCurrentDate
{
    if (fakeCurrentDate) {
        return fakeCurrentDate;
    }
    else {
        NSDate *result = [self fakeCurrentDate];
        return result;
    }
}

+(void)initialize
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"Swizzling...");
        SwizzleClassMethod([self class], @selector(date), @selector(fakeCurrentDate));
    });
}

@end

//MethodSwizzling.m:

void SwizzleMethod(Class c, SEL orig, SEL new, BOOL isClassMethod) {
    NSLog(@"Swizzling %@ method %@ of class %@ with fake selector %@.",
          (isClassMethod ? @"a class" : @"an instance"),
          NSStringFromSelector(orig),
          NSStringFromClass(c),
          NSStringFromSelector(new));

    Method origMethod = isClassMethod ? class_getClassMethod(c, orig) : class_getInstanceMethod(c, orig);
    Method newMethod = isClassMethod ? class_getClassMethod(c, new) : class_getInstanceMethod(c, new);

    method_exchangeImplementations(origMethod, newMethod);
    //Actually, it's better to do it using C-functions instead of Obj-C methods and
    //methos_setImplementation instead of method_exchangeImplementation, but since this
    //is not an open-source project and these components aren't going to be used by other people,
    //it's fine. The problem is that method_exchangeImplementations will mess things up if
    //the implementation relies on a fact that the selector passed as a _cmd parameter
    //matches the function name.
    //More about it: http://blog.newrelic.com/2014/04/16/right-way-to-swizzle/
}

void SwizzleClassMethod(Class c, SEL orig, SEL new) {
    SwizzleMethod(c, orig, new, YES);
}

void SwizzleInstanceMethod(Class c, SEL orig, SEL new) {
    SwizzleMethod(c, orig, new, NO);
}
Community
  • 1
  • 1
FreeNickname
  • 7,398
  • 2
  • 30
  • 60
  • Thank you. That's really interesting. BTW, do you have some sort of the guide (from your practice) what are the patterns and anti-patterns of `+load` and `+initialize` usage? – nickolay Jun 03 '15 at 21:55
  • 1
    @DaddyM, You're welcome! Well, I think, they are both used, when you need to prepare the initialized class for work somehow. Set static variables, swizzle methods, etc. So, when you feel a need to set up your class in some way, either `+load` or `+initialize` can be used. The main difference is that `+initialize` is called exactly once for each class, just before sending the first message to the class/it's instance, while `+load` is called once for each category of this class right after the startup. So, for instance, `+load` is not suitable for method swizzling (it might swizzle many times). – FreeNickname Jun 04 '15 at 20:14
  • btw I do not understand why we should use `dispatch_once` to swizzle methods? `+initialize` runs thread safe according to Apple's documentation. "The runtime sends the initialize message to classes in a thread-safe manner." Or I misunderstand smth. – nickolay Jun 05 '15 at 18:50
  • @DaddyM, Yes, you're right, I wasn't sure if it is thread-safe or not and I encountered a couple of problems with incorrect lazy initialization in the past (not in `+initialize`), that's why I wrote that comment. Indeed, both `dispatch_once` and a usual `if (!inited) { ... }` will work just fine. Gotta go apologize to Grady Player :) – FreeNickname Jun 07 '15 at 16:58