NSObject +load и +initialize - что они делают?
Мне интересно понять обстоятельства, побуждающие разработчика переопределить + инициализировать или + загрузить. Документация проясняет, что эти методы вызываются для вас средой выполнения Objective C, но это действительно все, что ясно из документации этих методов.:-)
Мое любопытство исходит от просмотра примера кода Apple - MVCNetworking. Их модельный класс имеет +(void) applicationStartup
метод. Он выполняет некоторую служебную работу с файловой системой, читает NSDefaults и т. Д. И т. Д., И, после попытки получить методы класса NSObject, кажется, что эту вспомогательную работу можно было бы поместить в +load.
Я изменил проект MVCNetworking, удалив вызов из App Delegate для +applicationStartup и поместив служебные биты в +load... мой компьютер не загорелся, но это не значит, что он правильный! Я надеюсь получить представление о любых тонкостях, хитросплетениях и многом другом вокруг пользовательского метода установки, который вы должны вызывать, вместо + load или + initialize.
Для + загрузки документации написано:
Сообщение о загрузке отправляется классам и категориям, которые динамически загружаются и статически связаны, но только если недавно загруженный класс или категория реализует метод, который может отвечать.
Это предложение является хитрым и трудно разобрать, если вы не знаете точного значения всех слов. Помогите!
Что подразумевается под "как динамически загружаемым, так и статически связанным?" Может ли что-то быть динамически загружено И статически связано, или они взаимоисключающие?
"... недавно загруженный класс или категория реализует метод, который может ответить" Какой метод? Ответить как?
Что касается + initialize, в документации сказано:
его инициализация вызывается только один раз для каждого класса. Если вы хотите выполнить независимую инициализацию для класса и для категорий класса, вы должны реализовать методы загрузки.
Я понимаю, что это означает: "если вы пытаетесь настроить класс... не используйте инициализацию". В порядке Хорошо. Когда или почему я должен переопределить инициализацию тогда?
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
из 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
отправить его, что является нормальной функцией отправки сообщений.
дальнейшее чтение
Ознакомьтесь с пятничными вопросами и ответами Майка Эша на эту тему.
Что это значит не переопределять +initialize
в категории вы, вероятно, что-то сломаете.
+load
вызывается один раз для класса или категории, которая реализует +load
, как только этот класс или категория загружена. Когда он говорит "статически связанный", это означает, что он скомпилирован в двоичный файл вашего приложения. +load
скомпилированные таким образом методы классов будут выполняться при запуске приложения, вероятно, до его входа main()
, Когда он говорит "динамически загружается", это означает, что он загружается через пакеты плагинов или вызов dlopen()
, Если вы используете iOS, вы можете игнорировать этот случай.
+initialize
вызывается в первый раз, когда сообщение отправляется в класс, непосредственно перед тем, как оно обработает это сообщение. Это (очевидно) происходит только один раз. Если вы переопределите +initialize
в категории произойдет одно из трех:
- Ваша реализация категории вызывается, а реализация класса не
- вызывается реализация чужой категории; ничего, что вы написали, не делает
- Ваша категория еще не загружена, и ее реализация никогда не вызывается.
Вот почему вы никогда не должны переопределять +initialize
в категории - на самом деле довольно опасно пытаться заменить любой метод в категории, потому что вы никогда не уверены, что заменяете, или ваша собственная замена будет заменена другой категорией.
Кстати, еще одна проблема, чтобы рассмотреть с +initialize
в том, что если кто-то подклассов вас, вы потенциально будете вызывать один раз для вашего класса и один раз для каждого подкласса. Если вы делаете что-то вроде настройки static
переменные, вы хотите защититься от этого: либо с dispatch_once()
или путем тестирования self == [MyClass class]
,