objective-c 有没有办法强制在 NSArray、NSMutableArray 等上打字?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/649483/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 21:16:05  来源:igfitidea点击:

Is there any way to enforce typing on NSArray, NSMutableArray, etc.?

objective-cgenericsdata-structurescollectionsstrong-typing

提问by Sam Lee

Can I make a NSMutableArrayinstance where allthe elements are of type SomeClass?

我可以创建一个所有元素都是类型的NSMutableArray实例吗?SomeClass

采纳答案by Chuck

You could make a category with an -addSomeClass:method to allow compile-time static type checking (so the compiler could let you know if you try to add an object it knows is a different class through that method), but there's no real way to enforce that an array only contains objects of a given class.

您可以使用-addSomeClass:允许编译时静态类型检查的方法创建一个类别(因此编译器可以让您知道如果您尝试通过该方法添加一个它知道是不同类的对象),但没有真正的方法来强制执行数组只包含给定类的对象。

In general, there doesn't seem to be a need for such a constraint in Objective-C. I don't think I've ever heard an experienced Cocoa programmer wish for that feature. The only people who seem to are programmers from other languages who are still thinking in those languages. If you only want objects of a given class in an array, only stick objects of that class in there. If you want to test that your code is behaving properly, test it.

一般来说,Objective-C 中似乎不需要这样的约束。我想我从未听说过有经验的 Cocoa 程序员希望使用该功能。唯一似乎是来自其他语言的程序员仍在用这些语言思考的人。如果您只想要数组中给定类的对象,则只在其中粘贴该类的对象。如果您想测试您的代码是否正常运行,请对其进行测试。

回答by Logan

Nobody's put this up here yet, so I'll do it!

还没有人把这个放在这里,所以我会做的!

Tthis is now officially supported in Objective-C. As of Xcode 7, you can use the following syntax:

现在,Objective-C 正式支持此功能。从 Xcode 7 开始,您可以使用以下语法:

NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];

Note

笔记

It's important to note that these are compiler warnings only and you can technically still insert any object into your array. There are scripts available that turn all warnings into errors which would prevent building.

重要的是要注意,这些只是编译器警告,从技术上讲,您仍然可以将任何对象插入到您的数组中。有可用的脚本将所有警告转换为错误,从而阻止构建。

回答by Barry Wark

This is a relatively common question for people transitioning from strongly type languages (like C++ or Java) to more weakly or dynamically typed languages like Python, Ruby, or Objective-C. In Objective-C, most objects inherit from NSObject(type id) (the rest inherit from an other root class such as NSProxyand can also be type id), and any message can be sent to any object. Of course, sending a message to an instance that it does not recognize may cause a runtime error (and will also cause a compiler warningwith appropriate -W flags). As long as an instance responds to the message you send, you may not care what class it belongs to. This is often referred to as "duck typing" because "if it quacks like a duck [i.e. responds to a selector], it is a duck [i.e. it can handle the message; who cares what class it is]".

对于从强类型语言(如 C++ 或 Java)过渡到弱类型或动态类型语言(如 Python、Ruby 或 Objective-C)的人们来说,这是一个相对常见的问题。在 Objective-C 中,大多数对象都继承自NSObject(type id)(其余的对象继承自其他根类,例如NSProxy并且也可以是 type id),并且任何消息都可以发送到任何对象。当然,向它不识别的实例发送消息可能会导致运行时错误(并且还会导致编译器警告带有适当的 -W 标志)。只要一个实例响应你发送的消息,你可能并不关心它属于哪个类。这通常被称为“鸭子输入”,因为“如果它像鸭子一样嘎嘎叫[即响应选择器],它就是鸭子[即它可以处理消息;谁在乎它是什么类]”。

You can test whether an instance responds to a selector at run time with the -(BOOL)respondsToSelector:(SEL)selectormethod. Assuming you want to call a method on every instance in an array but aren't sure that all instances can handle the message (so you can't just use NSArray's -[NSArray makeObjectsPerformSelector:], something like this would work:

您可以使用该-(BOOL)respondsToSelector:(SEL)selector方法测试实例是否在运行时响应选择器。假设您想在数组中的每个实例上调用一个方法,但不确定所有实例都可以处理消息(因此您不能只使用NSArray's -[NSArray makeObjectsPerformSelector:],这样的事情会起作用:

for(id o in myArray) {
  if([o respondsToSelector:@selector(myMethod)]) {
    [o myMethod];
  }
}

If you control the source code for the instances which implement the method(s) you wish to call, the more common approach would be to define a @protocolthat contains those methods and declare that the classes in question implement that protocol in their declaration. In this usage, a @protocolis analogous to a Java Interface or a C++ abstract base class. You can then test for conformance to the entire protocol rather than response to each method. In the previous example, it wouldn't make much of a difference, but if you were calling multiple methods, it might simplify things. The example would then be:

如果您控制实现您希望调用的方法的实例的源代码,更常见的方法是定义@protocol包含这些方法的 a 并声明相关类在其声明中实现该协议。在这种用法中,a@protocol类似于 Java 接口或 C++ 抽象基类。然后,您可以测试是否符合整个协议,而不是对每种方法做出响应。在前面的例子中,它不会有太大的不同,但如果你调用多个方法,它可能会简化事情。这个例子将是:

for(id o in myArray) {
  if([o conformsToProtocol:@protocol(MyProtocol)]) {
    [o myMethod];
  }
}

assuming MyProtocoldeclares myMethod. This second approach is favored because it clarifies the intent of the code more than the first.

假设MyProtocol声明myMethod. 第二种方法受到青睐,因为它比第一种方法更能阐明代码的意图。

Often, one of these approaches frees you from caring whether all objects in an array are of a given type. If you still do care, the standard dynamic language approach is to unit test, unit test, unit test. Because a regression in this requirement will produce a (likely unrecoverable) runtime (not compile time) error, you need to have test coverage to verify the behavior so that you don't release a crasher into the wild. In this case, peform an operation that modifies the array, then verify that all instances in the array belong to a given class. With proper test coverage, you don't even need the added runtime overhead of verifying instance identity. You do have good unit test coverage, don't you?

通常,这些方法之一使您无需关心数组中的所有对象是否都属于给定类型。如果您仍然关心,标准的动态语言方法是单元测试、单元测试、单元测试。由于此要求中的回归将产生(可能无法恢复的)运行时(而非编译时)错误,因此您需要进行测试覆盖以验证行为,以免将崩溃者释放到野外。在这种情况下,执行修改数组的操作,然后验证数组中的所有实例都属于给定类。有了适当的测试覆盖率,您甚至不需要验证实例身份的额外运行时开销。你确实有很好的单元测试覆盖率,不是吗?

回答by bendytree

You could subclass NSMutableArrayto enforce type safety.

您可以子类化NSMutableArray以强制执行类型安全。

NSMutableArrayis a class cluster, so subclassing isn't trivial. I ended up inheriting from NSArrayand forwarded invocations to an array inside that class. The result is a class called ConcreteMutableArraywhich iseasy to subclass. Here's what I came up with:

NSMutableArray是一个类 cluster,所以子类化不是微不足道的。我最终继承NSArray并将调用转发到该类中的一个数组。结果是一个名为的类ConcreteMutableArray,它容易子类化。这是我想出的:

Update:checkout this blog post from Mike Ashon subclassing a class cluster.

更新:查看Mike Ash 的这篇关于对类集群进行子类化的博客文章

Include those files in your project, then generate any types you wish by using macros:

将这些文件包含在您的项目中,然后使用宏生成您想要的任何类型:

MyArrayTypes.h

MyArrayTypes.h

CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)

MyArrayTypes.m

MyArrayTypes.m

CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)

Usage:

用法:

NSStringArray* strings = [NSStringArray array];
[strings add:@"Hello"];
NSString* str = [strings get:0];

[strings add:[User new]];  //compiler error
User* user = [strings get:0];  //compiler error

Other Thoughts

其他想法

  • It inherits from NSArrayto support serialization/deserialization
  • Depending on your taste, you may want to override/hide generic methods like

    - (void) addObject:(id)anObject

  • 它继承自NSArray以支持序列化/反序列化
  • 根据您的口味,您可能希望覆盖/隐藏通用方法,例如

    - (void) addObject:(id)anObject

回答by Barry Wark

Have a look at https://github.com/tomersh/Objective-C-Generics, a compile-time (preprocessor-implemented) generics implementation for Objective-C. Thisblog post has a nice overview. Basically you get compile-time checking (warnings or errors), but no runtime penalty for generics.

看看https://github.com/tomersh/Objective-C-Generics,Objective-C的编译时(预处理器实现)泛型实现。这篇博文有一个很好的概述。基本上你会得到编译时检查(警告或错误),但不会对泛型进行运行时惩罚。

回答by IluTov

This Github Projectimplements exactly that functionality.

这个 Github 项目正是实现了这个功能。

You can then use the <>brackets, just like you would in C#.

然后您可以使用<>方括号,就像在 C# 中一样。

From their examples:

从他们的例子:

NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed

回答by colin

If you mix c++ and objective-c (i.e. using mm file type), you can enforce typing using pair or tuple. For example, in the following method, you can create a C++ object of type std::pair, convert it to an object of OC wrapper type (wrapper of std::pair that you need to define), and then pass it to some other OC method, within which you need to convert the OC object back to C++ object in order to use it. The OC method only accepts the OC wrapper type, thus ensuring type safety. You can even use tuple, variadic template, typelist to leverage more advanced C++ features to facilitate type safety.

如果您混合使用 c++ 和 Objective-c(即使用 mm 文件类型),您可以使用 pair 或 tuple 强制输入。例如,在下面的方法中,你可以创建一个std::pair类型的C++对象,将其转换为OC包装类型的对象(你需要定义的std::pair的wrapper),然后将其传递给一些其他 OC 方法,在该方法中,您需要将 OC 对象转换回 C++ 对象才能使用它。OC 方法只接受 OC 包装器类型,从而确保类型安全。您甚至可以使用元组、可变参数模板、类型列表来利用更高级的 C++ 功能来促进类型安全。

- (void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath
{
 std::pair<UITableView*, NSIndexPath*> tableRow(tableView, indexPath);  
 ObjCTableRowWrapper* oCTableRow = [[[ObjCTableRowWrapper alloc] initWithTableRow:tableRow] autorelease];
 [self performSelector:@selector(selectRow:) withObject:oCTableRow];
}

回答by ingconti

my two cents to be a bit "cleaner":

我的两美分要“更干净”一点:

use typedefs:

使用类型定义:

typedef NSArray<NSString *> StringArray;

in code we can do:

在代码中我们可以这样做:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];

回答by vikingosegundo

I created a NSArray subclass that is using an NSArray object as backing ivar to avoid issues with the class-cluster nature of NSArray. It takes blocks to accept or decline adding of an object.

我创建了一个 NSArray 子类,它使用 NSArray 对象作为支持 ivar 来避免 NSArray 的类集群性质的问题。接受或拒绝添加对象需要块。

to only allow NSString objects, you can define an AddBlockas

只允许NSString对象,你可以定义AddBlock

^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
}

You can define a FailBlockto decide what to do, if an element failed the test — fail gracefully for filtering, add it to another array, or — this is default — raise an exception.

您可以定义 aFailBlock来决定要做什么,如果元素未通过测试 - 优雅地失败过滤,将其添加到另一个数组,或者 - 这是默认设置 - 引发异常。

VSBlockTestedObjectArray.h

VSBlockTestedObjectArray.h

#import <Foundation/Foundation.h>
typedef BOOL(^AddBlock)(id element); 
typedef void(^FailBlock)(id element); 

@interface VSBlockTestedObjectArray : NSMutableArray

@property (nonatomic, copy, readonly) AddBlock testBlock;
@property (nonatomic, copy, readonly) FailBlock failBlock;

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock;    
@end

VSBlockTestedObjectArray.m

VSBlockTestedObjectArray.m

#import "VSBlockTestedObjectArray.h"

@interface VSBlockTestedObjectArray ()
@property (nonatomic, retain) NSMutableArray *realArray;
-(void)errorWhileInitializing:(SEL)selector;
@end

@implementation VSBlockTestedObjectArray
@synthesize testBlock = _testBlock;
@synthesize failBlock = _failBlock;
@synthesize realArray = _realArray;


-(id)initWithCapacity:(NSUInteger)capacity
{
    if (self = [super init]) {
        _realArray = [[NSMutableArray alloc] initWithCapacity:capacity];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock 
             FailBlock:(FailBlock)failBlock 
              Capacity:(NSUInteger)capacity
{
    self = [self initWithCapacity:capacity];
    if (self) {
        _testBlock = [testBlock copy];
        _failBlock = [failBlock copy];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock
{
    return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0];
}

-(id)initWithTestBlock:(AddBlock)testBlock
{
    return [self initWithTestBlock:testBlock FailBlock:^(id element) {
        [NSException raise:@"NotSupportedElement" format:@"%@ faild the test and can't be add to this VSBlockTestedObjectArray", element];
    } Capacity:0];
}


- (void)dealloc {
    [_failBlock release];
    [_testBlock release];
    self.realArray = nil;
    [super dealloc];
}


- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if(self.testBlock(anObject))
        [self.realArray insertObject:anObject atIndex:index];
    else
        self.failBlock(anObject);
}

- (void) removeObjectAtIndex:(NSUInteger)index
{
    [self.realArray removeObjectAtIndex:index];
}

-(NSUInteger)count
{
    return [self.realArray count];
}

- (id) objectAtIndex:(NSUInteger)index
{
    return [self.realArray objectAtIndex:index];
}



-(void)errorWhileInitializing:(SEL)selector
{
    [NSException raise:@"NotSupportedInstantiation" format:@"not supported %@", NSStringFromSelector(selector)];
}
- (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;}

@end

Use it like:

像这样使用它:

VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSString", element);
}];


VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSNumber class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSNumber", element);
}];


[stringArray addObject:@"test"];
[stringArray addObject:@"test1"];
[stringArray addObject:[NSNumber numberWithInt:9]];
[stringArray addObject:@"test2"];
[stringArray addObject:@"test3"];


[numberArray addObject:@"test"];
[numberArray addObject:@"test1"];
[numberArray addObject:[NSNumber numberWithInt:9]];
[numberArray addObject:@"test2"];
[numberArray addObject:@"test3"];


NSLog(@"%@", stringArray);
NSLog(@"%@", numberArray);

This is just an example code and was never used in real world application. to do so it probably needs mor NSArray method implemented.

这只是一个示例代码,从未在实际应用中使用过。为此,它可能需要实现 mor NSArray 方法。

回答by mouviciel

A possible way could be subclassing NSArray but Apple recommends not to do it. It is simpler to think twice of the actual need for a typed NSArray.

一种可能的方法是继承 NSArray,但 Apple 建议不要这样做。对类型化 NSArray 的实际需求三思而后行会更简单。