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],

Другие вопросы по тегам