ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [NSHipster - iOS] 객체의 동등성 Equality 와 Identity에 대하여
    앱등이에게 살충제를 뿌린다./iOS 2016. 1. 26. 22:56


    <identity - Doggy Scaring Movie, 2003>


    Equality & Identity

    먼저 Equality와 Identity간의 차이를 아는 것이 중요합니다.


    같은 관찰가능한(Observable)프로퍼티들을 공유하고 있다면 두 객체는 Equal하다고 볼수있습니다. 또한 각 객체는 자신만의 Identity를 가지며 서로 독립적(distinct)이라고도 할 수 있습니다. 프로그래밍에 있어서 Identity는 객체의 메모리 주소와 관련되어 있는 부분입니다.


    NSObject클래스에서는 isEqual:메소드를 통해 다른 객체와의 equality를 판단합니다. 이 기본적인 메소드의 구현부에는 바로 두 객체의 identity를 비교하는 과정을 거칩니다. 두개의 NSObject객체는 같은 메모리 주소공간을 가리키고 있어야 Equal하다고 판단하게 됩니다.


    NSArray, NSDictionary, NSString과 같은 컨테이너 클래스들에서는 동등성 비교를 위해 좀 더 깊이 들어갑니다. 바로 컬렉션의 멤버들의 Equality를 각각 비교하게 됩니다.

    NSObject의 서브클래스에서 isEqual:메소드를 커스터마이징하여 구현을 하고자 한다면 아래의 내용들을 따라야 합니다.

    • 의미있는 값 비교를 하는 isEqualTo___ClassName__:메소드를 구현해야 합니다.
    • 클래스와 객체의 identity비교를 위한 isEqual:메소드를 오버라이딩해주시고 위에 말씀드린(의미있는 값 비교를 하는) 메소드를 사용하시면 됩니다.
    • hash메소드를 오버라이딩 해주세요. 이 메소드는 아래에 설명합니다.
    위 3가지를 NSArray 클래스에 한번 적용시켜보도록 하죠. (실제로는 훨씬 더 복잡합니다.)

    Foundation에 있는 NSObject의 서브클래스들(아래)은 Equality확인을 위한 메소드를 커스텀하게 구현하고 있습니다.

    • NSAttributedString -isEqualToAttributedString:
    • NSData -isEqualToData:
    • NSDate -isEqualToDate:
    • NSDictionary -isEqualToDictionary:
    • NSHashTable -isEqualToHashTable:
    • NSIndexSet -isEqualToIndexSet:
    • NSNumber -isEqualToNumber:
    • NSOrderedSet -isEqualToOrderedSet:
    • NSSet -isEqualToSet:
    • NSString -isEqualToString:
    • NSTimeZone -isEqualToTimeZone:
    • NSValue -isEqualToValue:
    위 클래스들의 두 객체를 비교할 때, isEqual:메소드보다 좀 더 high-level인 위의 메소드들을 사용하기를 권장합니다.
    그런데 우리가 위에서 언급했던 hash를 아직 구현하지 않았죠? 이제 hash메소드에 대해서 알아보도록 합시다.
    (NSString을 통해 살짝 삼천포로 빠졌다 가볼게요)



    The Curious Case Of NSString Equality

    좀 재밌는 상황인데요, 아래 예제를 한번 생각해보세요.

    먼저 NSString객체를 비교하는 올바른 방법은 isEqualToString이라는 점을 알아주세요. 어떤 상황에서도 NSString을 비교하기 위해 ==을 사용하는 경우는 없습니다.

    자 그럼 위 코드에서 어떤 일이 일어나게 될까요? 왜 비슷한 코드가 NSArray나 NSDictionary에서와 달리 YES로 작동할까요? 


    string interning이라고하는 기법과 연관되어 있습니다. 하나의 고유한 수정불가능한 스트링은 하나의 메모리 공간에 올라가 있습니다. 따라서 NSString *a와  *b모두 같은 문자열 @"Hello"를 참조하고 있으니 같은 메모리 공간을 가리키고 있는겁니다. (스태틱하게 생성된 수정불가능한 스트링에 한해서만 올바르게 작동함을 참고하세요.)

    재미를 위해 말씀드리자면, Objective-C의 메소드명 또한 interned string으로 Shared string pool에 저장됩니다.



    Hashing

    OOP에서 보통 객체의 Equality를 테스트 하는 데에는 컬렉션의 멤버들을 모두 비교하는 것입니다. 커스텀 Equality 비교를 구현하는 서브클래스들에서는 hash메소드도 구현해주어야 합니다.

    • 객체의 Equality는 commutative합니다. ( [a isEqual:b] => [b isEqual:a] )
    • 만약 객체끼리 Equal하다면 객체의 hash값 또한 같습니다. ( [a isEqual:b] => [a hash] == [b hash] )
    • 역은 성립하지 않습니다. hash값이 같다고 해서 객체가 같은건 아닙니다.


    잠깐 기초를 살펴보도록 합시다.

    Hash Table은 프로그래밍에서 널리 기본적으로 사용되는 자료구조입니다. 바로 이 자료구조 덕분에 NSDictionary와 NSSet클래스에서 멤버를 찾는데 상수시간만큼만 필요한 것입니다. Hash Table을 쉽게 이해하기 위해서 배열과 비교해서 설명을 해보도록 하겠습니다.
    Arrays는 연속적인 인덱스에 데이터를 저장합니다. 예를 들어 n개의 데이터를 저장한다면 0,1,2, ... ,n-1이라는 인덱스에 데이터를 저장하죠. 어떤 데이터가 이 배열에 저장되어 있는지를 검색하기 위해서는 배열의 인덱스 하나하나를 처음부터 살펴봐야 합니다.

    Hash Table은 조금 다른 방식으로 접근합니다. 데이터를 연속적으로 저장(0,1,2,...)하는 것이 아니라 n개의 메모리 공간을 할당합니다. 그리고 해쉬 함수를 사용하여 데이터가 저장될 메모리 공간을 결정합니다. Hash함수는 결정론적이고, 좋은 Hash함수는 컴퓨팅의 자원소비가 과하지 않은 선에서 각각 다른(uniform distribution) Hash값을 생성해냅니다. 두개의 다른 객체가 같은 Hash값을 생성하게 되면 '충돌이 발생했다'고 합니다. 충돌이 발생했을 때, Hash Table은 충돌 지점을 찾은 뒤 다른 새로운 공간을 할당하게 됩니다. Hash Table이 점점 복잡해질수록 충돌의 가능성 또한 증가하게 됩니다. 그러면 Hash Table은 더 자주 빈 공간을 찾아 헤매게 되겠죠. (Hash함수가 각각 다른 Hash값을 생성해줘야 하는 이유가 여기에 있습니다.)



    커스텀 Hash함수를 구현할 때, 가장 크게 하는 오해가 후건 긍정의 오류(affirming the consequent)입니다. 바로 Hash값이 유니크하다는 착각때문이죠. 이러한 오해로 인해 불필요할 정도로 복잡한 구현들을 하게되는 경우도 종종 있습니다. 보통은 99%이상의 경우에 Hash값에 XOR연산을 몇 차례 수행함으로써 주요한 프로퍼티들의 Hash값들을 처리해줄 수 있습니다.


    트릭을 알려드리자면 객체의 주요한 값(Critical value)이 무엇인지 생각해보는 것입니다.

    예를들어 NSDate에서는 특정 date로 부터의 time interval의 값 하나면 충분합니다.


    UIColor같은 경우에는 RGB의 값을 쉬프트한 뒤 더한값으로 계산해주면 되겠습니다.



    Implementing -isEqual: and hash in a Subclass

    위 내용을 모두 가져와 아래에서 어떻게 한 서브클래스가 Equality를 구현해놓았는지를 보여주고 있습니다.


    더 궁금하신 내용은 this post from Mike Ash를 참고하세요. 


    출처 : http://nshipster.com/equality/

Designed by Tistory.