Trong Objective-C tại sao tôi phải kiểm tra xem self = [super init] có phải không không?


165

Tôi có một câu hỏi chung về việc viết các phương thức init trong Objective-C.

Tôi thấy nó ở khắp mọi nơi (mã của Apple, sách, mã nguồn mở, v.v.) rằng một phương thức init sẽ kiểm tra xem self = [super init] có phải không trước khi tiếp tục khởi tạo.

Mẫu Apple mặc định cho phương thức init là:

- (id) init
{
    self = [super init];

    if (self != nil)
    {
        // your code here
    }

    return self;
}

Tại sao?

Ý tôi là khi nào init sẽ trở về nil? Nếu tôi gọi init trên NSObject và nhận được con số không, thì một cái gì đó phải thực sự sai lầm, phải không? Và trong trường hợp đó, bạn thậm chí có thể không viết một chương trình ...

Có thực sự phổ biến rằng phương thức init của lớp có thể trả về không? Nếu vậy, trong trường hợp nào, và tại sao?


1
Wil Shipley đã đăng một bài viết liên quan đến điều này một thời gian trước. [self = [ngu ngốc init];] ( wilshipley.com/blog/2005/07/elf-stool-init.html ) Đọc qua các bình luận là tốt, một số công cụ tốt.
Ryan Townshend

6
Bạn có thể hỏi Wil Shipley hoặc Mike Ash hoặc Matt Gallagher . Dù bằng cách nào, đó là một chủ đề tranh luận. Nhưng thường thì thật tốt khi gắn bó với thành ngữ của Apple ... rốt cuộc thì đó là Framework của họ.
jbrennan

1
Có vẻ như Wil đã làm cho một trường hợp nhiều hơn vì không tự gán lại một cách mù quáng trong khi init, biết rằng [super init] có thể không trả lại người nhận.
Jasarien

3
Wil đã thay đổi suy nghĩ của mình kể từ khi bài viết ban đầu được thực hiện.
bbum

5
Tôi đã thấy câu hỏi này một lúc trước và chỉ tìm thấy nó một lần nữa. Hoàn hảo. +1
Dan Rosenstark

Câu trả lời:


53

Ví dụ:

[[NSData alloc] initWithContentsOfFile:@"this/path/doesn't/exist/"];
[[NSImage alloc] initWithContentsOfFile:@"unsupportedFormat.sjt"];
[NSImage imageNamed:@"AnImageThatIsntInTheImageCache"];

... và như thế. (Lưu ý: NSData có thể ném ngoại lệ nếu tệp không tồn tại). Có khá nhiều lĩnh vực mà việc trả lại con số không là hành vi dự kiến ​​khi xảy ra sự cố và vì đây là cách thực hành tiêu chuẩn để kiểm tra số lượng khá nhiều, vì sự nhất quán.


10
Có, nhưng đây không phải là bên trong phương thức init của lớp tương ứng. NSData kế thừa từ NSObject. NSData có kiểm tra xem [super init] có trả về không không? Đó là những gì tôi đang hỏi ở đây. Xin lỗi nếu tôi không rõ ràng ...
Jasarien

9
Nếu bạn đã phân lớp các lớp đó, sẽ có khả năng thực sự là [super init] sẽ trở về con số không. Không phải tất cả các lớp là các lớp con trực tiếp của NSObject. Không bao giờ có bất kỳ đảm bảo rằng nó sẽ không trở về con số không. Nó chỉ là một thực hành mã hóa phòng thủ thường được khuyến khích.
Chuck

7
Tôi nghi ngờ NSObject init có thể trở lại con số không trong mọi trường hợp. Nếu bạn hết bộ nhớ, việc cấp phát sẽ thất bại, nhưng nếu thành công, tôi nghi ngờ init có thể thất bại - NSObject thậm chí không có bất kỳ biến đối tượng nào ngoại trừ Class. Trong GNUStep, nó được triển khai như là "tự trả về" và việc phân tách trên Mac trông giống nhau. Tất cả điều này, tất nhiên, không liên quan - chỉ cần làm theo thành ngữ tiêu chuẩn và bạn sẽ không phải lo lắng về việc nó có thể hay không thể.
Peter N Lewis

9
Tôi không quyết tâm không tuân theo các thực hành tốt nhất. Tôi sẽ, tuy nhiên, muốn biết tại sao họ là thực hành tốt nhất ở nơi đầu tiên. Nó giống như được bảo là nhảy ra khỏi một tòa tháp. Bạn không chỉ đi trước và làm điều đó trừ khi bạn biết tại sao. Có một cái gối lớn, mềm để hạ cánh ở phía dưới, với phần thưởng tiền mặt lớn? Nếu tôi biết rằng tôi sẽ nhảy. Nếu không, tôi sẽ không. Tôi không muốn mù quáng theo dõi một thực hành mà không biết tại sao tôi lại theo dõi nó ...
Jasarien

4
Nếu phân bổ trả về con số không, init được gửi đến con số không, điều này sẽ luôn dẫn đến con số không, điều này kết thúc với việc con số không.
T.

50

Thành ngữ đặc biệt này là tiêu chuẩn vì nó hoạt động trong mọi trường hợp.

Trong khi không phổ biến, sẽ có trường hợp ...

[super init];

... Trả về một thể hiện khác, do đó yêu cầu tự gán.

Và sẽ có trường hợp nó sẽ trả về nil, do đó yêu cầu kiểm tra nil để mã của bạn không cố khởi tạo một khe biến thể hiện không còn tồn tại.

Điểm mấu chốt là nó là mẫu chính xác được sử dụng và nếu bạn không sử dụng nó, bạn đang làm sai.


3
Điều này vẫn còn đúng trong ánh sáng của các nhà đầu cơ nullable? Nếu trình khởi tạo của siêu lớp của tôi không phải là null thì nó có thực sự đáng để kiểm tra thêm không? (Mặc dù NSObject bản thân dường như không có bất kỳ cho nó -initafaict ...)
natevw

Có bao giờ một trường hợp [super init]trả về con số không khi siêu lớp trực tiếp là NSObject? Đây không phải là một trường hợp "mọi thứ đều bị hỏng sao?"
Dan Rosenstark

1
@DanRosenstark Không phải nếu NSObjectlà siêu lớp trực tiếp. Nhưng ... ngay cả khi bạn tuyên bố NSObjectlà siêu lớp trực tiếp, một cái gì đó có thể đã được sửa đổi trong thời gian chạy sao cho NSObjectviệc thực thi initkhông phải là thứ thực sự được gọi.
bbum

1
Cảm ơn rất nhiều @bbum, điều này thực sự giúp tôi định hướng trong một số sửa lỗi. Tốt để loại trừ một số điều!
Dan Rosenstark

26

Tôi nghĩ, trong hầu hết các lớp, nếu giá trị trả về từ [super init] là không và bạn kiểm tra nó, theo khuyến nghị của các thông lệ tiêu chuẩn, và sau đó trả về sớm nếu không, về cơ bản, ứng dụng của bạn vẫn không hoạt động chính xác. Nếu bạn nghĩ về nó, mặc dù rằng nếu (tự! = Nil) kiểm tra là có, cho các hoạt động thích hợp của lớp học của bạn, 99,99% thời gian bạn thực sự làm cần tự là phi nil. Bây giờ, giả sử, vì bất kỳ lý do gì, [super init] đã trả về con số không, về cơ bản, séc của bạn đối với con số không về cơ bản là chuyển giao cho người gọi của lớp bạn, nơi nó có thể sẽ thất bại, vì nó sẽ tự nhiên cho rằng cuộc gọi là thành công

Về cơ bản, những gì tôi nhận được là 99,99% thời gian, nếu (tự! = Nil) không mua cho bạn bất cứ điều gì về độ mạnh mẽ hơn, vì bạn chỉ chuyển giao cho kẻ xâm lược. Để thực sự có thể xử lý việc này một cách mạnh mẽ, bạn thực sự sẽ cần phải kiểm tra trong toàn bộ hệ thống phân cấp cuộc gọi của bạn. Và thậm chí sau đó, điều duy nhất nó sẽ mua cho bạn là ứng dụng của bạn sẽ thất bại một cách sạch sẽ / mạnh mẽ hơn một chút. Nhưng nó vẫn sẽ thất bại.

Nếu một lớp thư viện tự ý quyết định trả lại con số không phải là kết quả của [super init], thì dù sao đi nữa, đó là một dấu hiệu cho thấy người viết của lớp thư viện đã phạm sai lầm khi thực hiện.

Tôi nghĩ rằng đây là một gợi ý mã hóa cũ, khi các ứng dụng chạy trong bộ nhớ hạn chế hơn nhiều.

Nhưng đối với mã cấp C, tôi vẫn thường kiểm tra giá trị trả về của malloc () so với con trỏ NULL. Trong khi đó, đối với Objective-C, cho đến khi tôi tìm thấy bằng chứng ngược lại, tôi nghĩ rằng tôi thường bỏ qua việc kiểm tra if (self! = Nil). Tại sao có sự khác biệt?

Bởi vì, ở cấp độ C và malloc, trong một số trường hợp bạn thực sự có thể phục hồi một phần. Trong khi tôi nghĩ trong Objective-C, trong 99,99% trường hợp, nếu [super init] không trả về con số nào, thì về cơ bản bạn vẫn đang xử lý nó, ngay cả khi bạn cố gắng xử lý nó. Bạn cũng có thể chỉ để ứng dụng bị sập và giải quyết hậu quả.


6
Nói hay lắm. Tôi thứ hai.
Roger CS Wernersson

3
+1 Tôi hoàn toàn đồng ý. Chỉ là một lưu ý nhỏ: Tôi không tin rằng mô hình là kết quả từ những lần phân bổ thường thất bại hơn. Phân bổ thường đã được thực hiện tại thời điểm init được gọi. Nếu phân bổ thất bại, init thậm chí sẽ không được gọi.
Nikolai Ruhe

8

Đây là loại tóm tắt của các ý kiến ​​trên.

Hãy nói rằng siêu lớp trở lại nil. Điều gì sẽ xảy ra?

Nếu bạn không tuân theo các quy ước

Mã của bạn sẽ bị sập ở giữa initphương thức của bạn . (trừ khi initkhông có gì quan trọng)

Nếu bạn tuân theo các quy ước, không biết rằng siêu lớp có thể trở về con số không (hầu hết mọi người kết thúc ở đây)

Mã của bạn là probalby sẽ bị sập vào một lúc nào đó, bởi vì ví dụ của bạn là nil, nơi bạn mong đợi một cái gì đó khác biệt. Hoặc chương trình của bạn sẽ hoạt động bất ngờ mà không gặp sự cố. Trời ơi! Bạn có muốn cái này không? Tôi không biết...

Nếu bạn tuân theo các quy ước, sẵn sàng cho phép lớp con của bạn trở về con số không

Tài liệu mã của bạn (!) Phải ghi rõ: "trả về ... hoặc không" và phần còn lại của mã của bạn cần được chuẩn bị để xử lý việc này. Bây giờ nó có ý nghĩa.


5
Điểm thú vị ở đây, tôi nghĩ, đó là tùy chọn # 1 rõ ràngthích hợp hơn với tùy chọn # 2. Nếu có những trường hợp thực sự mà bạn có thể muốn init của lớp con của mình trở về con số không, thì # 3 sẽ được ưu tiên. Nếu lý do duy nhất từng xảy ra là do lỗi trong mã của bạn, thì hãy sử dụng # 1. Sử dụng tùy chọn # 2 chỉ là trì hoãn ứng dụng của bạn phát nổ cho đến thời điểm muộn hơn, và do đó làm cho công việc của bạn khi bạn đến để gỡ lỗi khó khăn hơn nhiều. Nó giống như âm thầm bắt ngoại lệ và tiếp tục mà không xử lý chúng.
Đánh dấu Amery

Hoặc bạn chuyển sang swift và chỉ sử dụng tùy chọn
AndrewSB

7

Thông thường, nếu lớp của bạn xuất phát trực tiếp từ NSObject, bạn sẽ không cần. Tuy nhiên, đó là một thói quen tốt để tham gia, vì nếu lớp của bạn xuất phát từ các lớp khác, trình khởi tạo của chúng có thể quay lại nilvà nếu vậy, trình khởi tạo của bạn có thể nắm bắt điều đó và hành xử chính xác.

Và vâng, đối với hồ sơ, tôi tuân theo thực tiễn tốt nhất và viết nó trên tất cả các lớp học của tôi, ngay cả những người xuất phát trực tiếp từ đó NSObject.


1
Với suy nghĩ này, nó có phải là một thực hành tốt để kiểm tra không sau khi khởi tạo một biến và trước khi gọi các hàm trên nó? ví dụFoo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
dev_does_software

Kế thừa từ NSObjectkhông đảm bảo nó cũng -initmang lại cho bạn NSObject, nếu tính các thời gian chạy kỳ lạ như các phiên bản cũ hơn của GNUstep (trả về GSObject) vì vậy không có vấn đề gì, kiểm tra và gán.
Maxthon Chan

3

Bạn nói đúng, bạn thường có thể viết [super init], nhưng điều đó sẽ không hiệu quả đối với một lớp con của bất cứ thứ gì. Mọi người chỉ thích ghi nhớ một dòng mã tiêu chuẩn và sử dụng nó mọi lúc, ngay cả khi đôi khi chỉ cần thiết, và do đó chúng tôi có được tiêu chuẩn if (self = [super init]), trong đó có cả khả năng không được trả lại và khả năng của một đối tượng khác hơn selflà được trả lại vào tài khoản.


3

Một lỗi phổ biến là viết

self = [[super alloc] init];

trong đó trả về một thể hiện của siêu lớp, đó không phải là thứ bạn muốn trong hàm tạo / init của lớp con. Bạn nhận lại một đối tượng không đáp ứng với các phương thức của lớp con, điều này có thể gây nhầm lẫn và tạo ra các lỗi khó hiểu về việc không sửa chữa lại các phương thức hoặc mã định danh không tìm thấy, v.v.

self = [super init]; 

là cần thiết nếu lớp siêu có các thành viên (các biến hoặc các đối tượng khác) để khởi tạo đầu tiên trước khi thiết lập các thành viên của lớp con. Nếu thời gian chạy objc khởi cho họ tất cả để 0 hoặc bằng không . ( không giống như ANSI C, thường phân bổ các khối bộ nhớ mà không xóa chúng )

Và đúng vậy, khởi tạo lớp cơ sở có thể thất bại do lỗi hết bộ nhớ, thiếu thành phần, lỗi thu nhận tài nguyên, v.v. vì vậy việc kiểm tra nil là khôn ngoan và mất ít hơn vài mili giây.


2

Điều này là để kiểm tra xem intialazation có hoạt động không, câu lệnh if trả về true nếu phương thức init không trả về nil, vì vậy đây là cách để kiểm tra việc tạo đối tượng hoạt động chính xác. Vài lý do tôi có thể nghĩ về init đó có thể thất bại có lẽ là một phương thức init quá mức mà siêu hạng không biết hoặc một cái gì đó thuộc loại này, tôi sẽ không nghĩ rằng đó là phổ biến mặc dù. Nhưng nếu nó xảy ra, tốt hơn là không có gì xảy ra khi tôi gặp sự cố nên nó luôn được kiểm tra ...


đó là, nhưng htey được gọi cùng nhau, điều gì xảy ra nếu cấp phát f ails?
Daniel

Tôi tưởng tượng nếu cấp phát thất bại, thì init sẽ được gửi đến nil chứ không phải là một thể hiện của bất kỳ lớp nào bạn đang gọi init. Trong trường hợp đó, sẽ không có gì xảy ra và sẽ không có mã nào được thực thi để thậm chí kiểm tra xem [super init] có được trả về không.
Jasarien

2
Phân bổ bộ nhớ không phải lúc nào cũng được thực hiện trong + phân bổ. Xem xét trường hợp của cụm lớp; một NSString không biết nên sử dụng lớp con cụ thể nào cho đến khi trình khởi tạo được gọi.
bbum

1

Trong OS X, nó không có khả năng -[NSObject init]bị lỗi do lý do bộ nhớ. Điều tương tự không thể nói cho iOS.

Ngoài ra, đó là cách thực hành tốt để viết khi phân lớp một lớp có thể trở lại nilvì bất kỳ lý do gì.


2
Trên cả iOS và Mac OS -[NSObject init]rất khó có khả năng thất bại vì lý do bộ nhớ vì nó không phân bổ bất kỳ bộ nhớ.
Nikolai Ruhe

Tôi đoán anh ta có nghĩa là phân bổ, không phải init :)
Thomas Tempelmann
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.