NSArray, NSMutableArray 등에서 입력을 강제하는 방법이 있습니까?


답변:


35

-addSomeClass:컴파일시 정적 유형 검사를 허용 하는 메서드로 범주를 만들 수 있습니다 (그러므로 컴파일러는 해당 메서드를 통해 다른 클래스라는 것을 알고있는 객체를 추가하려고하면 알려줄 수 있습니다). 배열에는 주어진 클래스의 객체 만 포함됩니다.

일반적으로 Objective-C에는 이러한 제약이 필요하지 않은 것 같습니다. 경험이 풍부한 Cocoa 프로그래머가 그 기능에 대한 소망을 들어 본 적이 없다고 생각합니다. 다른 언어의 프로그래머로 보이는 유일한 사람들은 여전히 ​​그 언어로 생각하고 있습니다. 주어진 클래스의 객체만을 배열로 원한다면 그 클래스의 객체 만 거기에 고정하십시오. 코드가 제대로 작동하는지 테스트하려면 테스트하십시오.


136
저는 '경험이 풍부한 Cocoa 프로그래머'가 그들이 무엇을 놓치고 있는지 모른다고 생각합니다. Java 경험은 타입 변수가 코드 이해력을 향상시키고 더 많은 리팩토링을 가능하게한다는 것을 보여줍니다.
tgdavies 2010

11
그들은 처음부터에 넣어하지 않았기 때문에 음, 자바의 제네릭 지원은 크게 ..., 그것의 자신의 오른쪽에 깨진
dertoni

28
@tgdavies에 동의해야합니다. C #에서 가졌던 지능 및 리팩토링 기능이 그립습니다. 동적 타이핑을 원할 때 C # 4.0에서 얻을 수 있습니다. 강력한 유형의 물건을 원할 때도 가질 수 있습니다. 나는 그 두 가지를위한 시간과 장소가 있음을 발견했습니다.
Steve

18
@charkrit Objective-C를 '필요하지 않게'만드는 것은 무엇입니까? C #을 사용할 때 필요하다고 느꼈습니까? 많은 사람들이 Objective-C에서 필요하지 않다고 말하는 것을 들었지만, 같은 사람들이 어떤 언어로도 필요하지 않다고 생각하기 때문에 필요가 아닌 선호도 / 스타일 문제가됩니다.
bacar

17
컴파일러가 실제로 문제를 찾는 데 도움을 줄 수 있도록 허용하는 것이 아닙니다. 물론 "배열에 지정된 클래스의 객체 만 원하는 경우 해당 클래스의 객체 만 거기에 고정"이라고 말할 수 있습니다. 그러나 테스트가이를 시행하는 유일한 방법이라면 불이익을 받게됩니다. 코드 작성에서 멀어 질수록 문제는 더 많은 비용이 듭니다.
GreenKiwi

145

아직 아무도 여기에 올려 놓지 않았으니 제가 할게요!

이제 Objective-C에서 공식적으로 지원됩니다. Xcode 7부터 다음 구문을 사용할 수 있습니다.

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

노트

이는 컴파일러 경고 일 뿐이며 기술적으로 모든 객체를 배열에 삽입 할 수 있다는 점에 유의해야합니다. 모든 경고를 빌드를 방해하는 오류로 바꾸는 스크립트가 있습니다.


나는 여기서 게으르지 만 왜 XCode 7에서만 사용할 수 있습니까? 우리는 nonnullXCode 6에서 사용할 수 있으며 내가 기억하는 한 동시에 도입되었습니다. 또한 이러한 개념의 사용법은 XCode 버전 또는 iOS 버전에 따라 달라 집니까?
Guven 2015-07-03

@Guven-null 허용 여부가 6에 왔지만 맞습니다.하지만 ObjC 제네릭은 Xcode 7까지 도입되지 않았습니다.
Logan

나는 그것이 Xcode 버전에만 의존한다고 확신합니다. 제네릭은 컴파일러 경고 일 뿐이며 런타임에 표시되지 않습니다. 나는 당신이 원하는 OS로 컴파일 할 수 있다고 확신합니다.
Logan

2
@DeanKelly-당신은 이렇게 할 수 있습니다 : @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; 약간 투박해 보이지만 속임수를 사용합니다!
Logan

1
@Logan, 경고가 감지 된 경우 빌드를 방지하는 스크립트 세트 만있는 것이 아닙니다. Xcode에는 "구성"이라는 완벽한 메커니즘이 있습니다. 에서이 문제를 확인 boredzo.org/blog/archives/2009-11-07/warnings
adnako

53

이것은 강력한 유형의 언어 (예 : C ++ 또는 Java)에서 Python, Ruby 또는 Objective-C와 같은 더 약하거나 동적으로 유형이 지정된 언어로 전환하는 사람들에게 비교적 일반적인 질문입니다. Objective-C에서 대부분의 객체는 NSObject(type id) 에서 상속되며 ( 나머지는 NSProxy및 같은 다른 루트 클래스에서 상속되며 type id일 수도 있음 ) 모든 메시지를 모든 객체로 보낼 수 있습니다. 물론 인식하지 못하는 인스턴스에 메시지를 보내면 런타임 오류가 발생할 수 있으며 컴파일러 경고가 발생할 수도 있습니다.적절한 -W 플래그 사용). 인스턴스가 보내는 메시지에 응답하는 한 어떤 클래스에 속하는지 신경 쓰지 않을 수 있습니다. 이것은 "오리처럼 꽥꽥 거리는 경우 (예 : 선택자에 응답하는 경우), 오리 (즉, 메시지를 처리 ​​할 수 ​​있고, 어떤 클래스인지 누가 신경 쓰는가)"이기 때문에 "오리 타이핑"이라고도합니다.

-(BOOL)respondsToSelector:(SEL)selector메서드를 사용하여 런타임에 인스턴스가 선택기에 응답하는지 여부를 테스트 할 수 있습니다 . 배열의 모든 인스턴스에서 메서드를 호출하고 싶지만 모든 인스턴스가 메시지를 처리 ​​할 수 ​​있는지 확실하지 않다고 가정합니다. 따라서 NSArray's 만 사용할 수는 없습니다 -[NSArray makeObjectsPerformSelector:]. 다음과 같이 작동합니다.

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

호출하려는 메서드를 구현하는 인스턴스의 소스 코드를 제어하는 ​​경우보다 일반적인 접근 방식은 @protocol해당 메서드를 포함하는를 정의하고 해당 클래스가 선언에서 해당 프로토콜을 구현한다고 선언하는 것입니다. 이 사용법에서 a @protocol는 Java 인터페이스 또는 C ++ 추상 기본 클래스와 유사합니다. 그런 다음 각 방법에 대한 응답이 아닌 전체 프로토콜에 대한 적합성을 테스트 할 수 있습니다. 이전 예제에서는 큰 차이가 없지만 여러 메서드를 호출하는 경우 작업을 단순화 할 수 있습니다. 예는 다음과 같습니다.

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

MyProtocol선언 한다고 가정 합니다 myMethod. 이 두 번째 접근 방식은 첫 번째 방법보다 코드의 의도를 더 명확하게하기 때문에 선호됩니다.

종종 이러한 접근 방식 중 하나를 사용하면 배열의 모든 개체가 지정된 유형인지 여부를 걱정할 필요가 없습니다. 그래도 관심이 있다면 표준 동적 언어 접근 방식은 단위 테스트, 단위 테스트, 단위 테스트입니다. 이 요구 사항의 회귀는 (복구 불가능한) 런타임 (컴파일 시간이 아님) 오류를 생성하기 때문에 크래셔를 야생으로 출시하지 않도록 동작을 확인하기위한 테스트 적용 범위가 있어야합니다. 이 경우 배열을 수정하는 작업을 수행 한 다음 배열의 모든 인스턴스가 지정된 클래스에 속하는지 확인합니다. 적절한 테스트 범위를 사용하면 인스턴스 ID를 확인하는 추가 런타임 오버 헤드도 필요하지 않습니다. 당신은 단위 테스트 커버리지가 훌륭하지 않습니까?


35
단위 테스트는 괜찮은 유형 시스템을 대체하지 않습니다.
tba

8
예, 유형화 된 어레이가 감당할 수있는 도구가 필요한 사람. @BarryWark (및 그가 사용하고 읽고 이해하고 지원해야하는 코드베이스를 만진 다른 사람)은 100 % 코드 커버리지를 가지고 있다고 확신합니다. 그러나 id필요한 경우를 제외하고는 원시 s를 사용하지 않을 것이라고 장담합니다 Object. 왜 안돼? 단위 테스트가 있으면 필요하지 않습니까? 유형이 지정된 배열처럼 코드를 유지 관리하기 쉽게 만들어주기 때문입니다. 사람들이 포인트를 양보하기를 원하지 않고 플랫폼에 투자 한 것처럼 들리므로이 누락이 실제로 혜택이되는 이유를 발명하는 것 같습니다.
funkybro

"오리 타이핑"?? 재밌 네요! 전에는 들어 본 적이 없습니다.
John Henckel 2014

11

NSMutableArray유형 안전성을 강화하기 위해 하위 클래스 를 만들 수 있습니다.

NSMutableArrayA는 클래스 클러스터는 , 그래서 서브 클래스는 사소한 없습니다. 나는 결국 NSArray그 클래스 내부의 배열 에서 상속을 받고 호출을 전달했습니다. 그 결과 하위 클래스 쉽게 호출 ConcreteMutableArray되는 클래스가 생성됩니다. 내가 생각 해낸 것은 다음과 같습니다.

업데이트 : 클래스 클러스터 서브 클래 싱에 대한 Mike Ash 의이 블로그 게시물을 확인하십시오 .

프로젝트에 해당 파일을 포함시킨 다음 매크로를 사용하여 원하는 유형을 생성하십시오.

MyArrayTypes.h

CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)

MyArrayTypes.m

CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)

용법:

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

다른 생각들

  • NSArray직렬화 / 역 직렬화를 지원하기 위해 에서 상 속됨
  • 취향에 따라 다음과 같은 일반적인 방법을 재정의 / 숨기기를 원할 수 있습니다.

    - (void) addObject:(id)anObject


좋지만 현재로서는 일부 메서드를 재정 의하여 강력한 타이핑이 부족합니다. 현재는 약한 타이핑입니다.
Cœur

7

Objective-C에 대한 컴파일 타임 (전 처리기 구현) 제네릭 구현 인 https://github.com/tomersh/Objective-C-Generics를 살펴보십시오 . 블로그 게시물에는 멋진 개요가 있습니다. 기본적으로 컴파일 타임 검사 (경고 또는 오류)를 받지만 제네릭에 대한 런타임 패널티는 없습니다.


1
나는 그것을 시도했지만 아주 좋은 생각이지만 슬프게도 버그가 있고 추가 된 요소를 확인하지 않습니다.
Binarian

4

이 Github 프로젝트 는 정확히 그 기능을 구현합니다.

그런 다음 <> C # 에서처럼 대괄호 .

그들의 예에서 :

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

0

가능한 방법은 NSArray를 서브 클래 싱하는 것이지만 Apple은이를 권장하지 않습니다. 형식화 된 NSArray에 대한 실제 필요성을 두 번 생각하는 것이 더 간단합니다.


1
컴파일 시간에 정적 유형 검사를 수행하는 시간을 절약하고 편집이 훨씬 좋습니다. 장기간 사용하기 위해 lib를 작성할 때 특히 유용합니다.
pinxue

0

NSArray의 클래스 클러스터 특성과 관련된 문제를 피하기 위해 NSArray 개체를 ivar 백업으로 사용하는 NSArray 하위 클래스를 만들었습니다. 개체 추가를 수락하거나 거부하려면 블록이 필요합니다.

NSString 객체 만 허용하려면 다음 AddBlock과 같이 정의 할 수 있습니다.

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

FailBlock요소가 테스트에 실패한 경우 수행 할 작업을 결정하기 위해 a 를 정의 할 수 있습니다. 필터링을 위해 정상적으로 실패하거나 다른 배열에 추가하거나 (기본값) 예외를 발생시킵니다.

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

#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

다음과 같이 사용하십시오.

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);

이것은 단지 예제 코드이며 실제 응용 프로그램에서는 사용되지 않았습니다. 그렇게하려면 아마도 mor NSArray 메서드를 구현해야합니다.


0

C ++와 Objective-c를 혼합하면 (예 : mm 파일 유형 사용) 쌍 또는 튜플을 사용하여 입력을 강제 할 수 있습니다. 예를 들어, 다음 메소드에서 std :: pair 유형의 C ++ 객체를 생성하고이를 OC 래퍼 유형의 객체 (정의해야하는 std :: pair의 래퍼)로 변환 한 다음이를 일부에 전달할 수 있습니다. OC 객체를 사용하려면 다시 C ++ 객체로 변환해야하는 다른 OC 방법. 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];
}

0

내 두 센트가 좀 더 깨끗해 지도록 :

typedef를 사용하십시오.

typedef NSArray<NSString *> StringArray;

코드에서 우리는 할 수 있습니다 :

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.