꿈돌이랜드

코코아 인터널스 - 5장 불변 객체와 가변 객체 본문

Programming/iOS

코코아 인터널스 - 5장 불변 객체와 가변 객체

loinsir 2023. 8. 10. 11:45
반응형
🪙

코코아 프레임워크 객체는 크게 두 가지로 분류할 수 있다. 하나는 객체를 초기화한 이후에는 내부 데이터를 변경할 수 없는 불변 객체이며, 다른 하나는 변경이 가능한 가변 객체다. 예를 들어 문자열을 다루는 NSString 클래스는 문자열을 바꿀 수 있는 인터페이스가 없는 불변 객체다. 가변 객체를 사용하려면 NSMutableString 클래스로 객체 인스턴스를 생성해야 한다.

5.1.1 불변 객체의 특징

불변 객체들은 다음과 같은 특징을 가진다.

  • 초기화 이후 객체 내부의 값이나 상태가 변하지 않는다.
  • 불변 속성 때문에 여러 객체에서, 여러 스레드에서 참조해도 안전하다.
  • 값이 바뀌는 상황을 고민하지 않기 때문에 설계가 쉽고 구현하기 수월하다.
  • 객체 내부에 모순된 상태가 줄어들어 부작용이 적다.

…중략

불변 객체를 사용할 경우의 단점은 값이 다르면 새로운 객체를 만들어야 한다는 점이다.

불변 객체 인스턴스 개수가 많아질 경우를 대비해 메모리를 효율적으로 사용하기 위한 최적화 과정을 두기도 한다. 불변 객체 중에 정체성이 동일한 객체가 이미 있는지 우선 확인하고, 중복된 불변 객체를 만들지 않도록 최적화한다. 특히 NSString 클래스 리터럴 문자열은 불변 객체로, 프로세스 메모리 영역에 문자열을 할당해서 중복 생성을 줄인다.(문자열 인터닝)

5.1.2 불변 객체 클래스

불변 객체는 가변 객체와 비교해서 구현하기도 사용하기도 쉽다. 불변 객체는 초기화 메서드로 객체의 초기 값을 지정한 이후에는 객체 상태를 변경할 수 있는 메서드를 제공하지 않는다. 대부분 객체 내부의 인스턴스 변수는 감춰지거나(private) 보이더라도 읽기만 가능하다.

자주 사용하는 클래스:

  • 타입별 데이터 구조를 다루는 클래스: NSNumber, NSValue, NSData
  • 규격에 맞춰 데이터를 다루는 클래스: NSString, NSDate, NSURL
  • 다른 객체를 참조하는 클래스: NSArray, NSDictionary
  • 다른 객체를 꾸며주는 클래스: NSFont, NSColor

이 클래스 중 일부는 동일한 역할을 하면서 데이터를 변경할 수 있는 동등한 수준의 가변 객체 클래스가 존재하기도 한다. 반면 가변 객체 없이 값만 저장하는 클래스도 있다. 예를 들어 NSNumber, NSColor. 만약 가변 객체 클래스가 존재한다면, 객체를 복사할 때 -mutableCopy로 가변 객체를 복사할 수 있는지도 확인해야 한다.

5.1.3 불변 객체 구현하기

앞서 설명한 불변 객체 클래스처럼 불변 객체를 구현할 때 고려해야 할 사항은 다음과 같다.

  • 초기화 이후 내부 값이나 상태를 재정의하는 메서드가 없어야 한다.
  • 내부 전용 인스턴스 변수는 감추고 접근하지 못하도록 한다.
  • 인스턴스 변수들은 상속이 불가능하도록 private 속성을 갖도록 하고, 읽기 전용 접근자만 허용한다.
  • 내부 데이터를 바꾸는 게 아니라 새로운 값을 반환하도록 구현한다.
  • 내부에서만 사용하는 가변 객체가 있다면, 외부에서 내부 가변 객체를 반환하거나 수정할 수 있는 인터페이스가 없어야 한다.

하지만 다음의 경우라면 불변 객체로 설계하기보다는 다른 방법을 고민해봐야 한다.

  • 내부 데이터 크기가 너무 커서 복사하기 부담스러운 경우
  • 초기 생성자에서 모든 값을 정할 수 없고 나중에(lazy) 혹은 점진적으로 데이터를 정해야하는 경우
  • 클래스 내부에 구조체를 포함하고, 그 구조체 내부에 변경 가능한 하위 요소가 있을 경우
  • 상태를 공유하는 공용 컨테이너로 동작하는 경우

5.2.1 가변 객체의 특징

가변 객체들은 다음과 같은 특징을 가진다.

  • 초기화 이후에도 객체 내부 값이나 상태를 추가, 삭제, 변경할 수 있다.
  • 여러 객체나 여러 스레드에서 참조하기 위해서는 동시 접근에 대한 예외 처리가 필요하다.
  • 성능 특성을 고려해야 한다. 불변 객체보다 설계가 복잡하고 구현하기 어렵다.
  • 어느 시점이든 값이 변경되서 부작용이 생길 수 있다.

5.2.2 가변 객체 클래스

…중략

객체를 초기화하는 과정에서 모든 데이터를 넘길 수 없을 수도 있고, 점진적으로 값을 변경해서 최종 값과 상태를 사용하는 경우도 있다. 코코아 프레임워크가 이런 경우를 위해서 제공하는 가변 객체 클래스 중에서 자주 사용하는 클래스는 다음과 같다.

  • 다른 객체를 참조하는 클래스: NSMutableArray, NSMutableDictionary, NSMutableSet
  • 특정 타입을 집합으로 다루는 클래스: NSMutableIndexSet, NSMutableCharacterSet
  • 문자열을 다루는 클래스: NSMutableString, NSMutableAttributedString
  • 특정 데이터 구조를 다루는 클래스: NSMutableData, NSMutableURLRequest

불변 객체와 달리 접두어 바로 다음에 ‘Mutable’ 단어를 넣어서 동등한 역할을 하는 불변 객체와 구분하고 있다. 이런 부류의 가변 클래스는 기존 불변 클래스를 상속받고, 추가로 데이터 변경을 위한 메서드를 제공한다. 예를 들어 NSMutableArray는 NSArray에서 상속받아 -insertObject:atIndex:나 -removeObject:atIndex: 메서드처럼 배열 내용을 바꾸는 메서드를 제공한다.

5.2.3 가변 객체 참조 사례1: 가변 모델 객체와 뷰 객체

…중략

테이블 변경

…중략

가변 객체를 참조하는 경우에는 테이블 뷰 갱신 문제처럼, 모델에 바뀐 데이터(가변 객체)를 화면에 반영하기 위해 키-값 감시(KVO)를 하거나 NSNotificationCenter 같은 옵저버 패턴을 활용하기도 한다. 결국 가변 데이터의 흐름을 따라서 컨트롤러와 뷰까지 영향을 주는 코드가 이어지게 된다.

가변 객체를 외부에서 직접 바꿀 수 없도록 불변 객체로 만들더라도 기술적으로 완변한 불변 객체가 아니다. 왜냐하면 키-값 코딩(KVC)을 사용해서 -setValue:forKey: 같은 메서드로 우회적으로 프로퍼티를 변경할 수 있기 때문이다. 그래서 읽기 전용 프로퍼티로 객체 외부에 노출하기보다 감추는 것이 좋다. 객체를 감추는 방법은 구현부에서 클래스 확장 카테고리로 확장하거나, 내부를 변경하는 인터페이스를 제공하고 인터페이스에서 데이터 흐름에 따라 다른 코드로 이어지도록 만들기를 권장한다.

5.2.4 가변 객체 참조 사례2: NSMutableSet와 가변 객체

가변 객체를 참조하는 또 다른 사례를 살펴보자. NSMutableSet 클래스는 내부에 여러 타입의 객체를 담을 수는 있지만, 동일한 객체 인스턴스를 중복해서 추가하지 못한다. 하지만 가변 객체를 “참조”할 때 일시적으로 동일한 객체 인스턴스를 포함할 수도 있다.

NSMutableSet* variableSet = [NSMutableSet set];
[variableSet addObject:@"unique-key"]; // 가변 집합을 만들어서 문자열을 추가한다.
NSLog(@"variableSet = %@", variableSet);
// 결과: variableSet = {("unique-key")}

[variableSet addObject:@"unique-key"]; // 동일한 내용 문자열을 추가할 수 없다. 집합 개념 적용
NSLog(@"variableSet = %@", variableSet);
// 결과: variableSet = {("unique-key")}

NSMutableString* variableString = [NSMutableString stringWithFormat:@"unique"];
[nariableSet addObject:variableString);
NSLog(@"variableSet = %@", variableSet);
// 결과: variableSet = {("unique", "unique-key")} // 값이 동일하지 않기 때문에 정상적으로 추가

[variableString appendString:@"-key"]; // 실제로는 동일한 내용이 집합 내부에 존재하지만 변경됨
NSLog(@"variableSet = %@", variableSet);
// 결과: variableSet = {("unique-key", "unique-key")}

NSSet *copySet = [variableSet copy]; // 복사하면 복사본을 만들면서 집합 내부 객체를 다시 비교하기에 중복해서 만들어지지 않음
NSLog(@"copySet = %@", copySet);
// 결과: variableSet = {("unique-key")}

결국 단지 복사한 것이지만, 전혀 다른 집합이 되어버린다. …중략… 이처럼 가변 객체를 참조하는 경우에는 의도하지 않은 예외 상황에 대해 대비해야만 한다.

객체 중복성 검사 NSSet이나 NSDictionary처럼 키 값을 사용하는 컬렉션은 내부적으로 객체 중복성을 검사할 때, 객체의 -hash와 -isEqual: 메서드가 중요한 역할을 담당한다. 반면 정렬한 배열처럼 순서가 중요한 컬렉션은, 순서를 정하기 위한 비교 메서드가 중요한 역할을 담당한다.

5.2.5 요약

가변 객체를 사용하는 경우에는 가변 객체의 내부 값이 바뀌기 때문에 생기는 부작용에 대비해야 한다.


Uploaded by N2T

반응형