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

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

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

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


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

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

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

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

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


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

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

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


person edelaney05    schedule 10.11.2012    source источник


Ответы (2)


Сообщение 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 _ 21_ и вызывает его прямо из call_class_loads в _ 23_.

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

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

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

person rob mayoff    schedule 10.11.2012
comment
Обратите внимание, что +load отправляется отдельно для категорий; то есть каждая категория в классе может содержать свой собственный +load метод. - person Jonathan Grynspan; 11.11.2012
comment
Также обратите внимание, что initialize будет правильно вызываться методом load, если это необходимо, поскольку load ссылается на неинициализированный объект. Это может (как ни странно, но разумно) привести к запуску initialize до load! Во всяком случае, это то, что я наблюдал. Похоже, что это противоречит И к тому времени, когда вы получите initialize, каждый класс в вашем процессе должен уже получить load (при необходимости). - person Benjohn; 27.05.2014
comment
Вы получите load первым. Затем вы можете получить initialize, пока load все еще работает. - person rob mayoff; 27.05.2014
comment
@Benjohn У меня такое же поведение. Возможно, + load не является прямой причиной запуска + инициализации, НО похоже, что + initialize вызывается из-за отправки + load и + initialize запускается до + load. - person nickolay; 03.06.2015
comment
И к тому времени, когда вы получите инициализацию, каждый класс в вашем процессе должен уже получить нагрузку (если это необходимо). - это правда. Фактически я воспроизвел ситуацию, когда + инициализация была выполнена до того, как была запущена + загрузка. Но вызывающая последовательность всегда сначала + получено сообщение загрузки, чем получено + инициализирующее сообщение. - person nickolay; 03.06.2015
comment
@robmayoff, разве нам не нужно добавлять строки [super initialize] и [super load] внутри соответствующих методов? - person damithH; 20.11.2015
comment
Обычно это плохая идея, потому что среда выполнения уже отправила оба этих сообщения всем вашим суперклассам, прежде чем отправить их вам. - person rob mayoff; 20.11.2015

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

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

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

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

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

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

person Community    schedule 10.11.2012