Objective-C支持泛型吗?

13

我想知道Objective-C是否支持泛型?

例如,考虑一个方法:

-(void) sort: (NSMutableArray *) deck {
}

有没有办法让它只处理一副纸牌?
可以强制执行这样的规定吗?

-(void) sort: (NSMutableArray <Card *>) deck {
}

1
这个链接 https://dev59.com/wXRA5IYBdhLWcg3w4SDo 可能会有所帮助。 - Joseph Redfern
6个回答

12

自2015年起,Objective-C支持轻量级泛型,使用Xcode 7即可。

Xcode 7编译器会在类型不匹配时给出编译器警告。

例如,以下行会引发编译器警告,因为数组中的第二个对象导致了类型不匹配。该数组仅允许NSString对象。

NSArray <NSString *> *myArray = [@"str2", @1, @"str2"];

1
这些是“轻量级”泛型,意味着仅在直接传递给NSMutableArray时产生警告(自定义方法不会警告),请参见如何强制特定的数组项类型? 以获取更多信息。 - Top-Master

6
你可以使用Objective-C运行时提供的内省工具
基本上,它意味着您可以检查数组中的所有对象是否是类别(A类或其一个子类)或类的成员(类A),或者对象是否符合协议响应选择器(存在某个特定方法)。
-(void) sort: (NSMutableArray *) deck {
    for(id obj in deck){
        if(obj isKindOfClass:[A class]]){
            //this is of right class
        }
    }
}

你可以在NSArray上编写一个分类方法,对每个对象进行检查。
BOOL allAreKindOfA = [array allObjectsAreKindOfClass:[A class]];

通常情况下,您实际上不需要经常使用它,因为您知道将什么放入集合中。 如果您需要检查数组中对象的类型或能力,则可能表明您的架构存在问题。

另一个选项可能是NSMutableArray的子类,只接受特定的类。但要注意NSMutableArrayNSArray的子类说明,因为它们是类簇,所以不容易子类化。

注意:在我的其他答案中,我创建了一个NSMutableArray子类,使用一个块来测试是否满足某个要求。如果你针对成员类进行测试,这将完全符合你的要求。使用第二个块进行错误处理。


从像C#这样的强类型语言转过来,很难理解为什么没有一个强类型集合可用的思路。我还在阅读和学习中,还有很长的路要走,但是肯定可以锁定到协议或特定实现的集合将为应用程序提供巨大的类型安全性。我在这里错过了什么吗? - The Senator
@TheSenator:通常你知道你要放入数组的内容,这不够安全吗? - vikingosegundo
我不明白为什么在Objective C中一定要争论类型安全。我们应该接受Objective C是一种弱类型语言这个事实,并且假设调用者总是发送正确的对象类型,如果调用者不正确,则会出现“无法识别的选择器发送到实例”错误,而不是确保兼容的类类型。 - tia
Objective-C支持类似泛型的类型检查的方式并不是像Java或C#那样直接支持泛型,其中一个泛型集合只允许派生自单个基类的对象。更好的方法是支持参数化协议检查,这样我们可以实例化一个集合类型,使得进出它的所有东西都支持协议X。这保持了Objective-C的动态性,同时也允许我们在编译时捕获错误,而不是等待它们在运行时失败。Google Go通过推断结构类型自动完成此操作。 - David Jeske
Xcode 7已经增加了对泛型的支持。请参见:https://dev59.com/9Gsy5IYBdhLWcg3w-i7h#30736487 - Pedro Mancheno
显示剩余5条评论

5

从Xcode 7版本开始,苹果公司增加了对Objective-C泛型的支持。

NSArray <NSString *> *arrayOfStrings = @[@"a", @"b"];

NSDictionary <NSString *, NSDate *> *dictionaryOfDates = @{ @"a" : @1 };

1
NSArray <NSString *> *aarray = @[@"aa", @"bb", @1]; 在我的 Xcode 7 beta 中没有产生任何错误。 - vikingosegundo

4
不直接地,没有。有几种模拟的方法,但需要很多包装代码、样板代码和运行时开销。当我想要或需要适当的泛型时,我只需切换到Objective-C++并使用C++模板。 因此,如果你想为NSArray引入类型安全/检查,可以采用以下方法:
template <typename T>
class t_typed_NSMutableArray {
public:
    t_typed_NSMutableArray() : d_array([NSMutableArray new]) {}
    ~t_typed_NSMutableArray() { [d_array release]; }

    /* ... */

    T* operator[](const size_t& idx) {
        T* const obj([this->d_array objectAtIndex:idx]);
        assert([obj isKindOfClass:[T class]]);
        return obj;
    }

    void addObject(T* const obj) {
        assert([obj isKindOfClass:[T class]]);
        [this->d_array addObject:obj];
    }

private:
    NSMutableArray * const d_array;
};

使用中:

 t_typed_NSMutableArray<Card> array([self cards]); // < note this exact constructor is not defined

 Card * firstCard = array[0]; // << ok
 NSString * string = array[0]; // << warning

如果你使用类型安全的集合,那么在传递集合时也会进行重载检查,这样你就无法将 t_typed_NSArray<Card> 作为 t_typed_NSArray<NSURL> 进行传递。


4

MonomorphicArray的启发,我想到了另一个主意:

创建一个NSMutableArray的子类,该子类接受两个块:

  • AddBlock - 一个测试块,如果一个或多个要求得到满足,则仅在对象通过测试时添加该对象
  • FailBlock - 一个块,定义了如果测试不成功会发生什么。

AddBlock可以测试特定类成员身份,例如

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

FailBlock可以抛出异常、无声失败或将未通过测试的元素添加到另一个数组中。如果没有提供failBlock,则默认块会引发错误。

这些块将定义数组是充当通用数组还是过滤器。
我将为第二种情况给出完整的示例。

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

使用方式如下:

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

注意:此代码尚未进行全面测试。也许应该实现一些未实现的方法,以便在实际程序中使用。


4
这里有一个简单而有效的方法可以做到这一点(我已经在项目中使用了几年)。可悲的是,有人删除了答案,并且我的尝试将其恢复被拒绝了。再试一次:
你可以在Obj-C中重新实现一个精简版的C++模板,因为Obj-C封装了所有的C(而C++模板是带有一些改进的编译器/调试器支持的C宏):
这只需要在一个头文件中完成一次,有人已经为您完成了:

https://github.com/tomersh/Objective-C-Generics

你最终将得到100%合法的Obj-C代码,看起来像这样:
NSArray<CustomClass> anArray= ...
CustomClass a = anArray[0]; // works perfectly, and Xcode autocomplete works too!

这在XCode中都能正常运作,包括自动完成等功能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接