iOS __kindof NSArray?

Posted

技术标签:

【中文标题】iOS __kindof NSArray?【英文标题】:iOS __kindof NSArray? 【发布时间】:2015-07-14 06:26:41 【问题描述】:

作为开发人员,我一直在查看 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

【问题讨论】:

您尝试添加错误的类型? 这就是美丽!在此之前可以正常工作,但现在使用 __kindof 它显示我添加了错误的类型。但是我找不到任何文档,所以我不知道发生了什么 不;那是泛型的使用,而不是__kindof 在 Xcode 7 中,Objective-C 支持泛型,允许您为 NSArray、NSSet 和 NSDictionary 等集合类指定类型信息。 developer.apple.com/library/prerelease/ios/documentation/… @***foe 我明白了!在我当前的代码库中,我非常喜欢这个添加 【参考方案1】:

遗憾的是,当前投票最多的答案有点不正确。

您实际上可以&lt;T&gt; 的任何子类添加到通用集合中:

@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!

但是当您尝试检索对象时,它将被严格输入为 &lt;T&gt; 并且需要强制转换:

//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 中的泛型可以接受&lt;T&gt; 的子类,__kindof 关键字指定返回值也可以是&lt;T&gt; 的子类。

【讨论】:

是的,就是这样,它避免了强制转换。 “遗憾的是,当前投票最多的答案有点不正确” 嘿,这是现在投票最多的答案。 那么现在票数最高的答案有点不正确,说明它本身是不正确的:)【参考方案2】:

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

是的,通过 Objective-C 的新轻量级泛型。在您提供的示例中,您有一个 NSArray 类型的属性,将接受 UIViews 的元素。

现在,可以按如下方式指定(不带__kindof)。

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

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

编辑:

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

Objective-C 的泛型似乎可以在访问泛型集合的元素时为您提供类型信息。例如,考虑以下代码,它将UIViewUIImageView 添加到NSMutableArray&lt;UIView *&gt;。这两个对象都被插入到数组中,编译器或运行时没有任何抱怨,但是当您尝试访问元素时,如果您的变量的类型不是数组的泛型类型,编译器会警告您( 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 的同一示例之间的主要区别在于,如果您尝试访问其元素,编译器不会抱怨,并将结果存储在类型为@ 的变量中987654343@ 它的子类之一。

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

【讨论】:

哦该死的,这非常好。这在 WWDC 视频中的某处提到过吗? @ScareCrow 我相信它出现在一些视频中,但在Swift and Objective-C Interoperability 谈话 (link to transcript) 中进行了最详细的讨论。 不正确,可以将UIView的子类添加到NSArray&lt;UIView *&gt;。请参阅下面的答案。 @ArtemAbramov 感谢您指出这一点!我已对我的回答进行了必要的更正。 本视频讨论了轻量级泛型:developer.apple.com/videos/play/wwdc2015-401 视频此时讨论了__kindof:developer.apple.com/videos/play/wwdc2015-401/?time=1752【参考方案3】:

iOS9 在 ObjC 上引入了轻量级泛型。 ObjC 是一种真正动态的语言,而 SWIFT 更喜欢静态类型,为了最大限度地提高 ObjC 中的互操作性和类型检查,您可以像这样声明一个数组:

NSArray<UIView *> *views;

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

【讨论】:

+1 我不知道使用__kindof 来允许子类。很遗憾,Apple 推出了一项功能,该功能与当今所有其他语言中的泛型略有不同。【参考方案4】:

@Artem Abramov 是正确的。我想指出一点,正如Objective-C Generics中提到的那样

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

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

这是 Xcode 7 中引入的 Clang 的一个功能。我认为它与 iOS 版本无关

【讨论】:

【参考方案5】:

如果您有一个返回__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 本身的子类的变量。

重要提示:即使没有__kindof,您也可以将UIView 子类存储在NSArray&lt;UIView *&gt; 中。 __kindof 只是关于访问数组的元素。

【讨论】:

以上是关于iOS __kindof NSArray?的主要内容,如果未能解决你的问题,请参考以下文章

ios开发ios9新特性关键字学习:泛型,逆变,协变,__kindof

使用“__kindof NSManagedObject * _Nonnull”类型的表达式初始化“NSEntityDescription *”的不兼容指针类型

IOS 中runtime 不可变数组__NSArray0 和__NSArrayI

iOS之NSArray类簇简介-(copymutableCopy导致程序crash)

关于 Reactivecocoa dyld:找不到符号:___NSArray0__

iOS 数组的实现原理