NSObject + завантаження та + ініціалізація - що вони роблять?


115

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

Моя цікавість викликає перегляд прикладу коду Apple - MVCNetworking. У їх модельному класі є +(void) applicationStartupметод. Він виконує деякий догляд за файловою системою, читає NSDefaults, тощо, і т. Д. ... і, спробувавши вподобати методи класу NSObject, здається, що ця дженіторична робота може бути добре вкласти в + load.

Я змінив проект MVCNetworking, видаливши виклик у програмі App Delegate на + applicationStartup і ввівши домоводки в + завантаження ... мій комп'ютер не загорівся, але це не означає, що це правильно! Я сподіваюся отримати розуміння будь-яких тонкощів, gotchas та нічого подібного до способу налаштування, який потрібно викликати порівняно з + load або + ініціалізувати.


Для документації + завантаження сказано:

Повідомлення про завантаження надсилається класам і категоріям, які є як динамічно завантаженими, так і статично пов'язаними, але тільки якщо ново завантажений клас або категорія реалізує метод, здатний реагувати.

Це речення є химерним і важким для розбору, якщо ви не знаєте точного значення всіх слів. Довідка!

  • Що розуміється під "як динамічно завантаженим, так і статично пов'язаним?" Чи може щось бути динамічно завантаженим і статично пов'язаним, або вони взаємовиключні?

  • "... щойно завантажений клас чи категорія реалізує метод, який може відповісти" Який метод? Як відповісти?


Щодо + ініціалізації, документація говорить:

ініціалізувати його викликається лише один раз у класі. Якщо ви хочете виконати незалежну ініціалізацію для класу та для категорій класу, слід застосувати методи завантаження.

Я вважаю, що це означає: "якщо ви намагаєтеся встановити клас ... не використовуйте ініціалізацію". Гаразд, добре. Коли або чому я би перекрив ініціалізацію тоді?

Відповіді:


184

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для надсилання, що є звичайною функцією надсилання повідомлень.

Подальше читання

Ознайомтесь із п’ятницею та питаннями п’ятниці Майка Еша на цю тему.


25
Ви повинні зазначити, що +loadнадсилається окремо для категорій; тобто кожна категорія в класі може містити свій власний +loadметод.
Джонатан Грінспан

1
Також зауважте, що при необхідності initializeбуде правильно покликаний loadметод, завдяки loadпосиланням на неініціалізовану сутність. Це може (як не дивно, але розумно) призвести до initializeбігу раніше load! Це я все-таки спостерігав. Це, здається, суперечить "І до моменту отримання initialize, кожен клас у вашому процесі вже повинен був отримати load(якщо це доречно)".
Бенджон

5
Ви отримуєте loadперший. Потім ви можете отримувати, initializeпоки loadвін ще працює.
пограбувати майофф

1
@robmayoff чи не потрібно нам додавати [супер ініціалізувати] та [супернавантаження] рядки у відповідних методах?
damithH

1
Зазвичай це погана ідея, оскільки час виконання вже надсилав обидва ці повідомлення у всі ваші суперкласи, перш ніж він надсилав їх вам.
грабувати майофф

17

Це означає, що не перекривайте +initializeкатегорію, ви, мабуть, щось зламаєте.

+loadвикликається один раз за клас або категорію, яка реалізує +load, як тільки цей клас або категорія завантажується. Коли він говорить "статично пов'язаний", це означає, що він зібраний у ваш бінарний додаток. Ці +loadметоди за класами , таким чином , складених будуть виконуватися , коли ваш додаток запускає, можливо , перш ніж він увійде main(). Коли він каже "динамічно завантажений", це означає, що завантажується через плагіни або виклик до dlopen(). Якщо ви перебуваєте на iOS, ви можете проігнорувати цей випадок.

+initializeназивається перший раз, коли повідомлення надсилається до класу, безпосередньо перед його обробкою. Це (очевидно) відбувається лише один раз. Якщо ви перекриєте +initializeкатегорію, відбудеться одна з трьох речей:

  • Ваша реалізація категорії викликається, а реалізація класу - ні
  • запрошується реалізація чужої категорії; нічого, що ви написали, не робить
  • Ваша категорія ще не завантажена, і її реалізація ніколи не викликається.

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

До речі, ще одна проблема, з якою слід звернути увагу, +initializeполягає в тому, що якщо хтось підкласитиме вас, вас потенційно можуть викликати один раз для вашого класу та один раз для кожного підкласу. Якщо ви робите щось на зразок налаштування staticзмінних, ви хочете захиститись від цього: або за dispatch_once()допомогою тестування self == [MyClass class].

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.