NSObject + tải và + khởi tạo - Họ làm gì?


115

Tôi quan tâm đến việc tìm hiểu các tình huống khiến nhà phát triển ghi đè + khởi tạo hoặc + tải. Tài liệu cho thấy rõ các phương thức này được gọi cho bạn bởi thời gian chạy Objective-C, nhưng đó thực sự là tất cả những gì rõ ràng từ tài liệu của các phương thức đó. :-)

Sự tò mò của tôi đến từ việc nhìn vào mã ví dụ của Apple - MVCNetworking. Lớp mô hình của họ có một +(void) applicationStartupphương pháp. Nó thực hiện một số công việc vệ sinh trên hệ thống tập tin, đọc NSDefaults, v.v.

Tôi đã sửa đổi dự án MVCNetworking, loại bỏ cuộc gọi trong App Delegate thành + applicationStartup và đưa các bit vệ sinh vào + tải ... máy tính của tôi không bắt lửa, nhưng điều đó không có nghĩa là chính xác! Tôi hy vọng có được sự hiểu biết về bất kỳ sự tinh tế, gotchas và whatnots xung quanh một phương thức thiết lập tùy chỉnh mà bạn phải gọi so với + tải hoặc + khởi tạo.


Đối với tài liệu tải + cho biết:

Thông báo tải được gửi đến các lớp và các danh mục được tải động và liên kết tĩnh, nhưng chỉ khi lớp hoặc danh mục mới được tải thực hiện một phương thức có thể đáp ứng.

Câu này khó hiểu và khó phân tích nếu bạn không biết nghĩa chính xác của tất cả các từ. Cứu giúp!

  • Điều gì có nghĩa là "cả tải động và liên kết tĩnh?" Một cái gì đó có thể được tải động VÀ liên kết tĩnh, hoặc chúng loại trừ lẫn nhau?

  • "... lớp hoặc danh mục mới được tải thực hiện một phương thức có thể đáp ứng" Phương thức nào? Trả lời thế nào?


Đối với + khởi tạo, tài liệu nói:

khởi tạo nó chỉ được gọi một lần mỗi lớp. Nếu bạn muốn thực hiện khởi tạo độc lập cho lớp và cho các thể loại của lớp, bạn nên thực hiện các phương thức tải.

Tôi hiểu điều này có nghĩa là "nếu bạn cố gắng thiết lập lớp ... không sử dụng khởi tạo." Tốt. Khi nào hoặc tại sao tôi sẽ ghi đè khởi tạo sau đó?

Câu trả lời:


184

Tin loadnhắn

Thời gian chạy gửi loadthông báo đến từng đối tượng lớp, rất nhanh sau khi đối tượng lớp được tải trong không gian địa chỉ của tiến trình. Đối với các lớp là một phần của tệp thực thi của chương trình, thời gian chạy sẽ gửi loadtin nhắn rất sớm trong vòng đời của quy trình. Đối với các lớp trong thư viện dùng chung (được tải động), bộ thực thi sẽ gửi thông báo tải ngay sau khi thư viện dùng chung được tải vào không gian địa chỉ của tiến trình.

Hơn nữa, thời gian chạy chỉ gửi loadđến một đối tượng lớp nếu chính đối tượng lớp đó thực hiện loadphương thức. Thí dụ:

@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

Thời gian chạy gửi loadtin nhắn đến Superclassđối tượng lớp. Nó không gửi loadtin nhắn đến Subclassđối tượng lớp, mặc dù Subclasskế thừa phương thức từ đó Superclass.

Thời gian chạy gửi loadtin nhắn đến một đối tượng lớp sau khi nó đã gửi loadtin nhắn đến tất cả các đối tượng siêu lớp của lớp đó (nếu các đối tượng siêu lớp đó thực hiện load) và tất cả các đối tượng lớp trong các thư viện chia sẻ mà bạn liên kết đến. Nhưng bạn không biết những lớp khác trong chương trình thực thi của riêng bạn đã nhận loadđược.

Mỗi lớp mà tiến trình của bạn tải vào không gian địa chỉ của nó sẽ nhận được một loadthông báo, nếu nó thực hiện loadphương thức đó, bất kể quá trình của bạn có sử dụng lớp nào khác không.

Bạn có thể xem như thế nào thời gian chạy ngước lên các loadphương pháp như một trường hợp đặc biệt trong _class_getLoadMethodcác objc-runtime-new.mm, và gọi đó là trực tiếp từ call_class_loadstrong objc-loadmethod.mm.

Thời gian chạy cũng chạy loadphương thức của mọi danh mục mà nó tải, ngay cả khi một số danh mục trên cùng một lớp thực hiện load. Điều này là bất thường. Thông thường, nếu hai danh mục định nghĩa cùng một phương thức trên cùng một lớp, một trong các phương thức đó sẽ giành chiến thắng và được sử dụng, và phương thức kia sẽ không bao giờ được gọi.

các initializePhương pháp

Thời gian chạy gọi initializephương thức trên một đối tượng lớp ngay trước khi gửi tin nhắn đầu tiên (ngoài loadhoặc initialize) đến đối tượng lớp hoặc bất kỳ trường hợp nào của lớp. Thông báo này được gửi bằng cơ chế bình thường, vì vậy nếu lớp của bạn không thực hiện initialize, nhưng kế thừa từ một lớp có, thì lớp của bạn sẽ sử dụng lớp của nó initialize. Thời gian chạy sẽ gửi initializecho tất cả các siêu lớp của lớp trước (nếu các siêu lớp chưa được gửi initialize).

Thí dụ:

@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;
}

Chương trình này in hai dòng đầu ra:

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

Vì hệ thống gửi initializephương thức một cách lười biếng, một lớp sẽ không nhận được thông báo trừ khi chương trình của bạn thực sự gửi tin nhắn đến lớp (hoặc một lớp con hoặc các thể hiện của lớp hoặc các lớp con). Và vào thời điểm bạn nhận được initialize, mọi lớp trong quy trình của bạn đã nhận được load(nếu phù hợp).

Cách thức kinh điển để thực hiện initializelà:

@implementation Someclass

+ (void)initialize {
    if (self == [Someclass class]) {
        // do whatever
    }
}

Điểm của mẫu này là để tránh Someclasstự khởi tạo lại khi nó có một lớp con không thực hiện initialize.

Thời gian chạy gửi initializetin nhắn trong _class_initializehàm trong objc-initialize.mm. Bạn có thể thấy rằng nó sử dụng objc_msgSendđể gửi nó, đó là chức năng gửi tin nhắn bình thường.

đọc thêm

Kiểm tra câu hỏi thứ sáu của Mike Ash về chủ đề này.


25
Bạn nên lưu ý rằng +loadđược gửi riêng cho các danh mục; nghĩa là, mọi thể loại trên một lớp có thể chứa +loadphương thức riêng của nó .
Jonathan Grynspan

1
Cũng lưu ý rằng initializesẽ được gọi chính xác bằng một loadphương thức, nếu cần thiết, do loadtham chiếu đến thực thể chưa được khởi tạo. Điều này có thể (kỳ quặc, nhưng hợp lý) dẫn đến initializechạy trước load! Dù sao đó cũng là những gì tôi đã quan sát. Điều này dường như trái ngược với "Và vào thời điểm bạn nhận được initialize, mọi lớp trong quy trình của bạn đã nhận được load(nếu phù hợp)."
Stewohn

5
Bạn nhận được loadtrước. Sau đó bạn có thể nhận được initializetrong khi loadvẫn đang chạy.
cướp mayoff

1
@robmayoff chúng ta không cần thêm các dòng [siêu khởi tạo] và [siêu tải], bên trong các phương thức tương ứng?
damithH

1
Đó thường là một ý tưởng tồi, bởi vì bộ thực thi đã gửi cả hai tin nhắn đó đến tất cả các siêu lớp của bạn trước khi nó gửi chúng cho bạn.
cướp mayoff

17

Điều đó có nghĩa là không ghi đè +initializetrong một danh mục, bạn có thể sẽ phá vỡ một cái gì đó.

+loadđược gọi một lần cho mỗi lớp hoặc danh mục thực hiện +load, ngay khi lớp hoặc danh mục đó được tải. Khi nó nói "liên kết tĩnh", nó có nghĩa được biên dịch thành nhị phân ứng dụng của bạn. Các +loadphương thức trên các lớp được biên dịch sẽ được thực thi khi ứng dụng của bạn khởi chạy, có thể trước khi nó vào main(). Khi nó nói "được tải động", có nghĩa là được tải thông qua các gói plugin hoặc một cuộc gọi đến dlopen(). Nếu bạn đang ở trên iOS, bạn có thể bỏ qua trường hợp đó.

+initializeđược gọi lần đầu tiên một tin nhắn được gửi đến lớp, ngay trước khi nó xử lý tin nhắn đó. Điều này (rõ ràng) chỉ xảy ra một lần. Nếu bạn ghi đè +initializetrong một danh mục, một trong ba điều sẽ xảy ra:

  • việc thực hiện danh mục của bạn được gọi và việc thực hiện của lớp không
  • thực hiện danh mục của người khác được gọi; không có gì bạn viết
  • danh mục của bạn chưa được tải và việc thực hiện nó không bao giờ được gọi.

Đây là lý do tại sao bạn không bao giờ nên ghi đè +initializetrong một danh mục - thực tế sẽ rất nguy hiểm khi thử và thay thế bất kỳ phương pháp nào trong một danh mục vì bạn không bao giờ chắc chắn những gì bạn thay thế hoặc liệu chính sự thay thế của bạn sẽ bị loại ra bởi một danh mục khác.

BTW, một vấn đề khác cần xem xét +initializelà nếu ai đó phân lớp bạn, bạn có khả năng sẽ được gọi một lần cho lớp của bạn và một lần cho mỗi lớp con. Nếu bạn đang làm một cái gì đó như thiết lập staticcác biến, bạn sẽ muốn đề phòng điều đó: bằng dispatch_once()hoặc bằng cách thử nghiệm self == [MyClass class].

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.