我作为开发人员一直在检查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
答案 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的泛型似乎存在,在访问泛型集合的元素时为您提供类型信息。例如,请考虑以下代码,将UIView
和UIImageView
添加到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]
的结果分配给任何类型为UIView
或UIView
的子类的变量。
重要提示:即使没有UIView
,您也可以将NSArray<UIView *>
子类存储在__kindof
中。 __kindof
仅与访问数组元素有关。