Gửi tin nhắn tới nil trong Objective-C


107

Là một nhà phát triển Java đang đọc tài liệu Objective-C 2.0 của Apple: Tôi tự hỏi " gửi tin nhắn đến nil " nghĩa là gì - hãy để một mình nó thực sự hữu ích như thế nào. Lấy một đoạn trích từ tài liệu:

Có một số mô hình trong Ca cao tận dụng thực tế này. Giá trị được trả về từ một tin nhắn thành nil cũng có thể hợp lệ:

  • Nếu phương thức trả về một đối tượng, bất kỳ kiểu con trỏ nào, bất kỳ vô hướng số nguyên nào có kích thước nhỏ hơn hoặc bằng sizeof (void *), float, double, long double hoặc long long, thì thông báo được gửi đến nil sẽ trả về 0 .
  • Nếu phương thức trả về một cấu trúc, như được định nghĩa bởi Hướng dẫn Gọi hàm Mac OS X ABI được trả về trong sổ đăng ký, thì một thông báo được gửi đến nil sẽ trả về 0,0 cho mọi trường trong cấu trúc dữ liệu. Các kiểu dữ liệu cấu trúc khác sẽ không được điền bằng số không.
  • Nếu phương thức trả về bất kỳ thứ gì khác với loại giá trị nói trên, giá trị trả về của một thông báo được gửi đến nil là không xác định.

Java có khiến bộ não của tôi không thể dò ra lời giải thích ở trên không? Hay có thứ gì đó mà tôi thiếu có thể làm cho thứ này trở nên rõ ràng như thủy tinh?

Tôi hiểu ý tưởng về tin nhắn / người nhận trong Objective-C, tôi chỉ đơn giản là nhầm lẫn về một người nhận sẽ xảy ra nil.


2
Tôi cũng có nền tảng Java và tôi đã rất sợ hãi trước tính năng tuyệt vời này lúc đầu, nhưng bây giờ tôi thấy nó hoàn toàn YÊU !;
Valentin Radu

1
Cảm ơn, đó là một câu hỏi tuyệt vời. Bạn đã xem qua để thấy lợi ích của việc đó chưa? Nó gây ấn tượng với tôi như một thứ "không phải là một lỗi, một tính năng". Tôi liên tục gặp lỗi trong đó Java sẽ chỉ cho tôi một ngoại lệ, vì vậy tôi biết vấn đề nằm ở đâu. Tôi không vui khi giao dịch ngoại lệ con trỏ null để lưu một hoặc hai dòng mã tầm thường ở đây và ở đó.
Maciej Trybiło

Câu trả lời:


92

Tôi nghĩ nó có thể được mô tả bằng một ví dụ rất giả. Giả sử bạn có một phương thức trong Java in ra tất cả các phần tử trong ArrayList:

void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

Bây giờ, nếu bạn gọi phương thức đó như sau: someObject.foo (NULL); bạn có thể sẽ nhận được một NullPointerException khi nó cố gắng truy cập danh sách, trong trường hợp này là trong lệnh gọi list.size (); Bây giờ, có thể bạn sẽ không bao giờ gọi someObject.foo (NULL) với giá trị NULL như vậy. Tuy nhiên, bạn có thể đã nhận được ArrayList của mình từ một phương thức trả về NULL nếu nó gặp một số lỗi khi tạo ArrayList như someObject.foo (otherObject.getArrayList ());

Tất nhiên, bạn cũng sẽ gặp vấn đề nếu bạn làm điều gì đó như sau:

ArrayList list = NULL;
list.size();

Bây giờ, trong Objective-C, chúng ta có phương thức tương đương:

- (void)foo:(NSArray*)anArray
{
    int i;
    for(i = 0; i < [anArray count]; ++i){
        NSLog(@"%@", [[anArray objectAtIndex:i] stringValue];
    }
}

Bây giờ, nếu chúng ta có mã sau:

[someObject foo:nil];

chúng ta có cùng một tình huống trong đó Java sẽ tạo ra một NullPointerException. Đối tượng nil sẽ được truy cập đầu tiên tại [anArray count] Tuy nhiên, thay vì ném NullPointerException, Objective-C sẽ chỉ trả về 0 theo các quy tắc ở trên, vì vậy vòng lặp sẽ không chạy. Tuy nhiên, nếu chúng ta đặt vòng lặp để chạy một số lần nhất định, thì trước tiên chúng ta sẽ gửi một thông báo tới anArray tại [anArray objectAtIndex: i]; Điều này cũng sẽ trả về 0, nhưng vì objectAtIndex: trả về một con trỏ và con trỏ đến 0 là nil / NULL, NSLog sẽ được chuyển bằng nil mỗi lần qua vòng lặp. (Mặc dù NSLog là một hàm chứ không phải là một phương thức, nó sẽ in ra (null) nếu được truyền vào một NSString nil.

Trong một số trường hợp, sẽ tốt hơn nếu có NullPointerException, vì bạn có thể biết ngay rằng chương trình nào đó không ổn, nhưng trừ khi bạn nắm bắt được ngoại lệ, chương trình sẽ bị sập. (Trong C, cố gắng bỏ tham chiếu NULL theo cách này sẽ khiến chương trình gặp sự cố.) Trong Objective-C, nó chỉ gây ra hành vi thời gian chạy có thể không chính xác. Tuy nhiên, nếu bạn có một phương thức không bị phá vỡ nếu nó trả về 0 / nil / NULL / a zeroed struct, thì điều này giúp bạn không phải kiểm tra để đảm bảo rằng đối tượng hoặc các tham số là nil.


33
Có lẽ điều đáng nói là hành vi này đã là chủ đề của nhiều cuộc tranh luận trong cộng đồng Objective-C trong vài thập kỷ qua. Sự cân bằng giữa "an toàn" và "tiện lợi" được đánh giá khác nhau bởi những người khác nhau.
Mark Bessey

3
Trong thực tế, có rất nhiều sự đối xứng giữa việc truyền thông điệp sang nil và cách Objective-C hoạt động, đặc biệt là trong tính năng con trỏ yếu mới trong ARC. Con trỏ yếu sẽ tự động được làm bằng không. Vì vậy, thiết kế bạn API để nó có thể đáp ứng với 0 / nil / NIL / NULL, vv
Cthutu

1
Tôi nghĩ rằng nếu bạn làm điều myObject->iVarđó sẽ sụp đổ, bất kể nó là C hay không có đối tượng. (xin lỗi vì gravedig.)
11684

3
@ 11684 Đúng vậy, nhưng ->không còn là một phép toán Objective-C mà là một phép toán C chung chung.
bbum

1
Các gốc OSX gần đây khai thác / ẩn backdoor api có thể truy cập cho tất cả người dùng (không chỉ là quản trị viên) vì tin nhắn Nil obj-c của.
dcow

51

Một thông báo để nillàm gì và lợi nhuận nil, Nil, NULL, 0, hoặc 0.0.


41

Tất cả các bài viết khác đều đúng, nhưng có lẽ khái niệm mới là điều quan trọng ở đây.

Trong các cuộc gọi phương thức Objective-C, bất kỳ tham chiếu đối tượng nào có thể chấp nhận một bộ chọn đều là mục tiêu hợp lệ cho bộ chọn đó.

Điều này tiết kiệm rất nhiều "đối tượng mục tiêu của loại X?" mã - miễn là đối tượng nhận cài đặt bộ chọn, nó hoàn toàn không tạo ra sự khác biệt nào cho nó là lớp nào! nillà một NSObject chấp nhận bất kỳ bộ chọn nào - nó chỉ không làm bất cứ điều gì. Điều này giúp loại bỏ rất nhiều mã "kiểm tra nil, không gửi tin nhắn nếu đúng". (Khái niệm "nếu nó chấp nhận nó, nó sẽ thực hiện nó" cũng là thứ cho phép bạn tạo các giao thức , là những giao thức sắp xếp giống như các giao diện Java: một tuyên bố rằng nếu một lớp triển khai các phương thức đã nêu, thì nó sẽ tuân theo giao thức.)

Lý do cho điều này là để loại bỏ mã khỉ không làm bất cứ điều gì ngoại trừ giữ cho trình biên dịch vui vẻ. Có, bạn nhận được chi phí của một cuộc gọi phương thức nữa, nhưng bạn tiết kiệm thời gian của lập trình viên , đây là một nguồn tài nguyên đắt hơn nhiều so với thời gian của CPU. Ngoài ra, bạn đang loại bỏ nhiều mã và độ phức tạp có điều kiện hơn khỏi ứng dụng của mình.

Làm rõ cho những người phản đối: bạn có thể nghĩ đây không phải là một cách tốt để thực hiện, nhưng đó là cách ngôn ngữ được triển khai và đó là thành ngữ lập trình được đề xuất trong Objective-C (xem các bài giảng về lập trình iPhone của Stanford).


17

Điều đó có nghĩa là thời gian chạy không tạo ra lỗi khi objc_msgSend được gọi trên con trỏ nil; thay vào đó nó trả về một số giá trị (thường hữu ích). Tin nhắn có thể có tác dụng phụ không làm gì cả.

Nó hữu ích vì hầu hết các giá trị mặc định thích hợp hơn là một lỗi. Ví dụ:

[someNullNSArrayReference count] => 0

Tức là nil dường như là mảng trống. Ẩn tham chiếu NSView nil không làm gì cả. Tiện dụng hả?


12

Trong phần trích dẫn từ tài liệu, có hai khái niệm riêng biệt - có lẽ sẽ tốt hơn nếu tài liệu làm rõ hơn:

Có một số mô hình trong Ca cao tận dụng thực tế này.

Giá trị được trả về từ một tin nhắn thành nil cũng có thể hợp lệ:

Cái trước có lẽ phù hợp hơn ở đây: thường có thể gửi tin nhắn để nillàm cho mã đơn giản hơn - bạn không phải kiểm tra các giá trị null ở mọi nơi. Ví dụ chính tắc có thể là phương thức truy cập:

- (void)setValue:(MyClass *)newValue {
    if (value != newValue) { 
        [value release];
        value = [newValue retain];
    }
}

Nếu gửi tin nhắn đến nilkhông hợp lệ, phương pháp này sẽ phức tạp hơn - bạn phải có thêm hai lần kiểm tra để đảm bảo valuenewValuekhông phải niltrước khi gửi tin nhắn cho chúng.

Tuy nhiên, điểm thứ hai (các giá trị được trả về từ một thông báo nilcũng thường hợp lệ), thêm hiệu ứng cấp số nhân cho điểm trước. Ví dụ:

if ([myArray count] > 0) {
    // do something...
}

Mã này một lần nữa không yêu cầu kiểm tra các nilgiá trị và trôi chảy tự nhiên ...

Tất cả những điều này đã nói lên tính linh hoạt bổ sung mà việc có thể gửi tin nhắn nilsẽ đến với một số chi phí. Có khả năng ở một số giai đoạn bạn sẽ viết mã không thành công theo cách đặc biệt vì bạn đã không tính đến khả năng một giá trị có thể xảy ra nil.


12

Từ Greg Parker 's trang web :

Nếu chạy LLVM Compiler 3.0 (Xcode 4.2) trở lên

Tin nhắn về nil với kiểu trả về | trở về
Số nguyên lên đến 64 bit | 0
Dấu phẩy động lên đến dài gấp đôi | 0,0
Con trỏ | không
Kết cấu | {0}
Bất kỳ loại _Complex nào | {0, 0}

9

Nó có nghĩa là thường không phải kiểm tra các đối tượng nil ở khắp mọi nơi để đảm bảo an toàn - đặc biệt là:

[someVariable release];

hoặc, như đã lưu ý, các phương thức đếm và độ dài khác nhau đều trả về 0 khi bạn có giá trị nil, vì vậy bạn không phải thêm kiểm tra thêm cho nil trên tất cả:

if ( [myString length] > 0 )

hoặc cái này:

return [myArray count]; // say for number of rows in a table

Hãy nhớ rằng mặt khác của đồng xu là tiềm năng cho các lỗi, chẳng hạn như "if ([myString length] == 1)"
hatfinch

Làm thế nào đó là một lỗi? [myString length] trả về số không (nil) nếu myString là nil ... một điều tôi nghĩ có thể là vấn đề là [khung myView] mà tôi nghĩ có thể cung cấp cho bạn điều gì đó kỳ lạ nếu myView là nil.
Kendall Helmstetter Gelner

Nếu bạn thiết kế các lớp và phương thức của mình xung quanh khái niệm rằng các giá trị mặc định (0, nil, NO) có nghĩa là "không hữu ích" thì đây là một công cụ mạnh mẽ. Tôi không bao giờ phải kiểm tra chuỗi của mình xem có số không trước khi kiểm tra độ dài. Đối với tôi chuỗi rỗng là vô dụng và là một chuỗi không khi tôi đang xử lý văn bản. Tôi cũng là một nhà phát triển Java và tôi biết những người theo chủ nghĩa thuần túy Java sẽ tránh xa điều này nhưng nó giúp tiết kiệm rất nhiều mã hóa.
Jason Fuerstenberg

6

Đừng nghĩ về việc "người nhận là con số không"; Tôi đồng ý, đó khá kỳ lạ. Nếu bạn đang gửi tin nhắn đến nil, không có người nhận. Bạn chỉ gửi một tin nhắn cho không có gì.

Làm thế nào để đối phó với điều đó là một sự khác biệt triết học giữa Java và Objective-C: trong Java, đó là một lỗi; trong Objective-C, nó không phải là lựa chọn.


Có một ngoại lệ đối với hành vi đó trong java, nếu bạn gọi một hàm tĩnh trên null, nó tương đương với việc gọi hàm trên lớp thời gian biên dịch của biến (không quan trọng nếu nó là null).
Roman A. Taycher

6

Các thông báo objC được gửi đến nil và có giá trị trả về có kích thước lớn hơn sizeof (void *) tạo ra các giá trị không xác định trên bộ xử lý PowerPC. Ngoài ra, những thông báo này cũng khiến các giá trị không xác định được trả về trong các trường cấu trúc có kích thước lớn hơn 8 byte trên bộ xử lý Intel. Vincent Gable đã mô tả điều này một cách độc đáo trong bài đăng trên blog của mình


6

Tôi không nghĩ rằng bất kỳ câu trả lời nào khác đã đề cập rõ ràng điều này: nếu bạn đã quen với Java, bạn nên nhớ rằng mặc dù Objective-C trên Mac OS X có hỗ trợ xử lý ngoại lệ, nhưng đó là một tính năng ngôn ngữ tùy chọn có thể được bật / tắt bằng cờ trình biên dịch. Tôi đoán rằng thiết kế "gửi tin nhắn đến nillà an toàn" này có trước việc bao gồm hỗ trợ xử lý ngoại lệ trong ngôn ngữ và được thực hiện với mục tiêu tương tự: các phương thức có thể trả vềnil để chỉ ra lỗi và vì gửi tin nhắn nilthường trả vềnilngược lại, điều này cho phép chỉ báo lỗi truyền qua mã của bạn, do đó bạn không cần phải kiểm tra nó ở mọi thông báo. Bạn chỉ phải kiểm tra nó ở những điểm mà nó quan trọng. Cá nhân tôi nghĩ rằng việc tuyên truyền và xử lý ngoại lệ là cách tốt hơn để giải quyết mục tiêu này, nhưng không phải ai cũng có thể đồng ý với điều đó. (Mặt khác, tôi chẳng hạn như không thích yêu cầu của Java về việc bạn phải khai báo những ngoại lệ nào mà một phương thức có thể ném ra, điều này thường buộc bạn phải tuyên truyền về mặt cú pháp các khai báo ngoại lệ trong suốt mã của bạn; nhưng đó là một cuộc thảo luận khác.)

Tôi đã đăng một câu trả lời tương tự, nhưng dài hơn, cho câu hỏi liên quan "Việc khẳng định rằng mọi việc tạo đối tượng thành công có cần thiết trong Mục tiêu C không?" nếu bạn muốn biết thêm chi tiết.


Tôi chưa bao giờ nghĩ về nó theo cách đó. Đây dường như là một tính năng rất tiện lợi.
mk12

2
Dự đoán tốt, nhưng không chính xác về mặt lịch sử về lý do tại sao quyết định được đưa ra. Việc xử lý ngoại lệ đã tồn tại trong ngôn ngữ này ngay từ đầu, mặc dù các trình xử lý ngoại lệ ban đầu khá thô sơ so với thành ngữ hiện đại. Nil-eat-message là một lựa chọn thiết kế có ý thức bắt nguồn từ hành vi tùy chọn của đối tượng Nil trong Smalltalk. Khi các API NeXTSTEP ban đầu được thiết kế, chuỗi phương thức khá phổ biến và việc niltrả lại của nó được sử dụng để rút ngắn một chuỗi thành một NO-op.
bbum

2

C không biểu thị gì bằng 0 cho các giá trị nguyên thủy và NULL cho con trỏ (tương đương với 0 trong ngữ cảnh con trỏ).

Objective-C được xây dựng dựa trên biểu diễn của C không có gì bằng cách thêm nil. nil là một con trỏ đối tượng đến hư không. Mặc dù khác biệt về mặt ngữ nghĩa với NULL, chúng tương đương về mặt kỹ thuật với nhau.

Các NSObjects mới được cấp phát bắt đầu hoạt động với nội dung của chúng được đặt thành 0. Điều này có nghĩa là tất cả các con trỏ mà đối tượng đó phải đến các đối tượng khác bắt đầu bằng nil, vì vậy, chẳng hạn, bạn không cần đặt self. (Association) = nil trong các phương thức init.

Tuy nhiên, hành vi đáng chú ý nhất của nil là nó có thể gửi tin nhắn đến nó.

Trong các ngôn ngữ khác, như C ++ (hoặc Java), điều này sẽ làm hỏng chương trình của bạn, nhưng trong Objective-C, việc gọi một phương thức trên nil sẽ trả về giá trị bằng không. Điều này đơn giản hóa đáng kể các biểu thức, vì nó loại bỏ nhu cầu kiểm tra nil trước khi làm bất cứ điều gì:

// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }

// ...can be simplified to:
if ([name isEqualToString:@"Steve"]) { ... }

Nhận thức được cách nil hoạt động trong Objective-C cho phép sự tiện lợi này trở thành một tính năng và không phải là một lỗi tiềm ẩn trong ứng dụng của bạn. Đảm bảo đề phòng các trường hợp không mong muốn giá trị nil, bằng cách kiểm tra và quay lại sớm để không bị lỗi một cách âm thầm hoặc thêm NSParameterAssert để đưa ra một ngoại lệ.

Nguồn: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapter/ocObjectsClasses.html (Gửi tin nhắn tới nil).

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.