Bisakah saya membuat NSMutableArray
contoh di mana semua elemen bertipe SomeClass
?
Jawaban:
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.
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.
nonnull
di 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?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
Terlihat sedikit kikuk, tetapi berhasil!
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 NSProxy
dan 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)selector
metode 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 NSArray
s -[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 @protocol
yang berisi metode tersebut dan mendeklarasikan bahwa kelas yang dimaksud mengimplementasikan protokol itu dalam deklarasi mereka. Dalam penggunaan ini, a @protocol
analog 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 MyProtocol
menyatakan 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?
id
kecuali jika diperlukan, lebih dari pembuat kode Java menyebarkan Object
s. 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.
Anda dapat membuat subkelas NSMutableArray
untuk menegakkan keamanan tipe.
NSMutableArray
adalah cluster kelas , jadi subclass tidak sepele. Saya akhirnya mewarisi dari NSArray
dan meneruskan pemanggilan ke array di dalam kelas itu. Hasilnya adalah kelas yang disebut ConcreteMutableArray
yang 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
NSArray
untuk mendukung serialisasi / deserializationBergantung pada selera Anda, Anda mungkin ingin mengganti / menyembunyikan metode umum seperti
- (void) addObject:(id)anObject
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.
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
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.
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 AddBlock
sebagai
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
Anda dapat menentukan a FailBlock
untuk 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.
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];
}