NSArray, NSMutableArray, vb. Üzerinde yazmaya zorlamanın bir yolu var mı?


Yanıtlar:


35

-addSomeClass:Derleme zamanı statik tip kontrolüne izin veren bir yöntemle bir kategori oluşturabilirsiniz (böylece derleyici, bu yöntem aracılığıyla farklı bir sınıf olduğunu bildiği bir nesneyi eklemeye çalışırsanız size bildirebilir), ancak bunu zorlamanın gerçek bir yolu yoktur. bir dizi yalnızca belirli bir sınıfın nesnelerini içerir.

Genel olarak, Objective-C'de böyle bir kısıtlamaya ihtiyaç yok gibi görünüyor. Deneyimli bir Kakao programcısının bu özellik için dilediğini hiç duymadım. Diğer dillerden programcılar gibi görünen tek kişi, hala bu dilleri düşünüyor. Bir dizide yalnızca belirli bir sınıfın nesnelerini istiyorsanız, oraya yalnızca o sınıfın nesnelerini yapıştırın. Kodunuzun düzgün çalışıp çalışmadığını test etmek istiyorsanız test edin.


137
Bence 'deneyimli Kakao programcıları' neyi kaçırdıklarını bilmiyorlar - Java deneyimi, tür değişkenlerinin kod anlamayı geliştirdiğini ve daha fazla yeniden düzenlemeyi mümkün kıldığını gösteriyor.
tgdavies

12
Java'nın Generics desteği kendi içinde büyük ölçüde bozuldu, çünkü başından
beri koymadılar

28
@Tgdavies ile aynı fikirde olmalıyım. C # ile sahip olduğum zeka ve yeniden düzenleme yeteneklerini özlüyorum. Dinamik yazım istediğimde, bunu C # 4.0'da alabilirim. Güçlü tipler istediğimde buna da sahip olabilirim. Bunların her ikisi için de bir zaman ve yer olduğunu buldum.
Steve

18
@charkrit Objective-C hakkında onu 'gerekli olmayan' yapan şey nedir? C # kullanırken gerekli olduğunu düşündünüz mü? Pek çok insanın Objective-C'de buna ihtiyacın olmadığını söylediğini duyuyorum ama bence bu insanlar ona herhangi bir dilde ihtiyacınız olmadığını düşünüyor, bu da onu bir tercih / stil meselesi yapıyor, zorunluluk değil.
Bacar

17
Bu, derleyicinizin problemleri bulmanıza gerçekten yardımcı olmasına izin vermekle ilgili değil mi? Elbette "Bir dizide yalnızca belirli bir sınıfın nesnelerini istiyorsanız, oraya yalnızca o sınıftaki nesneleri yapıştırın" diyebilirsiniz. Ancak bunu sağlamanın tek yolu testlerse, dezavantajlısınız demektir. Kodu yazmaktan ne kadar uzakta bir sorun bulursanız, bu sorun o kadar maliyetli olur.
GreenKiwi

145

Henüz kimse bunu buraya koymadı, bu yüzden yapacağım!

Tthis artık resmi olarak Objective-C'de destekleniyor. Xcode 7'den itibaren aşağıdaki sözdizimini kullanabilirsiniz:

NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];

Not

Bunların yalnızca derleyici uyarıları olduğunu ve teknik olarak yine de dizinize herhangi bir nesneyi ekleyebileceğinizi unutmamak önemlidir. Tüm uyarıları oluşturmayı engelleyecek hatalara dönüştüren komut dosyaları mevcuttur.


Burada tembellik yapıyorum, ancak bu neden yalnızca XCode 7'de mevcut? nonnullXCode 6'da kullanabiliriz ve hatırladığım kadarıyla aynı anda tanıtıldılar. Ayrıca, bu tür kavramların kullanımı XCode sürümüne mi yoksa iOS sürümüne mi bağlı?
Guven

@Guven - nullability 6 geldi, haklısınız, ancak ObjC jenerikleri Xcode 7'ye kadar tanıtılmadı.
Logan

Sadece Xcode sürümüne bağlı olduğundan oldukça eminim. Jenerikler yalnızca derleyici uyarılarıdır ve çalışma zamanında gösterilmez. İstediğin Os'a derleyebileceğinden oldukça eminim.
Logan

2
@DeanKelly - Bunu şöyle yapabilirsiniz: @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; Biraz hantal görünüyor, ama hile yapıyor!
Logan

1
@Logan, herhangi bir uyarı algılanması durumunda oluşturmayı engelleyen sadece betik seti yoktur. Xcode, "Yapılandırma" adlı mükemmel bir mekanizmaya sahiptir. Bunu kontrol edin boredzo.org/blog/archives/2009-11-07/warnings
adnako

53

Bu, güçlü tip dillerden (C ++ veya Java gibi) Python, Ruby veya Objective-C gibi daha zayıf veya dinamik olarak yazılmış dillere geçiş yapan kişiler için nispeten yaygın bir sorudur. Objective-C'de, çoğu nesne NSObject(type id) ' dan miras alır ( geri kalanı, type gibi başka bir kök sınıfından miras alır NSProxyve ayrıca tür olabilir id) ve herhangi bir nesneye herhangi bir mesaj gönderilebilir. Elbette, tanımadığı bir örneğe mesaj göndermek çalışma zamanı hatasına neden olabilir (ve ayrıca bir derleyici uyarısına neden olur.uygun -W bayrakları ile). Bir örnek gönderdiğiniz mesaja yanıt verdiği sürece, hangi sınıfa ait olduğu umurunuzda olmayabilir. Bu genellikle "ördek yazma" olarak adlandırılır, çünkü "ördek gibi şaka yapıyorsa [yani bir seçiciye yanıt veriyorsa], bir ördekdir [yani mesajı işleyebilir; hangi sınıf olduğunu kimin umursadığı]".

-(BOOL)respondsToSelector:(SEL)selectorYöntemle , bir örneğin çalışma zamanında bir seçiciye yanıt verip vermediğini test edebilirsiniz . Bir dizideki her örnekte bir yöntem çağırmak istediğinizi, ancak tüm örneklerin mesajı işleyebileceğinden emin olmadığınızı varsayarsak (bu nedenle yalnızca NSArray's kullanamazsınız -[NSArray makeObjectsPerformSelector:], bunun gibi bir şey çalışır:

for(id o in myArray) {
  if([o respondsToSelector:@selector(myMethod)]) {
    [o myMethod];
  }
}

Çağırmak istediğiniz yöntem (ler) i uygulayan örnekler için kaynak kodunu kontrol ederseniz, daha yaygın olan yaklaşım @protocol, bu yöntemleri içeren bir yöntemi tanımlamak ve söz konusu sınıfların bildirimlerinde bu protokolü uyguladığını bildirmek olacaktır. Bu kullanımda a @protocol, Java Arayüzüne veya C ++ soyut temel sınıfına benzer. Daha sonra her yönteme yanıt vermek yerine tüm protokole uygunluğu test edebilirsiniz. Önceki örnekte, pek bir fark yaratmazdı, ancak birden çok yöntemi çağırıyor olsaydınız, işleri basitleştirebilirdi. Örnek şu şekilde olacaktır:

for(id o in myArray) {
  if([o conformsToProtocol:@protocol(MyProtocol)]) {
    [o myMethod];
  }
}

varsayarsak MyProtocolbeyan eder myMethod. Bu ikinci yaklaşım, kodun amacını ilkinden daha fazla netleştirdiği için tercih edilir.

Genellikle, bu yaklaşımlardan biri sizi bir dizideki tüm nesnelerin belirli bir türde olup olmadığına aldırmaktan kurtarır. Hala umursuyorsanız, standart dinamik dil yaklaşımı birim testi, birim testi, birim testi yapmaktır. Bu gereksinimdeki bir gerileme, (muhtemelen kurtarılamaz) bir çalışma zamanı (derleme zamanı değil) hatası üreteceğinden, bir çökmeyi vahşice serbest bırakmamak için davranışı doğrulamak için test kapsamına sahip olmanız gerekir. Bu durumda, diziyi değiştiren bir işlem hazırlayın, ardından dizideki tüm örneklerin belirli bir sınıfa ait olduğunu doğrulayın. Uygun test kapsamı ile, örnek kimliğini doğrulamak için ek çalışma zamanı ek yüküne bile ihtiyacınız yoktur. Birim testi kapsamınız iyi, değil mi?


35
Birim testi, düzgün bir tip sistemin yerini tutmaz.
tba

8
Evet, yazılan dizilerin karşılayacağı aletlere kimin ihtiyacı var? Eminim @BarryWark (ve kullanması, okuması, anlaması ve desteklemesi gereken herhangi bir kod tabanına dokunan herkes)% 100 kod kapsamına sahiptir. Bununla birlikte, gerekmedikçe ham ids kullanmadığınıza bahse girerim , Java kodlayıcıları Objects'nin etrafından geçmez . Neden olmasın? Birim testleriniz varsa buna ihtiyacınız yok mu? Çünkü oradadır ve yazılan dizilerde olduğu gibi kodunuzu daha sürdürülebilir kılar. Görünüşe göre insanlar platforma yatırım yapmış ve bir noktayı kabul etmek istemiyorlar ve bu nedenle bu ihmalin aslında bir fayda olduğuna dair nedenler icat ediyorlar.
funkybro

"Ördek yazma" ?? bu çok komik! Bunu daha önce hiç duymamıştım.
John Henckel

11

NSMutableArrayTür güvenliğini sağlamak için alt sınıf yapabilirsiniz .

NSMutableArraybir sınıf kümesidir , bu nedenle alt sınıflandırma önemsiz değildir. Sonunda NSArrayçağrıları o sınıf içindeki bir diziden devraldım ve bu diziye ilettim. Sonuç olarak adlandırılan bir sınıftır olan alt sınıf kolay. İşte bulduğum şey:ConcreteMutableArray

Güncelleme: Mike Ash'in bir sınıf kümesini alt sınıflandırma hakkındaki bu blog gönderisine göz atın.

Bu dosyaları projenize dahil edin, ardından makroları kullanarak dilediğiniz türleri oluşturun:

MyArrayTypes.h

CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)

MyArrayTypes.m

CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)

Kullanım:

NSStringArray* strings = [NSStringArray array];
[strings add:@"Hello"];
NSString* str = [strings get:0];

[strings add:[User new]];  //compiler error
User* user = [strings get:0];  //compiler error

diğer düşünceler

  • NSArraySerileştirmeyi / serileştirmeyi desteklemek için'den miras alır
  • Zevkinize bağlı olarak, genel yöntemleri geçersiz kılmak / gizlemek isteyebilirsiniz:

    - (void) addObject:(id)anObject


Güzel ama şimdilik bazı yöntemleri geçersiz kılarak güçlü yazımdan yoksun. Şu anda sadece zayıf yazılıyor.
Cœur

7

Objective-C için bir derleme zamanı (önişlemci tarafından uygulanmış) jenerik uygulaması olan https://github.com/tomersh/Objective-C-Generics'e bir göz atın . Bu blog yazısı güzel bir genel bakışa sahip. Temel olarak derleme zamanı denetimi (uyarılar veya hatalar) alırsınız, ancak jenerikler için çalışma süresi cezası olmaz.


1
Denedim, çok iyi fikir, ama ne yazık ki buggy ve eklenen unsurları kontrol etmiyor.
Binarian

4

Bu Github Projesi tam olarak bu işlevi yerine getiriyor .

Daha sonra <>, C # 'da olduğu gibi parantezleri kullanabilirsiniz .

Örneklerinden:

NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed

0

Muhtemel bir yol NSArray'i alt sınıflamak olabilir, ancak Apple bunu yapmamayı önerir. Tipik bir NSArray için gerçek ihtiyacın iki katını düşünmek daha kolaydır.


1
Derleme sırasında statik tip kontrolüne sahip olmak için zamandan tasarruf sağlar, düzenleme daha da iyidir. Özellikle uzun süreli kullanım için lib yazarken faydalıdır.
2013

0

NSArray'in sınıf-küme doğasıyla ilgili sorunları önlemek için ivar'ı destekleyen bir NSArray nesnesi kullanan bir NSArray alt sınıfı oluşturdum. Bir nesnenin eklenmesini kabul etmek veya reddetmek için bloklar gerekir.

yalnızca NSString nesnelerine izin vermek için AddBlock,

^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
}

Bir tanımlayabilirsiniz FailBlock, filtreleme için incelikle başarısız başka diziye ekleyebilir veya - - bir eleman testinde başarısız olursa, ne yapacağına karar için bir istisna zam - Bu varsayılan değerdir.

VSBlockTestedObjectArray.h

#import <Foundation/Foundation.h>
typedef BOOL(^AddBlock)(id element); 
typedef void(^FailBlock)(id element); 

@interface VSBlockTestedObjectArray : NSMutableArray

@property (nonatomic, copy, readonly) AddBlock testBlock;
@property (nonatomic, copy, readonly) FailBlock failBlock;

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock;    
@end

VSBlockTestedObjectArray.m

#import "VSBlockTestedObjectArray.h"

@interface VSBlockTestedObjectArray ()
@property (nonatomic, retain) NSMutableArray *realArray;
-(void)errorWhileInitializing:(SEL)selector;
@end

@implementation VSBlockTestedObjectArray
@synthesize testBlock = _testBlock;
@synthesize failBlock = _failBlock;
@synthesize realArray = _realArray;


-(id)initWithCapacity:(NSUInteger)capacity
{
    if (self = [super init]) {
        _realArray = [[NSMutableArray alloc] initWithCapacity:capacity];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock 
             FailBlock:(FailBlock)failBlock 
              Capacity:(NSUInteger)capacity
{
    self = [self initWithCapacity:capacity];
    if (self) {
        _testBlock = [testBlock copy];
        _failBlock = [failBlock copy];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock
{
    return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0];
}

-(id)initWithTestBlock:(AddBlock)testBlock
{
    return [self initWithTestBlock:testBlock FailBlock:^(id element) {
        [NSException raise:@"NotSupportedElement" format:@"%@ faild the test and can't be add to this VSBlockTestedObjectArray", element];
    } Capacity:0];
}


- (void)dealloc {
    [_failBlock release];
    [_testBlock release];
    self.realArray = nil;
    [super dealloc];
}


- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if(self.testBlock(anObject))
        [self.realArray insertObject:anObject atIndex:index];
    else
        self.failBlock(anObject);
}

- (void) removeObjectAtIndex:(NSUInteger)index
{
    [self.realArray removeObjectAtIndex:index];
}

-(NSUInteger)count
{
    return [self.realArray count];
}

- (id) objectAtIndex:(NSUInteger)index
{
    return [self.realArray objectAtIndex:index];
}



-(void)errorWhileInitializing:(SEL)selector
{
    [NSException raise:@"NotSupportedInstantiation" format:@"not supported %@", NSStringFromSelector(selector)];
}
- (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;}

@end

Şunun gibi kullanın:

VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSString", element);
}];


VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSNumber class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSNumber", element);
}];


[stringArray addObject:@"test"];
[stringArray addObject:@"test1"];
[stringArray addObject:[NSNumber numberWithInt:9]];
[stringArray addObject:@"test2"];
[stringArray addObject:@"test3"];


[numberArray addObject:@"test"];
[numberArray addObject:@"test1"];
[numberArray addObject:[NSNumber numberWithInt:9]];
[numberArray addObject:@"test2"];
[numberArray addObject:@"test3"];


NSLog(@"%@", stringArray);
NSLog(@"%@", numberArray);

Bu sadece örnek bir koddur ve gerçek dünya uygulamasında asla kullanılmamıştır. bunu yapmak için muhtemelen uygulanmış daha fazla NSArray yöntemine ihtiyaç vardır.


0

C ++ ve amaç-c'yi karıştırırsanız (yani mm dosya türünü kullanarak), çift veya tuple kullanarak yazmayı zorlayabilirsiniz. Örneğin, aşağıdaki yöntemde, std :: pair türünde bir C ++ nesnesi oluşturabilir, onu OC sarmalayıcı türünde bir nesneye dönüştürebilir (tanımlamanız gereken std :: pair sarmalayıcısı) ve sonra bunu bazılarına iletebilirsiniz. OC nesnesini kullanmak için tekrar C ++ nesnesine dönüştürmeniz gereken diğer OC yöntemi. OC yöntemi yalnızca OC sarıcı türünü kabul eder, böylece tür güvenliğini sağlar. Tip güvenliğini kolaylaştırmak için daha gelişmiş C ++ özelliklerinden yararlanmak için tuple, variadic şablon, tip listesi bile kullanabilirsiniz.

- (void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath
{
 std::pair<UITableView*, NSIndexPath*> tableRow(tableView, indexPath);  
 ObjCTableRowWrapper* oCTableRow = [[[ObjCTableRowWrapper alloc] initWithTableRow:tableRow] autorelease];
 [self performSelector:@selector(selectRow:) withObject:oCTableRow];
}

0

iki sentim biraz "daha temiz" olacak:

typedef'leri kullanın:

typedef NSArray<NSString *> StringArray;

kodda şunları yapabiliriz:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];

0

2020, basit cevap. Sadece, türüne sahip değiştirilebilir bir diziye ihtiyacım olduğu oldu NSString.

Sözdizimi:

Type<ArrayElementType *> *objectName;

Misal:

@property(nonatomic, strong) NSMutableArray<NSString *> *buttonInputCellValues;
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.