NSObject + load и + initialize - Что они делают?

Мне интересно понять обстоятельства, побудившие разработчика переопределить + initialize или + load. Документация дает понять, что эти методы вызывают для среды выполнения Objective-C, но это действительно все, что ясно из документации этих методов.: -)

Мое любопытство исходит из взгляда на пример кода Apple - MVCNetworking. Их модельный класс имеет метод +(void) applicationStartup. Он выполняет некоторую домашнюю работу в файловой системе, читает NSDefaults и т.д. И т.д.... и, пытаясь найти методы класса NSObject, похоже, что эта работа в janitorial может быть в порядке, чтобы добавить + нагрузку.

Я изменил проект MVCNetworking, удалив вызов в App Delegate до + applicationStartup и добавив служебные биты в + загрузку... мой компьютер не загорелся, но это не значит, что это правильно! Я надеюсь получить понимание любых тонкостей, gotchas и whatnots вокруг настраиваемого метода настройки, который вы должны вызвать против + нагрузки или инициализации +.


В документации для загрузки + указано:

Сообщение загрузки отправляется классам и категориям, которые оба динамически загружаемых и статически связанных, но только если вновь загруженные класс или категория реализует метод, который может ответить.

Это предложение является kludgey и трудно разобрать, если вы не знаете точного значения всех слов. Помогите!

  • Что подразумевается под "динамически загруженной и статически связанной?" Может ли что-то быть динамически загружено и статически связано, или они являются взаимоисключающими?

  • "... недавно загруженный класс или категория реализует метод, который может ответить" Какой метод? Ответьте, как?


Что касается + инициализации, в документации говорится:

инициализировать, он вызывается только один раз для каждого класса. Если вы хотите выполнить независимая инициализация для класса и для категорий class, вы должны реализовать методы загрузки.

Я предполагаю, что это означает: "если вы пытаетесь настроить класс... не используйте инициализацию". В порядке отлично. Когда или почему я переопределяю инициализацию, то?

Ответ 1

Сообщение load

Время выполнения отправляет сообщение load каждому объекту класса, очень скоро после того, как объект класса загружается в адресное пространство процесса. Для классов, которые являются частью исполняемого файла программы, среда выполнения отправляет сообщение load очень рано в течение жизненного цикла процесса. Для классов, которые находятся в общей (динамически загруженной) библиотеке, среда выполнения отправляет сообщение загрузки сразу после загрузки общей библиотеки в адресное пространство процесса.

Кроме того, среда выполнения отправляет load объекту класса, если этот объект класса реализует метод load. Пример:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)load {
    NSLog(@"in Superclass load");
}

@end

@implementation Subclass

// ... load not implemented in this class

@end

Время выполнения отправляет сообщение load объекту класса Superclass. Он не отправляет сообщение load в объект класса Subclass, хотя Subclass наследует метод от Superclass.

Время выполнения отправляет сообщение load объекту класса после того, как оно отправило сообщение load всем объектам суперкласса класса (если эти объекты суперкласса реализуют load) и все объекты класса в разделяемых библиотеках вы ссылаетесь. Но вы не знаете, какие еще классы в вашем собственном исполняемом файле уже получили load.

Каждый класс, загружаемый вашим процессом в его адресное пространство, получит сообщение load, если оно реализует метод load, независимо от того, использует ли ваш процесс любое другое использование класса.

Вы можете увидеть, как среда выполнения ищет метод load как частный случай в _class_getLoadMethod objc-runtime-new.mm и вызывает его непосредственно из call_class_loads в objc-loadmethod.mm.

В среде исполнения также выполняется метод load для каждой категории, которую он загружает, даже если несколько категорий в одном классе реализуют load. Это необычно. Обычно, если две категории определяют один и тот же метод в одном классе, один из методов будет "выигрывать" и использоваться, а другой метод никогда не будет вызываться.

Метод initialize

Время выполнения вызывает метод initialize объекта класса непосредственно перед отправкой первому сообщению (кроме load или initialize) объекту класса или любым экземплярам класса. Это сообщение отправляется с использованием обычного механизма, поэтому, если ваш класс не реализует initialize, но наследует от класса, который делает, то ваш класс будет использовать свой суперкласс initialize. Среда выполнения отправит initialize во все суперклассы класса (если суперклассы еще не отправлены initialize).

Пример:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)initialize {
    NSLog(@"in Superclass initialize; self = %@", self);
}

@end

@implementation Subclass

// ... initialize not implemented in this class

@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Subclass *object = [[Subclass alloc] init];
    }
    return 0;
}

Эта программа печатает две строки вывода:

2012-11-10 16:18:38.984 testApp[7498:c07] in Superclass initialize; self = Superclass
2012-11-10 16:18:38.987 testApp[7498:c07] in Superclass initialize; self = Subclass

Поскольку система отправляет метод initialize лениво, класс не получит сообщение, если ваша программа фактически не отправляет сообщения классу (или подклассу, или экземплярам класса или подклассов). И к тому времени, когда вы получите initialize, каждый класс вашего процесса должен уже получить load (при необходимости).

Канонический способ реализации initialize заключается в следующем:

@implementation Someclass

+ (void)initialize {
    if (self == [Someclass class]) {
        // do whatever
    }
}

Точка этого шаблона должна избегать Someclass повторной инициализации, когда у нее есть подкласс, который не реализует initialize.

Время выполнения отправляет сообщение initialize в _class_initialize в objc-initialize.mm. Вы можете видеть, что он использует objc_msgSend для его отправки, что является обычной функцией отправки сообщений.

Дальнейшее чтение

Отъезд Майк Эш Пятница Q & A по этой теме.

Ответ 2

Что это значит, не переопределяйте +initialize в категории, вы, вероятно, сломаете что-то.

+load вызывается один раз для каждого класса или категории, который реализует +load, как только загружается этот класс или категория. Когда он говорит "статически связанный", это означает, что он скомпилирован в бинарное приложение. Методы +load на скомпилированных классах будут выполняться, когда ваше приложение запустится, возможно, до того, как оно вступит в main(). Когда он говорит "динамически загружен", он означает загрузку через пакеты плагинов или вызов dlopen(). Если вы используете iOS, вы можете игнорировать этот случай.

+initialize вызывается в первый раз, когда сообщение отправляется классу непосредственно перед его обработкой. Это (очевидно) происходит только один раз. Если вы переопределите +initialize в категории, произойдет одна из трех вещей:

  • вызывается ваша реализация категории и реализация класса не
  • вызывает вызов другой категории; ничего не написал.
  • Ваша категория еще не загружена, и ее реализация никогда не вызывается.

Вот почему вы никогда не должны переопределять +initialize в категории - на самом деле довольно опасно пытаться заменить любой метод в категории, потому что вы никогда не знаете, что вы заменяете, или сама ли ваша замена будет выключен другой категорией.

Кстати, еще одна проблема с +initialize заключается в том, что если кто-то подклассифицирует вас, вы потенциально получите вызов один раз для своего класса и один раз для каждого подкласса. Если вы делаете что-то вроде настройки переменных static, вам нужно защититься от этого: либо с помощью dispatch_once(), либо путем тестирования self == [MyClass class].