Cocoa Internals - 불변 객체와 가변 객체

불변 객체와 가변 객체

Cocoa 프레임워크 객체는 크게 초기화 이후 내부 데이터를 변경할 수 없는 불변 객체와, 변경이 가능한 가변 객체로 분류할 수 있다.

일반적으로 NSString <-> NSMutableString 처럼 가변의 의미를 담아 클래스 이름이 지어져 있다.

불변 객체

불변 객체의 특징

  • 초기화 이후 객체 내부의 값이나 상태가 변하지 않음
  • 여러 객체에서, 여러 스레드에서 참조해도 안전
  • 값이 바뀌는 상황을 고려하지 않아도 되므로 설계가 쉽고 구현하기 쉬움
  • 객체 내부에 모순된 상태가 줄어들어 사이드 이펙트가 적음

특히 함수 중심 언어에서 불변 객체의 중요성을 강조한다.

불변 객체의 값을 변경해야 하는 경우 새로운 객체를 만들어야 하는 것이 단점이다. 이 때문에 불변 객체 인스턴스가 많아질 경우를 대비한 최적화 과정을 두기도 한다.

그 예시로 NSString 클래스의 리터럴 문자열은 불변 객체인데, 동일한 리터럴 문자열을 가리키는 경우 프로세스 메모리 영역에 문자열을 할당하여 중복 생성을 줄인다.

불변 객체 클래스

불변 객체는 초기화 메소드로 객체의 초기 값을 지정한 이후로는 상태를 변경할 수 없다.

이를 위해 인스턴스 변수는 private 접근 수준을 갖게 하고, 이를 변경할 수 있는 인터페이스를 제공하지 않는다.

NSString, NSData와 같은 클래스는 NSMutableString, NSMutableData와 같은 동등 수준의 가변 클래스가 있으나 NSNumber 같은 클래스는 가변 객체 클래스가 존재하지 않는다.

가변 객체 클래스가 존재한다면 객체를 복사할 때 -mutableCopy 메소드를 사용하여 가변 객체를 복사할 수 있는지 확인해야 한다.

불변 객체 구현하기

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

다음의 경우에는 가변 객체로 설계를 바꾸고 최대한 변동 가능성이 적도록 구현하는 것이 좋다.

  • 내부 데이터의 크기가 너무 커서 복사하기 부담되는 경우
  • 초기 생성자에서 모든 값을 결정하기 힘들고 나중에 또는 점진적으로 데이터를 정해야 하는 경우
  • 클래스 내부에 구조체를 포함하고 그 구조체 내부에 변경 가능한 하위 요소가 있는 경우
  • 상태를 공유하는 공용 컨테이너로 동작하는 경우

요약

가변 객체를 반드시 사용해야 하는 이유가 없다면 불변 객체를 사용하도록 한다.

불변 객체를 사용하는 것으로 객체 간 참조 관계를 단순화할 수 있고, 사이드 이펙트를 최소화할 수 있으며, 스레드 안전성을 누릴 수 있다.

최근 블록을 활용한 핸들러 코드나 비동기 프로그래밍 방식에서도 불변 객체를 사용하는 것이 좋다.

가변 객체

가변 객체의 특징

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

내부 데이터를 변경할 수 있으므로 변경하는 값이 유효한지 판단해야 하고, 이를 참조하는 객체들은 변경 여부를 판단해야 하고, 사이드 이펙트가 없도록 예외 처리를 해야 한다.

가변 객체 클래스

NSMutableArrayNSArray를 상속받고, NSMutableStringNSString을 상속받는 등, 가변 객체 클래스는 동등 수준의 불변 객체 클래스를 상속받고 추가, 삭제, 변경을 위한 인터페이스를 추가로 구현하는 식으로 구현된다.

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

가변 객체를 참조하는 경우 참조하는 객체 내용이 변경될 때 그 변화에 따른 일련의 추가 작업이 필요하다.

예를 들어 어떠한 테이블 뷰가 가변 객체 모델을 참조하는 경우, 해당 모델에 변경이 일어날 때마다 뷰를 갱신하는 작업을 해주어야 한다.

가변 데이터를 외부에서 직접 바꿀 수 없게 하였을지라도 KVC를 사용해서 우회적으로 변경할 수 있으므로, 읽기 전용 프로퍼티로 객체 외부에 노출하는 것보다는 아예 숨기는 것이 좋다.

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

가변 집합 객체에 차례대로 ‘unique-key’, ‘unique-key’, ‘unique’를 넣으면 집합 객체에는 ‘unique-key’와 ‘unique’가 유일하게 존재하게 된다.

이 상태에서 ‘unique’를 ‘unique-key’로 바꾸면 집합 객체에는 ‘unique-key’가 두 개 존재하게 된다.

이 때 이 집합 객체를 복사하면, 복사된 객체는 집합의 개념이 적용되어 유일한 ‘unique-key’가 존재하게 된다.

이처럼 가변 객체를 참조하는 경우에는 의도치 않은 예외 상황에 대비해야 한다.

요약

가변 객체를 사용하는 경우에는 가변 객체의 내부 값이 바뀌기 때문에 생기는 부작용에 대비해야 하며, 의도치 않은 변화에 대비해서 동작 방식을 정확하게 이해하고 있어야 한다.

데이터 내용이 바뀌는 시점에 따라 데이터 흐름을 처리하는 코드가 있다면, 변화를 감지하기 위한 디자인 패턴을 적용하는 것이 좋다.