Câu trả lời:
Bạn có thể tạo một danh mục với một -addSomeClass:
phương thức để cho phép kiểm tra kiểu tĩnh trong thời gian biên dịch (vì vậy trình biên dịch có thể cho bạn biết nếu bạn cố gắng thêm một đối tượng mà nó biết là một lớp khác thông qua phương thức đó), nhưng không có cách nào thực sự để thực thi điều đó một mảng chỉ chứa các đối tượng của một lớp nhất định.
Nói chung, dường như không cần phải có những ràng buộc như vậy trong Objective-C. Tôi không nghĩ rằng tôi đã từng nghe một lập trình viên Cocoa có kinh nghiệm mong muốn tính năng đó. Những người duy nhất dường như là lập trình viên từ các ngôn ngữ khác vẫn đang suy nghĩ bằng các ngôn ngữ đó. Nếu bạn chỉ muốn các đối tượng của một lớp nhất định trong một mảng, chỉ gắn các đối tượng của lớp đó vào đó. Nếu bạn muốn kiểm tra xem mã của mình có hoạt động đúng hay không, hãy kiểm tra nó.
Chưa ai đưa cái này lên đây nên tôi sẽ làm!
Tính năng này hiện đã được hỗ trợ chính thức trong Objective-C. Đối với Xcode 7, bạn có thể sử dụng cú pháp sau:
NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];
Ghi chú
Điều quan trọng cần lưu ý là đây chỉ là cảnh báo của trình biên dịch và về mặt kỹ thuật, bạn vẫn có thể chèn bất kỳ đối tượng nào vào mảng của mình. Có sẵn các tập lệnh biến tất cả các cảnh báo thành lỗi sẽ ngăn cản việc xây dựng.
nonnull
trong XCode 6 và theo như tôi nhớ, chúng được giới thiệu cùng lúc. Ngoài ra, việc sử dụng các khái niệm như vậy có phụ thuộc vào phiên bản XCode hay phiên bản iOS không?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
Trông hơi lắt léo , nhưng thực sự là một mẹo nhỏ!
Đây là một câu hỏi tương đối phổ biến đối với những người chuyển đổi từ các ngôn ngữ gõ mạnh (như C ++ hoặc Java) sang các ngôn ngữ gõ động hoặc yếu hơn như Python, Ruby hoặc Objective-C. Trong Objective-C, hầu hết các đối tượng kế thừa từ NSObject
(type id
) (phần còn lại kế thừa từ một lớp gốc khác chẳng hạn như NSProxy
và cũng có thể là type id
) và bất kỳ thông báo nào cũng có thể được gửi đến bất kỳ đối tượng nào. Tất nhiên, việc gửi thông báo đến một phiên bản mà nó không nhận ra có thể gây ra lỗi thời gian chạy (và cũng sẽ gây ra cảnh báo trình biên dịchvới cờ -W thích hợp). Miễn là một cá thể phản hồi tin nhắn bạn gửi, bạn có thể không quan tâm nó thuộc lớp nào. Điều này thường được gọi là "vịt gõ" bởi vì "nếu nó kêu như vịt [tức là phản hồi với bộ chọn], nó là một con vịt [tức là nó có thể xử lý thông báo; ai quan tâm nó thuộc lớp nào]".
Bạn có thể kiểm tra xem một phiên bản có phản hồi với một bộ chọn tại thời điểm chạy hay không bằng -(BOOL)respondsToSelector:(SEL)selector
phương pháp này. Giả sử bạn muốn gọi một phương thức trên mọi trường hợp trong một mảng nhưng không chắc rằng tất cả các trường hợp đều có thể xử lý thông báo (vì vậy bạn không thể chỉ sử dụng NSArray
's -[NSArray makeObjectsPerformSelector:]
, một cái gì đó như thế này sẽ hoạt động:
for(id o in myArray) {
if([o respondsToSelector:@selector(myMethod)]) {
[o myMethod];
}
}
Nếu bạn kiểm soát mã nguồn cho các trường hợp triển khai (các) phương thức bạn muốn gọi, thì cách tiếp cận phổ biến hơn sẽ là xác định một @protocol
chứa các phương thức đó và khai báo rằng các lớp được đề cập thực hiện giao thức đó trong khai báo của chúng. Trong cách sử dụng này, a @protocol
tương tự với Giao diện Java hoặc lớp cơ sở trừu tượng C ++. Sau đó, bạn có thể kiểm tra sự phù hợp với toàn bộ giao thức thay vì phản ứng với từng phương pháp. Trong ví dụ trước, nó sẽ không tạo ra nhiều khác biệt, nhưng nếu bạn đang gọi nhiều phương thức, nó có thể đơn giản hóa mọi thứ. Ví dụ sau đó sẽ là:
for(id o in myArray) {
if([o conformsToProtocol:@protocol(MyProtocol)]) {
[o myMethod];
}
}
MyProtocol
khai báo giả định myMethod
. Cách tiếp cận thứ hai này được ưa chuộng vì nó làm rõ mục đích của mã hơn cách tiếp cận đầu tiên.
Thông thường, một trong những cách tiếp cận này giúp bạn giải phóng khỏi việc quan tâm liệu tất cả các đối tượng trong một mảng có thuộc một kiểu nhất định hay không. Nếu bạn vẫn quan tâm, cách tiếp cận ngôn ngữ động tiêu chuẩn là kiểm tra đơn vị, kiểm tra đơn vị, kiểm tra đơn vị. Bởi vì một hồi quy trong yêu cầu này sẽ tạo ra lỗi thời gian chạy (không phải thời gian biên dịch) (không phải thời gian biên dịch) (không phải thời gian biên dịch), bạn cần có phạm vi kiểm tra để xác minh hành vi để không phát hành lỗi tự nhiên. Trong trường hợp này, hãy thực hiện một hoạt động sửa đổi mảng, sau đó xác minh rằng tất cả các cá thể trong mảng đều thuộc về một lớp nhất định. Với phạm vi kiểm tra thích hợp, bạn thậm chí không cần chi phí thời gian chạy bổ sung để xác minh danh tính phiên bản. Bạn có phạm vi kiểm tra đơn vị tốt, phải không?
id
s ngoại trừ trường hợp cần thiết, bất kỳ thứ gì nhiều hơn những người viết mã Java chuyển xung quanh Object
s. Tại sao không? Không cần nó nếu bạn đã có các bài kiểm tra đơn vị? Bởi vì nó ở đó và làm cho mã của bạn dễ bảo trì hơn, cũng như các mảng đã nhập. Có vẻ như những người đầu tư vào nền tảng không muốn thừa nhận một điểm, và do đó, việc phát minh ra lý do tại sao sự thiếu sót này trên thực tế là một lợi ích.
Bạn có thể phân lớp NSMutableArray
để thực thi an toàn kiểu.
NSMutableArray
là một cụm lớp , vì vậy phân lớp không phải là tầm thường. Tôi đã kết thúc việc kế thừa từ NSArray
và chuyển tiếp các lời gọi tới một mảng bên trong lớp đó. Kết quả là một lớp được gọi ConcreteMutableArray
đó là dễ dàng để phân lớp. Đây là những gì tôi nghĩ ra:
Cập nhật: kiểm tra bài đăng blog này từ Mike Ash về phân lớp con một nhóm lớp.
Bao gồm các tệp đó trong dự án của bạn, sau đó tạo bất kỳ loại nào bạn muốn bằng cách sử dụng macro:
MyArrayTypes.h
CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)
MyArrayTypes.m
CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)
Sử dụng:
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
Suy nghĩ khác
NSArray
để hỗ trợ tuần tự hóa / giải mã hóaTùy thuộc vào sở thích của bạn, bạn có thể muốn ghi đè / ẩn các phương thức chung chung như
- (void) addObject:(id)anObject
Hãy xem https://github.com/tomersh/Objective-C-Generics , một triển khai chung thời gian biên dịch (do bộ xử lý thực hiện) cho Objective-C. Bài đăng trên blog này có một cái nhìn tổng quan tốt đẹp. Về cơ bản, bạn nhận được kiểm tra thời gian biên dịch (cảnh báo hoặc lỗi), nhưng không có hình phạt thời gian chạy cho generic.
Dự án Github này thực hiện chính xác chức năng đó.
Sau đó, bạn có thể sử dụng <>
dấu ngoặc, giống như bạn làm trong C #.
Từ các ví dụ của họ:
NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
Một cách khả thi có thể là phân lớp con NSArray nhưng Apple khuyến cáo không nên làm điều đó. Sẽ đơn giản hơn khi nghĩ lại nhu cầu thực tế đối với một NSArray đã nhập.
Tôi đã tạo một lớp con NSArray đang sử dụng một đối tượng NSArray làm hỗ trợ ivar để tránh các vấn đề với bản chất cụm lớp của NSArray. Cần các khối để chấp nhận hoặc từ chối việc thêm một đối tượng.
để chỉ cho phép đối tượng NSString, bạn có thể xác định một AddBlock
như
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
Bạn có thể xác định a FailBlock
để quyết định phải làm gì, nếu một phần tử không thành công trong quá trình kiểm tra - không thành công khi lọc, hãy thêm nó vào một mảng khác hoặc - đây là mặc định - nêu ra một ngoại lệ.
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
Sử dụng nó như:
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);
Đây chỉ là một mã ví dụ và chưa bao giờ được sử dụng trong ứng dụng thế giới thực. để làm như vậy nó có thể cần thực hiện phương thức NSArray mor.
Nếu bạn kết hợp c ++ và mục tiêu-c (tức là sử dụng loại tệp mm), bạn có thể thực thi nhập bằng cặp hoặc tuple. Ví dụ: trong phương pháp sau, bạn có thể tạo một đối tượng C ++ kiểu std :: pair, chuyển đổi nó thành một đối tượng thuộc loại OC wrapper (wrapper của std :: pair mà bạn cần xác định), rồi chuyển nó cho một số phương pháp OC khác, trong đó bạn cần chuyển đổi đối tượng OC trở lại đối tượng C ++ để sử dụng nó. Phương pháp OC chỉ chấp nhận kiểu bao bọc OC, do đó đảm bảo an toàn cho kiểu. Bạn thậm chí có thể sử dụng tuple, variadic template, typelist để tận dụng các tính năng C ++ nâng cao hơn nhằm tạo điều kiện an toàn cho kiểu.
- (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];
}