在Objective-C中是否存在强类型集合?


140

我是Mac / iPhone编程和Objective-C的新手。在C#和Java中,我们有“泛型”集合类,其成员只能是声明的类型。例如,在C#中

Dictionary<int, MyCustomObject>

只能包含整数键和MyCustomObject类型的值。在Objective-C中是否存在类似的机制?


刚开始自己​​了解ObjC。也许您可以使用ObjC ++进行繁重的工作?
Toybuilder

您可能对以下问题的答案感兴趣:是否可以通过任何方法在NSArray,NSMutableArray等上强制键入?。给出了为什么在Objective-C / Cocoa中不常见的说法。
mouviciel

2
ObjC ++并不是一种真正的语言...更多地是一种引用ObjC处理C ++内联与处理C语言相同的能力的方式。但是除非有必要,否则您不应该这样做(例如,如果需要使用使用C ++编写的第三方库)。
Marc W,2009年


@ Mark W-“不应该这样做”为什么不呢?我用过ObjC ++,效果很好。我可以做#import <map>和@property std :: map <int,NSString *> myDict; 我可以使用完整的可可api并具有强类型的集合。我没有看到任何不利的一面。
John Henckel 2014年

Answers:


211

苹果在Xcode 7中向Objective-C引入了“轻量级泛型”。在Objective-C中,如果类型不匹配,它们将生成编译器警告。

NSArray<NSString*>* arr = @[@"str"];

NSString* string = [arr objectAtIndex:0];
NSNumber* number = [arr objectAtIndex:0]; // Warning: Incompatible pointer types initializing 'NSNumber *' with an expression of type 'NSString *'

在Swift代码中,它们将产生编译器错误:

var str: String = arr[0]
var num: Int = arr[0] //Error 'String' is not convertible to 'Int'

轻量级泛型旨在与NSArray,NSDictionary和NSSet一起使用,但是您也可以将它们添加到自己的类中:

@interface GenericsTest<__covariant T> : NSObject

-(void)genericMethod:(T)object;

@end

@implementation GenericsTest

-(void)genericMethod:(id)object {}

@end

使用编译器警告时,Objective-C的行为将像以前一样。

GenericsTest<NSString*>* test = [GenericsTest new];

[test genericMethod:@"string"];
[test genericMethod:@1]; // Warning: Incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'

但是Swift会完全忽略通用信息。(在Swift 3+中不再适用。)

var test = GenericsTest<String>() //Error: Cannot specialize non-generic type 'GenericsTest'

除了这些Foundation集合类之外,Swift还会忽略Objective-C轻量级泛型。任何其他使用轻量级泛型的类型都将被导入到Swift中,就像它们是未参数化的一样。

与Objective-C API交互


因为我有问题,有关方法返回泛型和类型,我问我的问题在不同的线程,把一切都清楚:stackoverflow.com/questions/30828076/...
LVP

2
@rizzes。是的,它刚刚被引入。
康纳

需要注意的是,Swift不会完全忽略通用ObjC类中的类型注释。如果您指定约束,例如MyClass <Foo: id<Bar>>,您的Swift代码将假定值是约束的类型,这使您可以使用一些东西。但是,的特殊子类MyClass将忽略其特殊类型(实际上与泛型相同MyClass)。参见github.com/bgerstle/LightweightGenericsExample
Brian Gerstle

那么,这是否可以针对10.10、10.9和更早版本的操作系统进行编译?
p0lAris

它应该只要您设置的部署目标,以支持他们
康纳

91

这个答案已经过时了,但仍然具有历史价值。从Xcode 7开始,Connor从15年6月8日开始的回答更加准确。


不,除非您要在自己的自定义集合类中使用C ++模板(否则我强烈建议),那么Objective-C中没有泛型。

Objective-C具有动态类型化功能,这意味着运行时不关心对象的类型,因为所有对象都可以接收消息。当您将对象添加到内置集合中时,它们将被视为type对待id。但是不用担心,只需像平常一样向这些对象发送消息即可。它将正常工作(除非集合中的一个或多个对象不响应您发送的消息)

Java和C#等语言都需要泛型,因为它们是强大的静态类型的语言。完全不同于Objective-C的动态打字功能。


88
我不同意“不用担心,只向这些对象发送消息”。如果将错误类型的对象放入不响应这些消息的集合中,则会产生运行时错误。使用其他语言的泛型可避免编译时检查出现此问题。
henning77 2012年

8
@ henning77是的,但是Objective-C是比这些语言更具动态性的语言。如果要强类型安全,请使用这些语言。
拉菲·哈查杜安

36
我也不同意不用担心的哲学-例如,如果您将第一个项目从NSArray中拉出并转换为NSNumber,但该项目实际上是一个NSString,那么您就搞砸了……
jjxtra,2012年

13
@RaffiKhatchadourian-如果您正在编写iOS应用程序,则没有太多选择。如果使用Java编写代码很简单,并获得编写本机应用程序的所有好处,请相信我:我会的。
ericsoco

11
我对此最大的抱怨与动态语言与编译时间检查无关,而是与开发人员的简单交流。我不能只看一个属性声明,不知道它将返回什么类型的对象,除非在某处有文档说明。
devios1 2013年

11

不,但是为了更清楚一点,您可以使用要存储的对象类型对其进行注释,当您现在需要用Java 1.4编写某些东西时,我已经看到过几次这样做。例如:

NSMutableArray* /*<TypeA>*/ arrayName = ....

要么

NSDictionary* /*<TypeA, TypeB>*/ dictionaryName = ...

我想这是记录它的好方法,以防其他人读取您的代码。无论如何,变量的名称应该尽可能清楚,以了解其包含哪些对象。
htafoya



5

这是在Xcode 7中发布的(最终!)

注意,在Objective C代码中,这只是一个编译时检查。仅将错误的类型放入集合或分配给typed属性,就不会有运行时错误。

宣布:

@interface FooClass <T> : NSObject
@property (nonatomic) T prop;
@end

用:

FooClass<NSString *> *foo = [[FooClass alloc] init];
NSArray<FooClass<NSString *> *> *fooAry = [NSArray array];

小心那些*


4

通用NSArray可以通过子类实现NSArray,并使用限制性更强的方法重新定义所有提供的方法。例如,

- (id)objectAtIndex:(NSUInteger)index

将不得不在

@interface NSStringArray : NSArray

- (NSString *)objectAtIndex:(NSUInteger)index

NSArray仅包含NSStrings。

创建的子类可以用作嵌入式替换,并带来许多有用的功能:编译器警告,属性访问,更好的代码创建和Xcode中的-completion。所有这些都是编译时功能,无需重新定义实际的实现-NSArray的方法仍然可以使用。

可以将其自动化并将其简化为仅两个语句,这使其更接近支持泛型的语言。我已经使用WMGenericCollection创建了自动化,其中模板作为C预处理器宏提供。

导入包含宏的头文件后,您可以使用两个语句创建一个通用的NSArray:一个用于接口,一个用于实现。您只需要提供要存储的数据类型和子类的名称即可。WMGenericCollection为和提供了此类模板NSArray,以及它们的可变对等模板。NSDictionaryNSSet

一个示例:List<int>可以通过名为的自定义类实现,该类NumberArray使用以下语句创建:

WMGENERICARRAY_INTERFACE(NSNumber *, // type of the value class
                         // generated class names
                         NumberArray, MutableNumberArray)

创建完之后NumberArray,就可以在项目中的任何地方使用它。它缺少的语法<int>,但是您可以选择自己的命名方案以将它们标记为类作为模板。




2

现在梦想成真了-从今天开始,Objective-C中就有泛型(感谢WWDC)。这不是开玩笑-在Swift的官方页面上:

新的语法功能使您可以编写更具表现力的代码,同时提高整个语言的一致性。这些SDK已采用了新的Objective-C功能,例如泛型和可空性注释,以使Swift代码更加干净和安全。这只是Swift 2.0增强功能的示例。

图像证明了这一点:Objective-C泛型


2

只想跳进这里。我在这里写了一篇关于泛型的博客文章。

我想贡献的是泛型可以添加到任何类中,而不仅仅是Apple指出的集合类。

我已经成功地将它们添加到各种类中,因为它们的工作方式与Apple的集合完全相同。即。编译时间检查,代码完成,删除强制转换等。

请享用。


-2

Apple和GNUStep框架提供的Collections类是半通用的,因为它们假定给了它们对象,有些是可排序的,有些是响应某些消息的。对于浮点数,整数等原语,所有C数组结构均完整无缺,可以使用,并且有特殊的包装对象可用于常规集合类(例如NSNumber)。此外,Collection类可以被子类化(或通过类别进行专门修改)以接受任何类型的对象,但是您必须自己编写所有类型处理代码。消息可以发送到任何对象,但如果不适合该对象,则应返回null,或者应将消息转发到适当的对象。True类型错误应在编译时而不是运行时捕获。在运行时,应该对其进行处理或忽略。最后,Objc提供了运行时反射工具来处理棘手的情况,并且可以在对象发送消息或放入不适当的集合之前检查对象的消息响应,特定类型和服务。当心不同的库和框架在它们发送的消息没有代码响应的情况下对对象的行为采用不同的约定,因此使用RTFM。除玩具程序和调试版本外,大多数程序都不必崩溃,除非它们确实搞砸了,并试图将不良数据写入内存或磁盘,执行非法操作(例如,除以零,但您也可以捕获)或访问限制系统资源。Objective-C的动态性和运行时允许事情正常地失败,因此应该将其内置到您的代码中。(提示)如果您在函数通用性方面遇到麻烦,尝试一些特异性。用特定类型编写函数,然后让运行时在运行时选择适当的成员函数(这就是为什么将它们称为选择器!)。

Example:
    -(id) sort (id) obj;  // too generic. catches all.
     // better
    -(id) sort: (EasilySortableCollection*) esc;
    -(id) sort: (HardToSortCollection*) hsc; 
    ...
    [Sorter  sort: MyEasyColl];
    [Sorter  sort: MyHardColl];
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.