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

143

我是 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
这基本上是 https://dev59.com/mHRB5IYBdhLWcg3wXWG2 的一个完全复制,有没有办法在 NSArray、NSMutableArray 等上强制执行类型? - Barry Wark
@Mark W - “不应该这样做”为什么?我已经使用了ObjC++,效果非常好。我可以使用 #import<map> 和 @property std::map<int, NSString*> myDict; 我可以使用完整的Cocoa API并拥有强类型集合,我看不出任何缺点。 - John Henckel
苹果刚刚在Xcode 7中引入了泛型。 - Pedro Mancheno
11个回答

215
在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交互

由于我对泛型和方法返回的类型有疑问,因此我在不同的线程中提出了我的问题,以保持一切清晰:https://dev59.com/S4vda4cB1Zd3GeqPgPSD - lvp
2
@rizzes。是的,它刚刚被介绍了。 - Connor
这里有一个需要注意的地方,Swift 并不完全忽略你泛型 ObjC 类中的类型注释。如果你指定了约束条件,例如 MyClass <Foo: id<Bar>>,那么你的 Swift 代码将假定值是你约束的类型,这为你提供了一些可用的东西。然而,MyClass 的专门子类将会忽略它们的专门类型(被视为通用的 MyClass)。请参阅 https://github.com/bgerstle/LightweightGenericsExample。 - Brian Gerstle
这个能否编译在10.10、10.9以及之前的操作系统上? - p0lAris
只要您将部署目标设置为支持它们,就应该可以。 - Connor
Swift 3 应该导入轻量级泛型:https://github.com/apple/swift-evolution/blob/master/proposals/0057-importing-objc-generics.md - Jeff Kelley

91

此回答已过时,但仍保留历史价值。从Xcode 7开始,Connor在2015年6月8日的回答更准确。


不,除非您想在自己的自定义集合类中使用C++模板(我强烈不建议这样做),Objective-C中没有泛型。

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

泛型在Java和C#等语言中是必需的,因为它们是强类型、静态类型的语言。这与Objective-C的动态类型特性大相径庭。


88
我不同意“不要担心,只需向那些对象发送消息”的说法。如果将错误类型的对象放入集合中,这些对象将无法响应这些消息,并导致运行时错误。其他语言中使用泛型可以通过编译时检查避免这个问题。 - henning77
8
是的,但Objective-C是比这些语言更具动态性的语言。如果您想要强类型安全,请使用那些语言。 - khatchad
36
我不同意“不要担心”的哲学。例如,如果你从 NSArray 中取出第一个项目并将其强制转换为 NSNumber,但该项目实际上是一个 NSString,那么你就会陷入困境... - jjxtra
13
如果你想写一个iOS应用程序,那么选择就不多了。如果使用Java编写一个具有本地应用程序所有优势的简单应用程序,相信我:我会这样做的。 - ericsoco
11
我对这个最大的抱怨并不是关于动态语言和编译时检查,而是简单的开发者沟通问题。如果没有文档说明,我不能仅通过查看属性声明就知道它将返回哪种类型的对象。 - devios1
显示剩余7条评论

11

不需要,但是为了更清晰明确,你可以在注释中加上你想要存储的对象类型。我曾经看到过在需要编写Java 1.4代码时使用这种方法的情况,例如:

NSMutableArray* /*<TypeA>*/ arrayName = ....
或者
NSDictionary* /*<TypeA, TypeB>*/ dictionaryName = ...

我想这是一个很好的记录方式,以防其他人阅读你的代码。无论如何,变量的名称应尽可能清晰,以便知道它包含的对象。 - htafoya

7

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

请注意,在Objective C代码中,这只是一个编译时检查;仅仅因为将错误的类型放入集合或分配给已定义类型的属性,不会导致运行时错误。

声明:

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

使用:

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

小心那些*


6

在XCode 7中,苹果为ObjC添加了泛型:

@property NSArray<NSDate *>* dates;
- (NSArray<NSDate *> *)datesBeforeDate:(NSDate *)date;
- (void)addDatesParsedFromTimestamps:(NSArray<NSString *> *)timestamps;

请看这里:

https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6-ID61

。该网页介绍了如何在Swift中使用Cocoa数据类型,包括字符串、数字、日期和集合等。它还提供了一些示例代码和注意事项,帮助您更好地理解这些概念。

6

Objective-C 没有泛型。

官方文档

数组是对象的有序集合。Cocoa 提供了多个数组类,包括 NSArray、NSMutableArray(NSArrary 的子类)和 NSPointerArray。


把以下與程式設計相關的內容從英文翻譯成中文。僅返回翻譯後的文字: 鏈接到文檔的回答已失效 - "抱歉,找不到該頁面"。 - Pang

4

通过子类化 NSArray 并重新定义所有提供的方法以更加严格的方式,可以实现通用的 NSArrays。例如,

- (id)objectAtIndex:(NSUInteger)index

必须重新定义

@interface NSStringArray : NSArray

作为

- (NSString *)objectAtIndex:(NSUInteger)index

一个 NSArray 只包含 NSStrings。

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

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

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

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

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

一旦您创建了NumberArray,您就可以在项目的任何地方使用它。它缺少<int>的语法,但您可以选择自己的命名方案将其标记为类模板。

请注意,相同的内容也存在于CoreLib中:https://github.com/core-code/CoreLib/blob/master/CoreLib/CoreLib.h#L105 - user1259710

2
现在梦想成真了-自WWDC以来,Objective-C中有了泛型(感谢WWDC)。 这不是一个玩笑-在Swift的官方页面上:
“新的语法特性让您编写更具表现力的代码,同时提高了整个语言的一致性。SDK已经采用了新的Objective-C功能,例如泛型和空指针注释,以使Swift代码变得更加清晰和安全。以下是仅列出的Swift 2.0增强功能。”
下面的图片证明了这一点:Objective-C generics

2

1

我想插入一句话。我在这里写了一篇关于泛型的博客文章。

我想贡献的是泛型可以添加到任何类中,而不仅仅是像苹果所说的集合类。

我已经成功地将它们添加到各种类中,因为它们与苹果的集合类完全相同。即编译时检查、代码完成、使转换操作失效等。

享受吧。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接