Apakah ada cara untuk memaksakan pengetikan di NSArray, NSMutableArray, dll.?

Jawaban:

35

Anda dapat membuat kategori dengan -addSomeClass:metode untuk memungkinkan pemeriksaan tipe statis waktu kompilasi (sehingga kompilator dapat memberi tahu Anda jika Anda mencoba menambahkan objek yang diketahuinya adalah kelas yang berbeda melalui metode itu), tetapi tidak ada cara nyata untuk menerapkannya. sebuah array hanya berisi objek dari kelas tertentu.

Secara umum, sepertinya tidak ada kebutuhan akan batasan seperti itu di Objective-C. Saya rasa saya belum pernah mendengar programmer Cocoa berpengalaman menginginkan fitur itu. Satu-satunya orang yang tampaknya adalah programmer dari bahasa lain yang masih berpikir dalam bahasa tersebut. Jika Anda hanya menginginkan objek dari kelas tertentu dalam larik, hanya tempelkan objek kelas itu di sana. Jika Anda ingin menguji apakah kode Anda berperilaku dengan benar, ujilah.

Membuang
sumber
136
Saya pikir 'pemrogram Kakao berpengalaman' tidak tahu apa yang mereka lewatkan - pengalaman dengan Java menunjukkan bahwa variabel tipe meningkatkan pemahaman kode dan memungkinkan lebih banyak pemfaktoran ulang.
tgdavies
11
Nah, dukungan Java Generics sangat rusak dengan sendirinya, karena mereka tidak memasukkannya sejak awal ...
dertoni
28
Harus setuju dengan @tgdavies. Saya merindukan kemampuan intellisense dan refactoring yang saya miliki dengan C #. Saat saya menginginkan pengetikan dinamis, saya bisa mendapatkannya di C # 4.0. Ketika saya ingin barang tipe kuat, saya bisa memilikinya juga. Saya telah menemukan bahwa ada waktu dan tempat untuk kedua hal itu.
Steve
18
@charkrit Ada apa dengan Objective-C yang membuatnya 'tidak perlu'? Apakah Anda merasa perlu saat menggunakan C #? Saya mendengar banyak orang mengatakan Anda tidak membutuhkannya di Objective-C tetapi saya pikir orang yang sama ini berpikir Anda tidak membutuhkannya dalam bahasa apa pun, yang membuatnya menjadi masalah preferensi / gaya, bukan kebutuhan.
bacar
17
Bukankah ini tentang mengizinkan kompiler Anda untuk benar-benar membantu Anda menemukan masalah. Tentu Anda bisa mengatakan "Jika Anda hanya ingin objek dari kelas tertentu dalam larik, hanya tempelkan objek kelas itu di sana." Tetapi jika tes adalah satu-satunya cara untuk menerapkannya, Anda berada pada posisi yang kurang menguntungkan. Semakin jauh dari penulisan kode Anda menemukan masalah, semakin mahal masalah itu.
GreenKiwi
145

Belum ada yang meletakkan ini di sini, jadi saya akan melakukannya!

Ini sekarang secara resmi didukung di Objective-C. Pada Xcode 7, Anda dapat menggunakan sintaks berikut:

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

Catatan

Penting untuk dicatat bahwa ini hanyalah peringatan kompiler dan secara teknis Anda masih dapat memasukkan objek apa pun ke dalam array Anda. Ada skrip yang tersedia yang mengubah semua peringatan menjadi kesalahan yang akan mencegah pembuatan.

Logan
sumber
Saya malas di sini, tetapi mengapa ini hanya tersedia di XCode 7? Kita dapat menggunakan nonnulldi XCode 6 dan sejauh yang saya ingat, mereka diperkenalkan pada saat yang bersamaan. Juga, apakah penggunaan konsep tersebut bergantung pada versi XCode atau pada versi iOS?
Guven
@Guven - nullability masuk 6, Anda benar, tetapi obat generik ObjC tidak diperkenalkan sampai Xcode 7.
Logan
Saya cukup yakin ini hanya bergantung pada versi Xcode. Generik hanya peringatan kompilator dan tidak ditunjukkan pada waktu proses. Saya cukup yakin Anda bisa mengkompilasi ke Os apa pun yang Anda inginkan.
Logan
2
@DeanKelly - Anda dapat melakukannya seperti ini: @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; Terlihat sedikit kikuk, tetapi berhasil!
Logan
1
@Logan, tidak hanya ada sekumpulan skrip, yang mencegah pembangunan jika ada peringatan yang terdeteksi. Xcode memiliki mekanisme sempurna bernama "Konfigurasi". Lihat ini boredzo.org/blog/archives/2009-11-07/warnings
adnako
53

Ini adalah pertanyaan yang relatif umum untuk orang-orang yang beralih dari bahasa tipe kuat (seperti C ++ atau Java) ke bahasa dengan tipe lebih lemah atau dinamis seperti Python, Ruby, atau Objective-C. Di Objective-C, sebagian besar objek mewarisi dari NSObject(tipe id) (sisanya mewarisi dari kelas root lain seperti NSProxydan juga bisa berupa tipe id), dan pesan apa pun dapat dikirim ke objek apa pun. Tentu saja, mengirim pesan ke instance yang tidak dikenali dapat menyebabkan error runtime (dan juga akan menyebabkan peringatan compiler.dengan flag -W yang sesuai). Selama sebuah instance menanggapi pesan yang Anda kirim, Anda mungkin tidak peduli kelas itu miliknya. Ini sering disebut sebagai "mengetik bebek" karena "jika dukun seperti bebek [yaitu menanggapi pemilih], itu adalah bebek [yaitu dapat menangani pesan; siapa yang peduli kelas apa]".

Anda dapat menguji apakah sebuah instance merespons pemilih pada waktu proses dengan -(BOOL)respondsToSelector:(SEL)selectormetode tersebut. Dengan asumsi Anda ingin memanggil metode pada setiap instance dalam array tetapi tidak yakin bahwa semua instance dapat menangani pesan tersebut (jadi Anda tidak bisa hanya menggunakan NSArrays -[NSArray makeObjectsPerformSelector:], sesuatu seperti ini akan berfungsi:

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

Jika Anda mengontrol kode sumber untuk instance yang mengimplementasikan metode yang ingin Anda panggil, pendekatan yang lebih umum adalah dengan menentukan a @protocolyang berisi metode tersebut dan mendeklarasikan bahwa kelas yang dimaksud mengimplementasikan protokol itu dalam deklarasi mereka. Dalam penggunaan ini, a @protocolanalog dengan Java Interface atau kelas dasar abstrak C ++. Anda kemudian dapat menguji kesesuaian dengan seluruh protokol daripada menanggapi setiap metode. Dalam contoh sebelumnya, itu tidak akan membuat banyak perbedaan, tetapi jika Anda memanggil beberapa metode, itu mungkin menyederhanakan banyak hal. Contohnya adalah:

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

dengan asumsi MyProtocolmenyatakan myMethod. Pendekatan kedua ini disukai karena menjelaskan maksud kode lebih dari yang pertama.

Seringkali, salah satu pendekatan ini membebaskan Anda dari kepedulian apakah semua objek dalam array memiliki tipe tertentu. Jika Anda masih peduli, pendekatan bahasa dinamis standar adalah pengujian unit, pengujian unit, pengujian unit. Karena regresi dalam persyaratan ini akan menghasilkan error (kemungkinan tidak dapat dipulihkan) runtime (bukan waktu kompilasi), Anda harus memiliki cakupan pengujian untuk memverifikasi perilaku tersebut sehingga Anda tidak melepaskan crasher ke alam liar. Dalam kasus ini, lakukan operasi yang mengubah array, lalu verifikasi bahwa semua instance dalam array tersebut milik kelas tertentu. Dengan cakupan pengujian yang tepat, Anda bahkan tidak memerlukan tambahan waktu proses untuk memverifikasi identitas instance. Anda memiliki cakupan pengujian unit yang baik, bukan?

Barry Wark
sumber
35
Pengujian unit bukanlah pengganti sistem tipe yang layak.
tba
8
Ya, siapa yang membutuhkan perkakas yang bisa dibeli oleh array yang diketik. Saya yakin @BarryWark (dan siapa pun yang telah menyentuh basis kode apa pun yang perlu dia gunakan, baca, pahami, dan dukung) memiliki cakupan kode 100%. Namun saya yakin Anda tidak menggunakan bahan mentah idkecuali jika diperlukan, lebih dari pembuat kode Java menyebarkan Objects. Kenapa tidak? Tidak membutuhkannya jika Anda punya tes unit? Karena itu ada dan membuat kode Anda lebih mudah dipelihara, seperti array yang diketik. Kedengarannya seperti orang-orang yang berinvestasi di platform tidak ingin memberikan poin, dan karena itu menemukan alasan mengapa kelalaian ini sebenarnya bermanfaat.
funkybro
"Mengetik bebek" ?? itu lucu! tidak pernah mendengar hal itu sebelumnya.
John Henckel
11

Anda dapat membuat subkelas NSMutableArrayuntuk menegakkan keamanan tipe.

NSMutableArrayadalah cluster kelas , jadi subclass tidak sepele. Saya akhirnya mewarisi dari NSArraydan meneruskan pemanggilan ke array di dalam kelas itu. Hasilnya adalah kelas yang disebut ConcreteMutableArrayyang merupakan mudah untuk subclass. Inilah yang saya dapatkan:

Pembaruan: checkout posting blog ini dari Mike Ash tentang subclass cluster kelas.

Sertakan file tersebut di proyek Anda, lalu buat jenis apa pun yang Anda inginkan dengan menggunakan makro:

MyArrayTypes.h

CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)

MyArrayTypes.m

CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)

Pemakaian:

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

Pikiran Lainnya

  • Ini mewarisi dari NSArrayuntuk mendukung serialisasi / deserialization
  • Bergantung pada selera Anda, Anda mungkin ingin mengganti / menyembunyikan metode umum seperti

    - (void) addObject:(id)anObject

bendytree.dll
sumber
Bagus tetapi untuk saat ini tidak ada pengetikan yang kuat dengan mengganti beberapa metode. Saat ini hanya pengetikan yang lemah.
Cœur
7

Lihat https://github.com/tomersh/Objective-C-Generics , implementasi generik waktu kompilasi (diimplementasikan preprocessor) untuk Objective-C. Posting blog ini memiliki gambaran yang bagus. Pada dasarnya Anda mendapatkan pemeriksaan waktu kompilasi (peringatan atau kesalahan), tetapi tidak ada hukuman waktu proses untuk obat generik.

Barry Wark
sumber
1
Saya mencobanya, ide yang sangat bagus, tetapi sayangnya bermasalah dan tidak memeriksa elemen yang ditambahkan.
Binarian
4

Proyek Github ini mengimplementasikan fungsi itu dengan tepat.

Anda kemudian dapat menggunakan <>tanda kurung, seperti yang Anda lakukan di C #.

Dari contoh mereka:

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

Cara yang mungkin bisa dilakukan dengan membuat subclass NSArray tetapi Apple merekomendasikan untuk tidak melakukannya. Lebih mudah untuk berpikir dua kali tentang kebutuhan sebenarnya untuk NSArray yang diketik.

mouviciel
sumber
1
Ini menghemat waktu untuk memiliki pemeriksaan tipe statis pada waktu kompilasi, pengeditan bahkan lebih baik. Sangat membantu saat Anda menulis lib untuk penggunaan jangka panjang.
pinxue
0

Saya membuat subclass NSArray yang menggunakan objek NSArray sebagai backing ivar untuk menghindari masalah dengan sifat class-cluster NSArray. Dibutuhkan blok untuk menerima atau menolak penambahan suatu objek.

untuk hanya mengizinkan objek NSString, Anda dapat mendefinisikan AddBlocksebagai

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

Anda dapat menentukan a FailBlockuntuk memutuskan apa yang harus dilakukan, jika sebuah elemen gagal dalam pengujian - gagal dengan baik untuk pemfilteran, tambahkan ke larik lain, atau - ini default - buat pengecualian.

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

Gunakan seperti:

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);

Ini hanyalah contoh kode dan tidak pernah digunakan dalam aplikasi dunia nyata. untuk melakukannya mungkin perlu lebih banyak metode NSArray yang diimplementasikan.

vikingosegundo.dll
sumber
0

Jika Anda mencampur c ++ dan objektif-c (yaitu menggunakan tipe file mm), Anda dapat memaksakan pengetikan menggunakan pair atau tuple. Misalnya, dalam metode berikut, Anda dapat membuat objek C ++ berjenis std :: pair, mengonversinya menjadi objek berjenis OC wrapper (pembungkus std :: pair yang perlu Anda definisikan), lalu meneruskannya ke beberapa metode OC lainnya, di mana Anda perlu mengubah objek OC kembali ke objek C ++ untuk menggunakannya. Metode OC hanya menerima tipe pembungkus OC, sehingga memastikan keamanan tipe. Anda bahkan dapat menggunakan tuple, template variadic, typelist untuk memanfaatkan fitur C ++ yang lebih canggih untuk memfasilitasi keamanan tipe.

- (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];
}
colin
sumber
0

dua sen saya agar sedikit "lebih bersih":

gunakan typedefs:

typedef NSArray<NSString *> StringArray;

dalam kode yang bisa kita lakukan:

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