Objective-C'de nil'e mesaj gönderme


107

Apple'ın Objective-C 2.0 belgelerini okuyan bir Java geliştiricisi olarak: " Nil'e ileti göndermenin " ne anlama geldiğini merak ediyorum - ne kadar yararlı olduğunu bırakın. Dokümantasyondan bir alıntı yapmak:

Cocoa'da bu durumdan yararlanan birkaç kalıp vardır. Bir mesajdan sıfıra döndürülen değer de geçerli olabilir:

  • Yöntem bir nesne, herhangi bir işaretçi türü, sizeof (void *) değerinden küçük veya ona eşit boyutta herhangi bir tamsayı skalası, kayan nokta, çift, uzun çift veya uzun uzun döndürürse, nil'e gönderilen bir ileti 0 döndürür .
  • Yöntem, Mac OS X ABI İşlev Çağrısı Kılavuzu tarafından tanımlandığı gibi kayıtlarda döndürülecek bir yapı döndürürse, sıfıra gönderilen bir ileti, veri yapısındaki her alan için 0,0 değerini döndürür. Diğer yapı veri türleri sıfırlarla doldurulmayacaktır.
  • Yöntem yukarıda belirtilen değer türlerinden başka bir şey döndürürse, sıfıra gönderilen bir mesajın dönüş değeri tanımsızdır.

Java beynimi yukarıdaki açıklamaya uymaktan mahrum etti mi? Yoksa bunu cam kadar net kılacak bir şey mi eksik?

Objective-C'deki mesajlar / alıcılar fikrini anlıyorum, olması gereken bir alıcı konusunda kafam karıştı nil.


2
Ayrıca bir Java geçmişim vardı ve başlangıçta bu güzel özellik beni çok korkuttu, ama şimdi onu kesinlikle GÜZEL buluyorum !;
Valentin Radu

1
Teşekkürler, bu harika bir soru. Bunun faydalarını gördünüz mü? Bu bana "hata değil, özellik" meselesi gibi geliyor. Java'nın beni bir istisna dışında tokatlayacağı hatalar almaya devam ediyorum, bu yüzden sorunun nerede olduğunu biliyordum. Burada ve orada bir veya iki satır önemsiz kod kaydetmek için boş işaretçi istisnasını takas etmekten mutlu değilim.
Maciej Trybiło

Yanıtlar:


92

Pekala, çok uydurma bir örnekle anlatılabileceğini düşünüyorum. Diyelim ki Java'da bir ArrayList'teki tüm öğeleri yazdıran bir yönteminiz var:

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

Şimdi, bu yöntemi şöyle çağırırsanız: someObject.foo (NULL); list.size () çağrısında muhtemelen listeye erişmeye çalıştığında bir NullPointerException alacaksınız; Şimdi, muhtemelen birObject.foo (NULL) 'u asla böyle NULL değeriyle çağırmazsınız. Bununla birlikte, ArrayList'inizi, someObject.foo (otherObject.getArrayList ()) gibi ArrayList oluştururken bir hatayla karşılaşırsa NULL döndüren bir yöntemden almış olabilirsiniz;

Elbette, böyle bir şey yaparsanız sorun yaşarsınız:

ArrayList list = NULL;
list.size();

Şimdi, Objective-C'de, eşdeğer bir yöntemimiz var:

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

Şimdi, eğer aşağıdaki koda sahipsek:

[someObject foo:nil];

Java'nın bir NullPointerException oluşturacağı aynı duruma sahibiz. İlk olarak nil nesnesine [anArray sayımı] 'nda erişilecektir. Bununla birlikte, Objective-C, bir NullPointerException oluşturmak yerine, yukarıdaki kurallara uygun olarak 0 döndürür, böylece döngü çalışmaz. Bununla birlikte, döngüyü belirli sayıda çalıştıracak şekilde ayarlarsak, o zaman önce [anArray objectAtIndex: i] 'deki birArray öğesine bir mesaj gönderiyoruz; Bu aynı zamanda 0 döndürür, ancak objectAtIndex: bir işaretçi döndürdüğünden ve 0'a bir gösterici nil / NULL olduğundan, NSLog döngü boyunca her seferinde sıfır geçirilecektir. (NSLog bir yöntem değil bir işlev olsa da, nil NSString geçirilirse (null) yazdırır.

Bazı durumlarda bir NullPointerException'a sahip olmak daha iyidir, çünkü programda bir sorun olduğunu hemen söyleyebilirsiniz, ancak istisnayı yakalamazsanız program çökecektir. (C'de, bu şekilde NULL başvurusunu kaldırmaya çalışmak, programın çökmesine neden olur.) Objective-C'de, bunun yerine muhtemelen yanlış çalışma zamanı davranışına neden olur. Ancak, 0 / nil / NULL / sıfırlanmış bir yapı döndürürse bozulmayan bir yönteminiz varsa, bu sizi nesnenin veya parametrelerin sıfır olduğundan emin olmak için kontrol etme zorunluluğundan kurtarır.


33
Muhtemelen bu davranışın, son birkaç on yılda Objective-C topluluğunda çok tartışılan bir konu olduğunu belirtmekte fayda var. "Güvenlik" ve "uygunluk" arasındaki değiş tokuş, farklı kişiler tarafından farklı şekilde değerlendirilir.
Mark Bessey

3
Pratikte, mesajları sıfıra geçirmek ve Objective-C'nin nasıl çalıştığı arasında, özellikle ARC'deki yeni zayıf işaretçiler özelliğinde çok fazla simetri vardır. Zayıf işaretçiler otomatik olarak sıfırlanır. Öyleyse
API'nizi

1
Ben bunu yaparsanız düşünüyorum myObject->iVaro C ise ne olursa olsun, çökecektir ile veya olmadan nesneler. (
gravedig

3
@ 11684 Bu doğru, ancak ->artık bir Objective-C işlemi değil, daha çok genel bir C-izm.
bbum

1
Son OSX kök / gizli arka kapı API istismar çünkü obj-c kullanıcısının Nil mesajlaşma tüm kullanıcıları (sadece yöneticiler) için erişilebilir.
dcow

51

Bir ileti için nilhiçbir şey ve döner yapar nil, Nil, NULL, 0, veya 0.0.


41

Diğer tüm gönderiler doğrudur, ancak burada önemli olan kavram belki de budur.

Objective-C yöntem çağrılarında, bir seçiciyi kabul edebilen herhangi bir nesne başvurusu, o seçici için geçerli bir hedeftir.

Bu, "X türünün hedef nesnesi mi?" kod - alıcı nesne seçiciyi uyguladığı sürece, hangi sınıf olduğu kesinlikle fark etmez ! nilHerhangi seçiciyi kabul ettiğini bir NSObject - sadece gelmez do şey. Bu, birçok "sıfır için kontrol edin, eğer doğruysa mesajı göndermeyin" kodunu da ortadan kaldırır. ("Kabul ederse uygular" kavramı da protokoller oluşturmanıza olanak sağlar Java arayüzleri gibi bir tür : Bir sınıf belirtilen yöntemleri uygularsa, o zaman protokole uyacağına dair bir bildirim.)

Bunun nedeni, derleyiciyi mutlu etmekten başka hiçbir şey yapmayan maymun kodunu ortadan kaldırmaktır. Evet, bir başka yöntem çağrısının ek yükünü elde edersiniz, ancak programcı zamanından tasarruf edersiniz , bu CPU zamanından çok daha pahalı bir kaynaktır. Ek olarak, uygulamanızdan daha fazla kodu ve daha fazla koşullu karmaşıklığı ortadan kaldırırsınız.

Olumsuz oy verenler için açıklama: Bunun iyi bir yol olmadığını düşünebilirsiniz, ancak dilin nasıl uygulandığı ve Objective-C'deki önerilen programlama deyimidir (Stanford iPhone programlama derslerine bakın).


17

Bunun anlamı, nil işaretçisinde objc_msgSend çağrıldığında çalışma zamanının bir hata üretmemesidir; bunun yerine bazı (genellikle yararlı) bir değer döndürür. Yan etkisi olabilecek mesajlar hiçbir şey yapmaz.

Kullanışlıdır çünkü varsayılan değerlerin çoğu bir hatadan daha uygundur. Örneğin:

[someNullNSArrayReference count] => 0

Yani, boş dizi olarak nil görünüyor. Sıfır NSView referansını gizlemek hiçbir şey yapmaz. Kullanışlı, ha?


12

Dokümantasyondan alınan alıntıda iki ayrı kavram vardır - belki dokümantasyon bunu daha açık hale getirirse daha iyi olabilir:

Cocoa'da bu durumdan yararlanan birkaç kalıp vardır.

Bir mesajdan sıfıra döndürülen değer de geçerli olabilir:

İlki muhtemelen burada daha uygundur: tipik olarak nilkodu daha basit hale getirmek için mesaj gönderebilmek - her yerde boş değerleri kontrol etmek zorunda değilsiniz. Kanonik örnek muhtemelen erişimci yöntemidir:

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

Mesaj göndermek nilgeçerli olmasaydı, bu yöntem daha karmaşık olurdu - emin olmak valueve newValueolmamak için iki ek kontrol yapmanız gerekir.nil ona mesaj göndermeden önce.

İkinci nokta (bir mesajdan döndürülen değerler nilde tipik olarak geçerlidir), yine de, birincisine bir çarpan etkisi ekler. Örneğin:

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

Bu kod için yine bir kontrol gerektirmez nil değerlerin ve doğal olarak akar ...

Bütün bunlar, mesaj gönderebilmenin sağladığı ek esnekliğin nilbir bedeli olduğunu söyledi. Bir değerin olabileceği ihtimalini hesaba katmadığınız için, bir aşamada tuhaf bir şekilde başarısız olan bir kod yazma olasılığınız vardır nil.


12

Gönderen Greg Parker 'ın sitesinde :

LLVM Compiler 3.0 (Xcode 4.2) veya sonraki bir sürümünü çalıştırıyorsanız

Dönüş türü ile sıfıra gönderilen iletiler | dönüş
64 bite kadar tamsayılar | 0
Uzun ikiye kadar kayan nokta | 0.0
İşaretçiler | sıfır
Yapılar | {0}
Herhangi bir _Complex türü | {0, 0}

9

Çoğu zaman güvenlik için her yerde sıfır nesneyi kontrol etmek zorunda olmamak anlamına gelir - özellikle:

[someVariable release];

veya belirtildiği gibi, çeşitli sayma ve uzunluk yöntemlerinin tümü bir sıfır değeriniz olduğunda 0 döndürür, bu nedenle sıfır için fazladan kontroller eklemeniz gerekmez:

if ( [myString length] > 0 )

veya bu:

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

Madalyonun diğer yüzünün "if ([myString length] == 1)" gibi hatalar için potansiyel olduğunu unutmayın
hatfinch

Bu nasıl bir böcek? myString nil ise [myString length] sıfır (nil) döndürür ... Sorun olabileceğini düşündüğüm bir şey, myView nil ise size tuhaf bir şey verebileceğini düşündüğüm [myView frame].
Kendall Helmstetter Gelner

Sınıflarınızı ve yöntemlerinizi, varsayılan değerlerin (0, nil, NO) "kullanışlı değil" anlamına geldiği kavramı etrafında tasarlarsanız, bu güçlü bir araçtır. Uzunluğu kontrol etmeden önce dizelerimin sıfır olup olmadığını kontrol etmek zorunda kalmam. Bana göre boş dize, metni işlerken kullanışsız ve sıfır bir dizedir. Aynı zamanda bir Java geliştiricisiyim ve Java uzmanlarının bundan kaçınacağını biliyorum, ancak çok fazla kodlama tasarrufu sağlıyor.
Jason Fuerstenberg

6

"Alıcının sıfır olduğunu" düşünmeyin; Bunun katılıyorum olduğunu oldukça garip. Eğer sıfıra mesaj gönderiyorsanız, alıcı yoktur. Sadece hiçbir şeye mesaj göndermiyorsun.

Bununla nasıl başa çıkılacağı Java ve Objective-C arasındaki felsefi bir farktır: Java'da bu bir hatadır; Objective-C'de, işlem yok.


Java'da bu davranışın bir istisnası vardır, null üzerinde statik bir işlevi çağırırsanız, bu, değişkenin derleme zamanı sınıfındaki işlevi çağırmaya eşdeğerdir (boş olup olmadığı önemli değildir).
Roman A. Taycher

6

Nil'e gönderilen ve dönüş değerleri sizeof'tan (void *) büyük olan ObjC mesajları PowerPC işlemcilerde tanımsız değerler üretir. Buna ek olarak, bu mesajlar Intel işlemcilerde boyutu 8 bayttan büyük olan yapı alanlarında da tanımsız değerlerin döndürülmesine neden olur. Vincent Gable bunu blog yazısında güzelce anlattı


6

Diğer yanıtlardan hiçbirinin bundan açıkça bahsettiğini sanmıyorum: Java'ya alışkınsanız, Mac OS X'te Objective-C'nin istisna işleme desteği varken, olabilen isteğe bağlı bir dil özelliği olduğunu unutmayın. bir derleyici bayrağıyla açılır / kapanır. Tahminimce, bu "mesaj gönderme nilgüvenli" tasarımı , dilde istisna işleme desteğinin dahil edilmesinden önce geliyor ve benzer bir amaç göz önünde bulundurularak yapıldı: yöntemler geri dönebilirnil hataları belirtmek ve bir mesaj gönderdikten sonranil genellikle geri döndüğündennilbu da hata göstergesinin kodunuzda yayılmasına izin verir, böylece her mesajda kontrol etmek zorunda kalmazsınız. Sadece önemli olduğu noktalarda kontrol etmelisiniz. Kişisel olarak istisna yaymanın ve işlemenin bu hedefe ulaşmanın daha iyi bir yolu olduğunu düşünüyorum, ancak herkes buna katılmayabilir. (Öte yandan, örneğin, Java'nın bir yöntemin hangi istisnaları atabileceğini bildirme zorunluluğundan hoşlanmıyorum, bu da sizi genellikle sözdizimsel olarak kodunuz boyunca istisna bildirimlerini yaymaya ; ama bu başka bir tartışma.)

İlgili soruya benzer ama daha uzun bir cevap gönderdim "Her nesne yaratımının Hedef C'de başarılı olduğunu iddia etmek gerekli mi?" Daha fazla ayrıntı istiyorsanız.


Bunu hiç böyle düşünmemiştim. Bu çok kullanışlı bir özellik gibi görünüyor.
mk12

2
İyi tahmin, ancak kararın neden verildiği konusunda tarihsel olarak yanlış. Orijinal istisna işleyicileri modern deyime kıyasla oldukça ilkel olsa da, dilde istisna işleme başından beri vardı. Nil-eats-message, Smalltalk'taki Nil nesnesinin isteğe bağlı davranışından türetilen bilinçli bir tasarım seçimiydi . Orijinal NeXTSTEP API'leri tasarlandığında, yöntem zincirleme oldukça yaygındı ve nilbir zinciri kısa devre yaparak bir NO-op'a geri döndürmek için sıklıkla kullanıldı.
bbum

2

C, hiçbir şeyi ilkel değerler için 0 olarak ve işaretçiler için NULL (bir işaretçi bağlamında 0'a eşdeğerdir) olarak temsil eder.

Objective-C, sıfır ekleyerek C'nin hiçbir şeyin temsilini temel alır. nil hiçbir şeye göstericidir. Anlamsal olarak NULL'dan farklı olsalar da, teknik olarak birbirlerine eşdeğerdirler.

Yeni tahsis edilen NSObjects, içeriği 0'a ayarlanmış olarak hayata başlar. Bu, nesnenin diğer nesnelere sahip olduğu tüm işaretçilerin sıfır olarak başlaması anlamına gelir, bu nedenle, örneğin, init yöntemlerinde self. (İlişkilendirme) = nil ayarlamasına gerek yoktur.

Sıfırın en dikkate değer davranışı, kendisine gönderilen mesajlara sahip olabilmesidir.

C ++ (veya Java) gibi diğer dillerde bu, programınızı çökertebilir, ancak Objective-C'de nil üzerinde bir yöntemi çağırmak sıfır değeri döndürür. Bu, herhangi bir şey yapmadan önce sıfır olup olmadığını kontrol etme ihtiyacını ortadan kaldırdığı için ifadeleri büyük ölçüde basitleştirir:

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

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

Objective-C'de sıfırın nasıl çalıştığının farkında olmak, bu kolaylığın uygulamanızda gizli bir hata değil, bir özellik olmasını sağlar. Sessizce başarısız olmak için erken kontrol edip geri dönerek veya bir istisna atmak için bir NSParameterAssert ekleyerek sıfır değerlerinin istenmeyen olduğu durumlara karşı koruma sağladığınızdan emin olun.

Kaynak: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (Mesajın sıfıra gönderilmesi).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.