NSObject + load and + initialize - เขาทำอะไร?


116

ฉันสนใจที่จะทำความเข้าใจสถานการณ์ที่ทำให้นักพัฒนาซอฟต์แวร์ลบล้าง + เริ่มต้นหรือ + โหลด เอกสารประกอบทำให้ชัดเจนว่าเมธอดเหล่านี้เรียกให้คุณโดยรันไทม์ Objective-C แต่นั่นคือทั้งหมดที่ชัดเจนจากเอกสารของวิธีการเหล่านั้น :-)

ความอยากรู้ของฉันมาจากการดูโค้ดตัวอย่างของ Apple - MVCNetworking คลาสโมเดลของพวกเขามี+(void) applicationStartupวิธีการ มันทำหน้าที่ดูแลระบบไฟล์บางอย่างอ่าน NSDefaults ฯลฯ ฯลฯ ... และหลังจากพยายามรวบรวมวิธีการเรียนของ NSObject ดูเหมือนว่างานภารโรงนี้อาจจะโอเคที่จะใส่ใน + load

ฉันแก้ไขโครงการ MVCNetworking โดยลบการโทรใน App Delegate เป็น + applicationStartup และใส่บิตการดูแลทำความสะอาดลงใน + โหลด ... คอมพิวเตอร์ของฉันไม่ลุกเป็นไฟ แต่นั่นไม่ได้หมายความว่าถูกต้อง! ฉันหวังว่าจะได้รับความเข้าใจเกี่ยวกับรายละเอียดปลีกย่อย gotchas และ whatnots เกี่ยวกับวิธีการตั้งค่าแบบกำหนดเองที่คุณต้องเรียกเทียบกับ + load หรือ + initialize


สำหรับ + ​​โหลดเอกสารระบุว่า:

ข้อความโหลดจะถูกส่งไปยังคลาสและประเภทที่มีทั้งโหลดแบบไดนามิกและลิงก์แบบสแตติก แต่เฉพาะในกรณีที่คลาสหรือประเภทที่โหลดใหม่ใช้วิธีการที่สามารถตอบสนองได้

ประโยคนี้ไม่ชัดเจนและยากที่จะแยกวิเคราะห์หากคุณไม่ทราบความหมายที่ชัดเจนของคำทั้งหมด ช่วยด้วย!

  • "ทั้งโหลดแบบไดนามิกและเชื่อมโยงแบบคงที่" หมายความว่าอย่างไร บางสิ่งสามารถโหลดแบบไดนามิกและเชื่อมโยงแบบสแตติกได้หรือไม่?

  • "... คลาสหรือหมวดหมู่ที่โหลดใหม่ใช้วิธีการที่สามารถตอบสนอง" วิธีการใด? ตอบสนองอย่างไร?


สำหรับ + ​​เริ่มต้นเอกสารระบุว่า:

เริ่มต้นจะถูกเรียกเพียงครั้งเดียวต่อชั้นเรียน หากคุณต้องการเริ่มต้นอย่างอิสระสำหรับคลาสและสำหรับประเภทของคลาสคุณควรใช้วิธีการโหลด

ฉันคิดว่านี่หมายความว่า "ถ้าคุณพยายามตั้งค่าคลาส ... อย่าใช้ initialize" โอเคสบายดี ฉันจะลบล้างการเริ่มต้นเมื่อใดหรือเพราะเหตุใด

คำตอบ:


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วัตถุชั้นแม้ว่าสืบทอดวิธีการจากSubclassSuperclass

รันไทม์ส่งloadข้อความไปยังคลาสอ็อบเจ็กต์หลังจากที่ส่งloadข้อความไปยังอ็อบเจ็กต์ superclass ของคลาสทั้งหมด (หากอ็อบเจ็กต์ superclass เหล่านั้นใช้งานload) และอ็อบเจ็กต์คลาสทั้งหมดในไลบรารีแบบแบ่งใช้ที่คุณเชื่อมโยง แต่คุณไม่รู้ว่าคลาสอื่น ๆ ในไฟล์ปฏิบัติการของคุณได้รับแล้วหรือloadยัง

ทุกคลาสที่กระบวนการของคุณโหลดลงในพื้นที่แอดเดรสจะได้รับloadข้อความหากใช้loadเมธอดไม่ว่ากระบวนการของคุณจะใช้คลาสอื่นหรือไม่ก็ตาม

คุณสามารถดูวิธีรันไทม์ที่มีลักษณะขึ้นloadวิธีการเป็นกรณีพิเศษใน_class_getLoadMethodการobjc-runtime-new.mmและเรียกมันว่าโดยตรงจากในcall_class_loadsobjc-loadmethod.mm

รันไทม์ยังเรียกใช้loadเมธอดของทุกหมวดหมู่ที่โหลดแม้ว่าจะมีหลายประเภทในคลาสเดียวกันloadก็ตาม นี่เป็นเรื่องผิดปกติ โดยปกติถ้าสองประเภทกำหนดวิธีการเดียวกันในคลาสเดียวกันวิธีใดวิธีหนึ่งจะ“ ชนะ” และถูกนำไปใช้และอีกวิธีหนึ่งจะไม่ถูกเรียก

initializeวิธี

รันไทม์เรียกใช้initializeเมธอดบนคลาสอ็อบเจ็กต์ก่อนที่จะส่งข้อความแรก (นอกเหนือจากloadหรือinitialize) ไปยังคลาสอ็อบเจ็กต์หรืออินสแตนซ์ใด ๆ ของคลาส ข้อความนี้ถูกส่งโดยใช้กลไกปกติดังนั้นหากระดับของคุณไม่ใช้initializeแต่สืบทอดจากคลาสที่ไม่แล้วระดับของคุณจะใช้ของ initializesuperclass รันไทม์จะส่งinitializesuperclasses ของคลาสทั้งหมดก่อน (ถ้ายังไม่ได้ส่ง superclasses 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
    }
}

จุดของรูปแบบนี้คือการหลีกเลี่ยงอีกครั้งเริ่มต้นตัวเองเมื่อมันมีคลาสย่อยที่ไม่ได้ใช้Someclassinitialize

รันไทม์ส่งinitializeข้อความใน_class_initializeฟังก์ชันในobjc-initialize.mm. คุณจะเห็นว่ามันใช้objc_msgSendในการส่งซึ่งเป็นฟังก์ชันการส่งข้อความตามปกติ

อ่านเพิ่มเติม

ดูคำถามและคำตอบในวันศุกร์ของ Mike Ashในหัวข้อนี้


26
คุณควรทราบว่า+loadมีการส่งแยกต่างหากสำหรับหมวดหมู่ นั่นคือทุกหมวดหมู่ในคลาสอาจมี+loadวิธีการของตัวเอง
Jonathan Grynspan

1
นอกจากนี้โปรดทราบว่าinitializeจะถูกเรียกใช้อย่างถูกต้องโดยloadวิธีการหากจำเป็นเนื่องจากloadการอ้างอิงถึงเอนทิตีที่ไม่ได้เริ่มต้น สิ่งนี้สามารถ (แปลก ๆ แต่สมเหตุสมผล) นำไปสู่การinitializeวิ่งก่อนload! นั่นคือสิ่งที่ฉันสังเกตอยู่แล้ว สิ่งนี้ดูเหมือนจะตรงกันข้ามกับ "และเมื่อถึงเวลาที่คุณได้รับinitializeทุกชั้นเรียนในกระบวนการของคุณควรได้รับแล้วload(ถ้าเหมาะสม)"
Benjohn

5
คุณได้รับloadก่อน จากนั้นคุณอาจได้รับinitializeในขณะที่loadยังทำงานอยู่
rob mayoff

1
@robmayoff เราไม่จำเป็นต้องเพิ่มบรรทัด [super initialize] และ [super load] ในวิธีการต่างๆใช่หรือไม่?
damithH

1
ซึ่งโดยปกติแล้วเป็นความคิดที่ไม่ดีเนื่องจากรันไทม์ได้ส่งข้อความทั้งสองไปยังซูเปอร์คลาสทั้งหมดของคุณก่อนที่จะส่งถึงคุณ
rob mayoff

17

ความหมายคืออย่าแทนที่+initializeในหมวดหมู่คุณอาจทำลายบางสิ่งได้

+loadเรียกว่าครั้งต่อการเรียนหรือประเภทที่ดำเนินการ+load, เร็วที่สุดเท่าที่ชั้นเรียนหรือประเภทที่มีการโหลด เมื่อมีข้อความว่า "ลิงก์แบบคงที่" หมายความว่ารวบรวมเป็นไบนารีของแอป วิธีการในการเรียนการรวบรวมจึงจะได้รับการดำเนินการเมื่อมีการเปิดตัวแอปของคุณอาจจะเป็นก่อนที่จะเข้าสู่+load main()เมื่อมันพูดว่า "โหลดแบบไดนามิก" dlopen()มันหมายถึงการโหลดผ่านการรวมกลุ่มปลั๊กอินหรือโทรไป หากคุณใช้ iOS คุณสามารถเพิกเฉยต่อกรณีนั้นได้

+initializeเรียกว่าเป็นครั้งแรกที่ส่งข้อความถึงชั้นเรียนก่อนที่จะจัดการข้อความนั้น สิ่งนี้ (ชัดเจน) เกิดขึ้นเพียงครั้งเดียว หากคุณลบล้าง+initializeในหมวดหมู่หนึ่งในสามสิ่งจะเกิดขึ้น:

  • การติดตั้งหมวดหมู่ของคุณถูกเรียกและการใช้งานของชั้นเรียนไม่ได้
  • มีการเรียกใช้หมวดหมู่ของผู้อื่น ไม่มีอะไรที่คุณเขียน
  • หมวดหมู่ของคุณยังไม่ถูกโหลดและการใช้งานจะไม่ถูกเรียกใช้

นี่คือเหตุผลที่คุณไม่ควรแทนที่+initializeในหมวดหมู่ - อันที่จริงมันค่อนข้างอันตรายที่จะลองและแทนที่วิธีการใด ๆในหมวดหมู่เพราะคุณไม่แน่ใจว่าคุณกำลังจะเปลี่ยนอะไรหรือว่าการแทนที่ของคุณเองจะถูกเปลี่ยนโดยหมวดหมู่อื่น

BTW อีกประเด็นหนึ่งที่ต้องพิจารณา+initializeคือหากมีคนย่อยคลาสคุณคุณอาจถูกเรียกหนึ่งครั้งสำหรับคลาสของคุณและหนึ่งครั้งสำหรับคลาสย่อยแต่ละคลาส หากคุณกำลังทำอะไรบางอย่างเช่นการตั้งค่าstaticตัวแปรที่คุณจะต้องการที่จะป้องกันการว่าทั้งที่มีหรือโดยการทดสอบdispatch_once()self == [MyClass class]

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.