Có cách nào để thực thi gõ trên NSArray, NSMutableArray, v.v. không?


Câu trả lời:


35

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ó.


136
Tôi nghĩ rằng 'các lập trình viên Cocoa có kinh nghiệm' chỉ không biết họ đang thiếu gì - kinh nghiệm với Java cho thấy rằng các biến kiểu cải thiện khả năng hiểu mã và làm cho nhiều khả năng tái cấu trúc hơn.
tgdavies

11
Vâng, hỗ trợ Generics Java được rất nhiều chia theo đúng nghĩa của nó, bởi vì họ không đặt nó trong từ đầu ...
dertoni

28
Gotta đồng ý với @tgdavies. Tôi nhớ khả năng cấu trúc lại và cấu trúc lại intellisense mà tôi đã có với C #. Khi tôi muốn nhập động, tôi có thể lấy nó trong C # 4.0. Khi tôi muốn loại mạnh mẽ, tôi cũng có thể có. Tôi thấy có thời gian và địa điểm cho cả hai điều đó.
Steve

18
@charkrit Điều gì về Objective-C khiến nó 'không cần thiết'? Bạn có thấy cần thiết khi sử dụng C # không? Tôi nghe nhiều người nói rằng bạn không cần nó trong Objective-C nhưng tôi nghĩ những người này cũng nghĩ rằng bạn không cần nó trong bất kỳ ngôn ngữ nào, điều này khiến nó trở thành một vấn đề về sở thích / phong cách, không cần thiết.
bacar

17
Đây không phải là việc cho phép trình biên dịch của bạn thực sự giúp bạn tìm ra các vấn đề. Chắc chắn bạn có thể nói "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, hãy chỉ gắn các đối tượng của lớp đó vào đó." Nhưng nếu các thử nghiệm là cách duy nhất để thực thi điều đó, bạn sẽ gặp bất lợi. Càng xa khi viết mã, bạn thấy vấn đề, vấn đề đó càng tốn kém.
GreenKiwi

145

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.


Tôi đang lười biếng ở đây, nhưng tại sao điều này chỉ có trong XCode 7? Chúng ta có thể sử dụng nonnulltrong 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?
Guven

@Guven - nullability bước vào 6, bạn là chính xác, nhưng ObjC Generics không được giới thiệu cho đến khi Xcode 7.
Logan

Tôi khá chắc chắn rằng nó chỉ phụ thuộc vào phiên bản Xcode. Các generic chỉ là cảnh báo của trình biên dịch và không được chỉ ra trong thời gian chạy. Tôi khá chắc rằng bạn có thể biên dịch thành bất kỳ Os nào bạn muốn.
Logan

2
@DeanKelly - Bạn có thể làm như thế này: @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ỏ!
Logan

1
@Logan, không chỉ có một bộ tập lệnh, ngăn chặn việc xây dựng trong trường hợp phát hiện bất kỳ cảnh báo nào. Xcode có một cơ chế hoàn hảo mang tên "Cấu hình". Kiểm tra điều này, xem chánzo.org/blog/archives/2009-11-07/warnings
adnako

53

Đâ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ư NSProxyvà 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)selectorphươ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 @protocolchứ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 @protocoltươ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];
  }
}

MyProtocolkhai 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?


35
Kiểm thử đơn vị không thay thế cho một hệ thống loại tốt.
tba

8
Vâng, ai cần công cụ mà mảng đã nhập sẽ đủ khả năng. Tôi chắc chắn rằng @BarryWark (và bất kỳ ai khác đã chạm vào bất kỳ cơ sở mã nào mà anh ta cần sử dụng, đọc, hiểu và hỗ trợ) có phạm vi mã 100%. Tuy nhiên, tôi cá là bạn không sử dụng raw ids 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 Objects. 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.
funkybro

"Vịt gõ" ?? thật là vui nhộn! chưa bao giờ nghe thấy điều đó trước đây.
John Henckel

11

Bạn có thể phân lớp NSMutableArrayđể thực thi an toàn kiểu.

NSMutableArraylà 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ừ NSArrayvà 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đó 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

  • Nó kế thừa từ NSArrayđể hỗ trợ tuần tự hóa / giải mã hóa
  • Tù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


Tốt nhưng hiện tại nó thiếu tính năng gõ mạnh bằng cách ghi đè một số phương thức. Hiện tại nó chỉ gõ yếu.
Cœur

7

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.


1
Tôi đã thử nó, ý tưởng rất hay, nhưng thật đáng buồn là lỗi và nó không kiểm tra các yếu tố được thêm vào.
Binarian

4

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

0

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.


1
Tiết kiệm thời gian kiểm tra kiểu tĩnh tại thời điểm biên dịch, việc chỉnh sửa thậm chí còn tốt hơn. Đặc biệt hữu ích khi bạn đang viết lib để sử dụng lâu dài.
pinxue

0

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 AddBlocknhư

^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.


0

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];
}

0

hai xu của tôi để "sạch" hơn một chút:

sử dụng typedefs:

typedef NSArray<NSString *> StringArray;

trong mã chúng ta có thể làm:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.