답변:
-addSomeClass:
컴파일시 정적 유형 검사를 허용 하는 메서드로 범주를 만들 수 있습니다 (그러므로 컴파일러는 해당 메서드를 통해 다른 클래스라는 것을 알고있는 객체를 추가하려고하면 알려줄 수 있습니다). 배열에는 주어진 클래스의 객체 만 포함됩니다.
일반적으로 Objective-C에는 이러한 제약이 필요하지 않은 것 같습니다. 경험이 풍부한 Cocoa 프로그래머가 그 기능에 대한 소망을 들어 본 적이 없다고 생각합니다. 다른 언어의 프로그래머로 보이는 유일한 사람들은 여전히 그 언어로 생각하고 있습니다. 주어진 클래스의 객체만을 배열로 원한다면 그 클래스의 객체 만 거기에 고정하십시오. 코드가 제대로 작동하는지 테스트하려면 테스트하십시오.
아직 아무도 여기에 올려 놓지 않았으니 제가 할게요!
이제 Objective-C에서 공식적으로 지원됩니다. Xcode 7부터 다음 구문을 사용할 수 있습니다.
NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];
노트
이는 컴파일러 경고 일 뿐이며 기술적으로 모든 객체를 배열에 삽입 할 수 있다는 점에 유의해야합니다. 모든 경고를 빌드를 방해하는 오류로 바꾸는 스크립트가 있습니다.
nonnull
XCode 6에서 사용할 수 있으며 내가 기억하는 한 동시에 도입되었습니다. 또한 이러한 개념의 사용법은 XCode 버전 또는 iOS 버전에 따라 달라 집니까?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
약간 투박해 보이지만 속임수를 사용합니다!
이것은 강력한 유형의 언어 (예 : 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를 확인하는 추가 런타임 오버 헤드도 필요하지 않습니다. 당신은 단위 테스트 커버리지가 훌륭하지 않습니까?
id
필요한 경우를 제외하고는 원시 s를 사용하지 않을 것이라고 장담합니다 Object
. 왜 안돼? 단위 테스트가 있으면 필요하지 않습니까? 유형이 지정된 배열처럼 코드를 유지 관리하기 쉽게 만들어주기 때문입니다. 사람들이 포인트를 양보하기를 원하지 않고 플랫폼에 투자 한 것처럼 들리므로이 누락이 실제로 혜택이되는 이유를 발명하는 것 같습니다.
NSMutableArray
유형 안전성을 강화하기 위해 하위 클래스 를 만들 수 있습니다.
NSMutableArray
A는 클래스 클러스터는 , 그래서 서브 클래스는 사소한 없습니다. 나는 결국 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
Objective-C에 대한 컴파일 타임 (전 처리기 구현) 제네릭 구현 인 https://github.com/tomersh/Objective-C-Generics를 살펴보십시오 . 이 블로그 게시물에는 멋진 개요가 있습니다. 기본적으로 컴파일 타임 검사 (경고 또는 오류)를 받지만 제네릭에 대한 런타임 패널티는 없습니다.
이 Github 프로젝트 는 정확히 그 기능을 구현합니다.
그런 다음 <>
C # 에서처럼 대괄호 .
그들의 예에서 :
NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
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 메서드를 구현해야합니다.
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];
}