iOS __kindof NSArray?

时间:2015-07-14 06:26:42

标签: ios objective-c

我作为开发人员一直在检查iOS 9的新功能,其中一些像StackView看起来很棒。

当我去UIStackView的头文件时,我看到了这个:

@property(nonatomic,readonly,copy) NSArray<__kindof UIView *> *arrangedSubviews;

NSArray *上的__kindof是什么?我们现在能够在NSArray *上指定类型吗?

小测试:

@interface DXTEST ()
@property (strong, nonatomic) NSMutableArray <__kindof NSString *>  *strings;
@end

@implementation DXTEST

- ( instancetype ) init {
    self = [super init];

    if( self ) {
        _strings = [NSMutableArray new];

        [_strings addObject:@(1)]; <-- compiler warning wieeeeee
    }

    return self;
}

@end

5 个答案:

答案 0 :(得分:60)

可悲的是,目前最高投票的答案有点不正确。

您实际上可以<T>的任何子类添加到通用集合中:

@interface CustomView : UIView @end
@implementation CustomView @end

NSMutableArray<UIView *> *views;
UIView *view = [UIView new];
CustomView *customView = [CustomView new];
[views addObject:view];
[views addObject:customView];//compiles and runs!

但是当您尝试检索对象时,它将被严格键入<T>并需要转换:

//Warning: incompatible pointer types initializing 
//`CustomView *` with an expression of type `UIView * _Nullable`
CustomView *retrivedView = views.firstObject;

但是,如果您要添加__kindof关键字,则返回的类型将更改为kindof T,并且不需要转换:

NSMutableArray<__kindof UIView *> *views;
<...>
CustomView *retrivedView = views.firstObject;

TLDR :Objective-C 中的泛型可以接受<T>的子类,__kindof关键字指定返回值也可以是{的子类{1}}。

答案 1 :(得分:41)

  

我们现在可以在NSArray *上指定类型吗?

是的,通过Objective-C的新轻量级仿制药。在您提供的示例中,您有一个类型为NSArray的属性,它将接受UIView s的元素。

现在,这可以指定如下(没有__kindof)。

@property(nonatomic,readonly,copy) NSArray<UIView *> *arrangedSubviews;

在这种情况下,数组将接受类为UIView的对象,但不接受任何作为UIView子类的对象。 __kindof声明将数组的泛型类型标记为可以接受UIView类的实例和任何UIView子类的实例的类型。< /击>

编辑:

我已经删除了原始答案的大部分,因为我错误地认为指定数组的类型会阻止您插入不正确类型的对象,而事实并非如此。 (感谢Artem Abramov指出这一点。请在下方see his answer了解更多详情)

Objective-C的泛型似乎存在,在访问泛型集合的元素时为您提供类型信息。例如,请考虑以下代码,将UIViewUIImageView添加到NSMutableArray<UIView *>。两个对象都插入到数组中而没有编译器或运行时的任何抱怨,但是当您尝试访问元素时,如果变量的类型不是通用类型,则编译器会警告您。数组(UIView),即使它是UIView的子类之一。

NSMutableArray<UIView *> *subviews = [[NSMutableArray alloc] init];

[subviews addObject:[[UIView alloc] init]]; // Works
[subviews addObject:[[UIImageView alloc] init]]; // Also works

UIView *sameView = subviews[0]; // Works
UIImageView *sameImageView = subviews[1]; // Incompatible pointer types initializing 'UIImageView *' with an expression of type 'UIView *'

NSLog(@"%@", NSStringFromClass([sameView class])); // UIView
NSLog(@"%@", NSStringFromClass([sameImageView class])); // UIImageView

现在,这会产生编译时警告,但不会在运行时崩溃。这个和数组的泛型类型被标记为__kindof的相同示例之间的关键区别在于,如果您尝试访问其元素,并且存储,则编译器不会抱怨导致变量的类型为UIView 其子类之一。

NSMutableArray<__kindof UIView *> *subviews = [[NSMutableArray alloc] init];

[subviews addObject:[[UIView alloc] init]]; // Works
[subviews addObject:[[UIImageView alloc] init]]; // Also works

UIView *sameView = subviews[0]; // No problem
UIImageView *sameImageView = subviews[1]; // No complaints now!

NSLog(@"%@", NSStringFromClass([sameView class])); // UIView
NSLog(@"%@", NSStringFromClass([sameImageView class])); // UIImageView

答案 2 :(得分:7)

iOS9在ObjC上引入了轻量级泛型。 ObjC是一种非常动态的语言,而SWIFT更喜欢静态类型,为了最大化互操作性和类型检查现在在ObjC中你可以声明一个这样的数组:

NSArray<UIView *> *views;

这意味着所有视图对象都是UIView对象的实例,想象UIView子视图属性它可以包含可以是不同类型的元素,UIView和继承自{{1}的对象但是编译器会抱怨,因为即使它们继承了它们也是不同的类型。
那就是UIView开始发挥作用。就像是说数组包含类型为__kindof的对象 就像仍然使用UIView类型的优势但仅限于某种类。

答案 3 :(得分:3)

@Artem Abramov是对的。我想指出另外一点,如Objective-C Generics

中所述
  

__kindof说这个东西必须是X或更多派生的类。为什么这有必要?文档并没有把它说出来,但我怀疑是因为   将泛型添加到Objective-C引入了一个巨大的难题:如果   UIView.subviews现在是NSArray<UIView *>,然后是很多代码   现在根据编译器的类型检查器无效:   [view.subviews[0] setImage:nil]是假的,因为UIView没有   图像属性。我们被Objective-C这个事实宠坏了   编译器将允许您将任何可见的消息选择器发送到id ... only   现在subviews数组不返回id,它返回UIView *。糟糕。

     

__kindof通过说subviews数组不是显式的UIView *数组而是UIView *子类的数组来解决这个问题。   然后编译器将允许您发送任何可以应用的选择器   UIView *或其所有子类,或从该数组中分配一个元素   到UIImageView * ,但如果您尝试这样做,它仍然会抱怨   这个:NSNumber *n = view.subviews[0]

这是在Xcode 7中引入的Clang的一个功能。我不认为它与iOS版本有任何关系

答案 4 :(得分:2)

如果您有一个返回__kindof SomeType *的函数,那么您可以将该函数的结果分配给SubtypeOfSomeType *类型的变量,而无需显式强制转换。没有__kindof编译器会抱怨不兼容的类型。

实际上在UIKit中至少有一个这样的函数:https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/index.html#//apple_ref/occ/instm/UITableView/dequeueReusableCellWithIdentifier

现在让我们将这些知识扩展到NSArray。在泛型类型参数中指定__kindof会将__kindof添加到返回类型-objectAtIndex:-objectAtIndexedSubscript:(实际上,返回ObjectType的通用NSArray的任何方法,现在您可以轻松地将调用stackView.arrangedSubviews[i]的结果分配给任何类型为UIViewUIView的子类的变量。

重要提示:即使没有UIView,您也可以将NSArray<UIView *>子类存储在__kindof中。 __kindof仅与访问数组元素有关。