【问题标题】:Does Objective-C support Generics?Objective-C 是否支持泛型?
【发布时间】:2011-11-18 23:43:50
【问题描述】:

我想知道 Objective-C 是否提供任何对泛型的支持?

例如,考虑一个方法:

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

我有什么办法让它只处理一副纸牌吗?
这样的事情可以强制执行吗?

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

【问题讨论】:

标签: objective-c cocoa-touch generics


【解决方案1】:

Objective-C 从 2015 年开始支持 轻量级泛型,在 Xcode 7 中。

如果类型不匹配,Xcode 7 编译器会给你编译器警告。

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

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

【讨论】:

  • 那些是“轻量级”泛型,这意味着它只会在直接传递给NSMutableArray时产生警告(自定义方法不会发出警告),请参阅How to force specific array item type?了解更多信息
【解决方案2】:

您可以使用objective-c运行时提供的introspection tools

基本上,这意味着您可以检查数组中的所有对象是否为kind of class(A 类或其一个子类)或member of class(A 类),或者对象是否为conforms to a protocol 或@ 987654325@(存在某种方法)。

-(void) sort: (NSMutableArray *) deck {
    for(id obj in deck){
        if(obj isKindOfClass:[A class]]){
            //this is of right class
        }
    }
}

您可以在 NSArray 上编写一个 Category 方法,在每个对象上检查它。

BOOL allAreKindOfA = [array allObjectsAreKindOfClass:[A class]];

通常你实际上并不经常需要这个,因为你知道你在集合中放了什么。

如果您需要检查数组中对象的类型或能力,这可能表明您的架构已损坏


另一个选项可能是NSMutableArray 的子类,它只接受某些类。但请注意NSMutableArrayNSArray 的子类化注释,因为它们是类簇,因此不容易子类化。

注意:在我的other answer 中,我创建了一个NSMutableArray 子类,它使用块来测试是否满足某个要求。如果您针对班级成员资格进行测试,这将完全符合您的要求。使用第二个块进行错误处理。

【讨论】:

  • 有人做了最后一种方法stackoverflow.com/questions/5197446/… — 令人印象深刻
  • 一篇关于子类集群的博文cocoawithlove.com/2008/12/…
  • 一般情况下,使用isKindOfClass: 表示架构已损坏...
  • @bbum 是的,这就是我所说的“......不经常需要这个,你知道,你放在集合中的东西。”我会更加强调。
  • 来自 C# 等强类型语言,很难理解没有强类型集合背后的想法?我仍在阅读和学习,还有很长的路要走,但是可以锁定到协议或特定实现的集合肯定会为应用程序提供巨大的类型安全性吗?我在这里错过了什么?
【解决方案3】:

在 Xcode 7 的发布中,Apple 增加了对 Objective-C 泛型的支持。

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

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

【讨论】:

  • NSArray &lt;NSString *&gt; *aarray = @[@"aa", @"bb", @1]; 在 Xcode 7 beta 中对我来说没有产生任何错误。
【解决方案4】:

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

在 NSMutableArray 上创建一个子类,它需要两个块:

  • AddBlock — 一个测试块,如果一个或多个需求是完整的,并且只添加对象,如果它通过测试
  • FailBlock — 一个块,用于定义测试不成功时会发生什么。

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

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

并且 FailBlock 可以引发异常、静默失败或将未通过测试的元素添加到另一个数组。如果没有提供failBlock,默认的block会报错。

这些块将定义,如果一个数组作为一个通用数组,或者作为一个过滤器。
对于第二种情况,我会给出一个完整的例子。

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

注意:此代码未经过全面测试。可能应该实现一些未实现的方法以在实际程序中使用。

【讨论】:

    【解决方案5】:

    不直接,不。有几种方法可以模拟它,但它需要大量的包装代码、样板代码和运行时开销。当我想要或需要适当的泛型时,我只是切换到 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&lt;Card&gt; 作为 t_typed_NSArray&lt;NSURL&gt; 传递。

    【讨论】:

      【解决方案6】:

      有一种简单有效的方法可以做到这一点(我已经在项目中使用它几年了)。可悲的是,有人删除了答案,我试图恢复它的尝试被拒绝了。又来了:

      您可以在 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 中都可以正常工作,带有自动完成等功能。

      【讨论】:

        猜你喜欢
        • 2012-05-18
        • 2016-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多