[Effective Objective-C] #8 객체의 동등 비교를 이해하라
출처 : Effective Objective-C
-
== 연산자를 사용하면 포인터 값을 비교한다.
두 객체가 같은지 비교하려면 NSObject 프로토콜에 정의되어 있는 isEqual: 메서드를 사용해야 한다.
-
몇몇 객체는 이미 같은 클래스인지 확인된 두 객체를 비교하는 특별한 동등 확인(equality-checking) 메서드들을 제공한다.
-
다음 두 메서드는 NSObject 프로토콜의 핵심 동등 비교 메서드이다.
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
위 두 메서드는 NSObject 클래스에 기본적인 구현이 되어 있다.
구현 내용은 두 객체가 같은 객체일 뿐 아니라 포인터도 정확히 같아야 똑같다고 판단하는 것이다.
-
isEqual: 메서드가 두 객체가 같다고 판단하기 위해서는 두 객체의 hash 메서드 결과도 같아야 한다.
그러나 hash 메서드의 반환값이 같은 두 객체가 isEqual: 에서도 같을 필요는 없다.
-
isEqual: 의 예는 아래와 같다.
- (BOOL)isEqual:(id)object {
if ( self == object ) return YES;
if ( [self class] != [object class] ) return NO;
EOCPerson *otherPerson = (EOCPerson*)object;
if ( ![_firstName isEqualToString:otherPerson.firstName] ){
return NO;
}
if ( … ){
return NO;
}
...
return YES;
}
제일 먼저 객체의 포인터가 자신과 같은지 비교한다.
포인터가 같다면 같은 객체이다.
다음으로 두 객체의 클래스를 비교한다.
클래스가 같지 않다면 두 객체는 같을 수 없다.
하위 클래스 인스턴스가 동일하다고 판단되기를 원할 수도 있다. 이 때는 다른 구현을 추가해야 한다.
마지막으로 각 프로퍼티가 같은지 검사해야 한다.
-
서로 같은 객체는 같은 해시값을 반환해야 한다는 메서드 명세가 있다.
그러나 해시값이 같은 객체들이 꼭 같을 필요는 없다.
그러므로 isEqual: 을 재정의하면 반드시 hash 도 재정의해야 한다.
-
집합(set) 구현체는 객체를 서로 다른 배열에 넣기 위해 hash 를 사용한다.
-
hash 를 구현하는 간단한 방법은 아래와 같다.
- (NSUInteger) hash{
NSString *stringToHash = [NSString stringWithFormat:@“%@:%@:%i”, _firstName, _lastName, _age];
return [stringToHash hash];
}
이 방법의 단점은 느리다는 단점이 있다.
문자열을 새로 생성하는 부하가 있기 때문이다.
이 부하는 객체를 컬렉션에 추가할 때 또 다시 성능 문제를 야기한다.
객체가 컬렉션에 추가될 때 hash 를 계산하기 때문이다.
-
다음과 같이 hash 생성할 수도 있다.
- (NSUinteger)hash {
NSUInteger firstNameHash = [_firstName hash];
NSUInteger lastNameHash = [_lastName hash];
NSUInteger ageHash = _age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
효율성을 확보하는 것과 적절한 크기의 해시값을 생성하는 것 사이에서 적절한 균형을 이룬다.
물론 이 알고리즘으로 생성된 해시는 충돌할 가능성이 있다.
그러나 적어도 다수의 값을 반환할 수 있다.
충돌이 발생하는 빈도와 해시값 계산에 드는 계산량의 균형점은 여러 방법을 직접 테스트해 보고 자신의 객체에 가장 적합한 방법을 찾아야 한다.
클래스 전용 동등 비교 메서드
-
NSString 이외에 특별 동등 비교 메서드를 제공하는 클래스는 NSArray(isEqualArray:), NSDictionary(isEqualDictionary:)가 있다.
두 메서드 다 비교하려는 객체가 각각 배열 또는 사전이 아니면 예외를 던질 것이다.
오브젝티브-c 는 컴파일 시간에 타입을 엄격하게 검사하지 않는다.
그래서 잘못된 타입의 객체를 전달하기 쉽다.
그러므로 전달하는 객체가 정확한 타입인지 꼭 확인해야 한다.
-
동등성 비교를 매우 자주 해야 한다면 전용 동등 메서드를 만들어야 한다는 생각이 들 수 있다.
그렇게 하면 객체를 비교할 때 타입을 체크하지 않아 속도향상의 이득을 많이 얻을 수 있다.
전용 메서드를 만드는 또 다른 이유는 코드를 좀 더 예쁘게 보이고 읽기 편하게 만들기 위해서다.
-
전용 메서드를 만들면 isEqual: 메서드도 재정의해야 한다.
isEqual: 메서드가 인자로 받은, 비교할 객체의 클래스가 리시버 객체 자신의 클래스와 동일하면 전용 메서드로 넘긴다.
같지 않다면 상위 클래스의 구현으로 전달하는게 일반적인 구현 패턴이다.
깊은 동등성 대 얕은 동등성
-
동등 비교 메서드를 새로 만들 때 객체 전체를 비교할지, 아니면 몇몇 필드만 비교할지 정해야 한다.
NSArray 는 모든 객체가 같다면 두 배열을 같은 것으로 간주한다.
이를 깊은 동등성(deep equality) 비교라고 한다.
-
반대로 필요한 부분만 체크하여 동등하다고 보는 것을
얕은 동등성(swallow equality) 비교라고 한다.
컨테이너 내부의 가별 클래스들의 동등성
-
객체를 컬렉션에 추가하고 나면 그것의 해시값을 변경할 수 없다.
분류된 객체의 해시값이 바뀌면 객체는 이제 잘못된 곳에 담겨 있는 상황이 된다.
객체의 가변 구역에서 해시는 제외하는 것을 보장하거나, 간단히 컬렉션 내부에 있는 객체는 변경하지 않게 해야 한다.
-
컬렉션에 포함된 객체를 수정했을 때 일어나는 일을 알고 있어야 한다는 것!
이런 일(즉, 컬렉션에 포함된 객체를 수정하는 일)을 절대로 하지 말라는 것이 아니라,
그렇게 했을 때 발생하는 잠재적인 문제점과 그에 따른 코드를 잘 알고 있어야 한다는 것.
기억할 점
객체의 동등성을 비교하려면 그 객체의 isEqual: 과 hash 메서드를 구현하라.
같은 객체는 항상 해시값이 같아야 한다.
그러나 같은 해시값을 가진 객체가 꼭 동일할 필요는 없다.
객체가 동등한지 비교할 때 모든 프로퍼티를 비교하지 말고 꼭 필요한 프로퍼티만 비교하라.
hash 메서드를 구현할 때 최대한 빠른 결과를 나올 수 있게 구현해야 하지만 해시값 충돌도 최소화해야 한다.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Effective Objective-C] #9 클래스 클러스터 패턴을 사용해 구현의 상세 내용을 숨기라 (0) | 2017.08.11 |
---|---|
[Effective Objective-C] 목차와 요약을 통해 한 눈에 알아보는 Effective Objective-C #1 ~ #8 (0) | 2017.08.10 |
[Effective Objective-C] #7 인스턴스 변수에 내부에서 접근할 때는 직접 접근하라. (0) | 2017.07.28 |
[Effective Objective-C] #6 프로퍼티를 이해하라 (0) | 2017.07.27 |
[Effective Objective-C] #5 열거형을 사용해 상태, 옵션, 상태 코드를 정의하라 (0) | 2017.07.26 |
댓글