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

116

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

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

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


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

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

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

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

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


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

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

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

edelaney05
источник

Ответы:

185

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_getLoadMethodof objc-runtime-new.mmи вызывает его прямо из call_class_loadsin 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для отправки, что является обычной функцией отправки сообщений.

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

Посмотрите пятничные вопросы и ответы Майка Эша по этой теме.

Роб Мэйофф
источник
26
Обратите внимание, что +loadрассылается отдельно для категорий; то есть каждая категория в классе может содержать свой собственный +loadметод.
Jonathan Grynspan
1
Также обратите внимание, что при необходимости метод initializeбудет правильно вызываться loadиз-за loadссылки на неинициализированный объект. Это может (как ни странно, но разумно) привести к initializeзабегу раньше load! Во всяком случае, это то, что я наблюдал. Похоже, что это противоречит «И к тому времени, когда вы получите initialize, каждый класс в вашем процессе должен уже получить load(если необходимо)».
Benjohn
5
Вы получите loadпервым. Затем вы можете получить, initializeпока loadвсе еще работает.
Роб Майофф
1
@robmayoff, разве нам не нужно добавлять строки [super initialize] и [super load] внутри соответствующих методов?
damithH
1
Обычно это плохая идея, потому что среда выполнения уже отправила оба этих сообщения всем вашим суперклассам, прежде чем отправить их вам.
Роб Мэйофф
17

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

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

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

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

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

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


источник