Introduction to Collections
OSXDEV
Core Foundation의 콜렉션 객체는 모든 타입의 자료에 대해 많은 양을 저장하고 조작하고 읽어올 수 있다. 이 문서는 arrays, sets, dictionaries와 같은 여러가지 타입의 객체를 하나로 그룹화 할 수 있는 객체에 대해서 다루고 있다.
목차 |
[편집] 이 문서의 내용
조직화된 데이터를 빠르고 정확하게 가져오는 것 이외에도, 콜렉션 객체는 프로그래밍하는데 여러가지 도움이 된다.
- 저장된 자료형에 맞게 콜렉션의 기본 특성을 사용자의 요구에 맞게 수정할 수 있다 (“콜렉션 사용자 정의화하기”).
- 프로그램 정의 로직을 콜렉션의 모든 데이터 요소에 적용할 수 있다. ("프로그램 정의 함수를 콜렉션에 적용하기").
- 콜렉션에 들어간 값의 유일성(중복된 값이 없는지)을 확신할 수 있다.
- 콜렉션의 데이터 요소를 수정(추가, 삽입, 삭제, 정렬 등) 하는 것이 가능하다.
- string 객체(CFString), number 객체(CFNumber와 CFBoolean) 그리고 data 객체(CFData)와 함께 Core Foundation의 property list의 핵심이다. 자세한 내용은 Property Lists을 보기 바란다.
Core Foundation 몇가지 타입의 콜렉션 객체를 정의하고 있다:
- Array 객체 (opaque type CFArray)는 순차적으로 데이터 요소를 관리한다.
- Dictionary 객체 (opaque type CFDictionary)는 임의의 key를 통해 데이터 요소를 식별한다.
- Set 객체 (opaque type CFSet)는 데이터 요소의 중복이 허용되지 않는 콜렉션이다.
- Bag 객체 (opaque type CFBag)는 데이터 요소의 중복이 허용되는 콜렉션이다.
- Tree 객체(opaque type CFTree)는 부모-자식간의 계층적 관계로 데이터 요소를 관리한다.
콜렉션 객체는 어느정도는 값을 담는 컨테이너이라 할 수 있다. (이 문서에서 "값(value)"이라는 단어는 콜렉션에 담긴 하나의 요소를 뜻한다.) 그러나 콜렉션이 값을 포함하고 분배하는 방식에는 큰 차이가 있다. 이 문서의 구성이 그 차이를 반영하고 있다. "진짜 콜렉션"이라고 할 수 있는 array, dictionary, set, bag은 서로의 큰 유사성때문에 함께 설명하고 있다. 그 다음에 진짜 콜렉션이라고 하기에는 구조적으로 조금 차이가 있는 tree로 넘어간다.
콜렉션 API는 콜렉션 객체를 통해 원하는 작업을 할 수 있게 해준다. 콜렉션을 생성하고, 값을 추가하고, 다시 값을 뽑아내고 등등의 작업이 가능하다. CFArray, CFDictionary, CFSet, CFBag의 프로그래밍 인터페이스가 하는 일이나 그것을 처리하는 방식에서 굉장히 유사하기 때문에, 다음의 단락에서 이 모두를 함께 다루기로 한다. 그러나 CFTree 객체의 프로그래밍 인터페이스는 따로 다루기에 충분할 만큼 다른 객체들과 다르기 때문에 "Tree 구조체의 생성과 사용"에서 다루기로 한다.
[편집] 콜렉션의 일반적인 특성
콜렉션 객체(앞으로는 간단히 콜렉션이라고 하겠음)는 개별적인 값들(보통 같은 타입이다)을 저장하는 컨테이너이다. 콜렉션은 다른 Core Foundation 객체와 사용자 정의 자료 구조, 그리고 기본 자료형의 값들을 포함할 수 있다. 포함된 Core Foundation 객체의 타입은 서로 달라도 된다. 엄밀히 말해서 "포함한다(contain)"는 정확한 용어가 아니다 (하지만 개념적으로 유용하다) 왜냐하면 Core Foundation 콜렉션의 데이터 요소는 포인터의 크기(프로세서와 컴파일러에 따라 다르다)보다 클수가 없다. 그러나 포인터는 엄청난 유연성을 제공하기 때문에, 콜렉션 객체가 Core Foundation 객체나 포인터 주소보다 더 큰 공간을 차지하지 않는 어떤 데이터 덩어리에(integer와 같은 기본형 값 포함) 대한 포인터를 참조할 수 있다.
중요: 기본형의 값을 직접 콜렉션 객체에 저장할때는 주의를 기울여야 한다. 포인터 값의 크기가 어떤 플랫폼에서는 달라질 수가 있고 코드가 실행될 수 있는 플랫폼이 다양할 수 있기 때문에, 기본형 값을 콜렉션에 직접 넣는 것은 값이 짤리는 것과 같은 이상한 결과가 발생될 수 있다.
모든 콜렉션 객체에서는 포함된 자료중에 특정한 외부 속성를 만족시키는 것에 접근이 가능하다. 이 속성(일반적으로 "key"라고 부른다)은 콜렉션의 타입에 따라 자료를 조작하는 체계에 따라 다르다. 예를 들면 array에 대한 key는 콜렉션 내부에서의 위치를 가리키는 integer 값이다. 반면에 dictionary같은 경우는 어떤 임의의 값을 key로 사용하여 콜렉션에서 값을 가져올 수 있다(dictionary의 경우는 "key"가 좀 더 관례적인 뜻을 갖는다). 콜렉션의 Core Foundation 객체는 그 객체를 소유하고 있는 코드에서 이미 해제해 버렸을 수도 있기 때문에, retain 을 하던가 복사해서 넣어야 한다. 특정 콜렉션 타입에 대해 더 알고 싶다면 "콜렉션의 종류"를 보기 바란다.
Core Foundation 콜렉션 객체들은 서로 비슷한 방법으로 포함하고 있는 값들을 자동으로 다룬다. 콜렉션은 입력된 값에 대해 포인터를 통한 약한 참조(weak references)를 갖는다, 그러나 추가적인 retain(유지), release(해제) 방식을 정의할 수 있다. 대부분의 콜렉션 객체는 콜렉션 요소들에 대해 기본적인 수행을 정의해놓은 콜백 함수들을 포함하고 있다. 이 콜백 함수들은 호출되어 다음과 같은 일을 수행한다.
- 콜렉션에 추가된 요소들에 대해 retain 수행
- 콜렉션에서 제거된 요소들에 대해 release 수행
- 콜렉션 내의 한 요소와 다른 요소를 비교
- 포함된 요소들에 대해 디버깅 정보 출력
- set, bags, dictionary의 key에 대해 hash code 계산
콜렉션이 Core Foundation 객체를 담을 경우 기본 콜백 함수가 제공된다. 그러나 사용자 정의 자료를 포함하고 있거나, 기본 콜백 함수가 사용 목적에 충분하지 못하면, 직접 콜백 함수를 정의할 수 있다. "콜렉션의 일반적인 특성"과 "콜렉션의 생성과 복사"를 보기 바란다.
tree를 제외하고(tree는 다른 객체를 포함하는 진짜 컨테이너가 아니다), 콜렉션은 변경가능과, 변경불가능한 2가지 종류가 있다. 변경불가능한 콜렉션의 값들은 콜렉션이 소멸될때까지 그대로이다, 값을 넣을 수도 없고 지울수도 없다. 변경가능한 콜렉션은 값을 추가하고, 순서를 변경하고 삭제할 수 있다. 변경가능한 콜렉션은 다시 고정된 크기와 변경가능한 크기의 두가지 종류로 나눠지는데, 콜렉션 객체를 생성하는 함수의 capacity 파라메터 값에 따라 그에 맞는 객체가 생성된다. 고정 크기 콜렉션을 사용할 경우 포함할 수 있는 최대 값들의 갯수를 지정해 주어야 한다. 변경가능한 크기의 콜렉션을 사용할 경우 무제한의 값을 담을 수 있다 (메모리가 허용하는 한도 등의 외부적 요인에 의해 제한될 수 있다). 고정 크기의 콜렉션은 더 성능이 좋은 경향이 있으나, 담을 수 있는 값들의 최대 한도를 명시해야 한다.
어떤 콜렉션 함수들은 변경가능과 변경불가능한 콜렉션 모두에서 동작한다 (예를 들면 데이터 요소들의 갯수를 구하는 거나, 데이터에 접근하는 것이 있다). 하지만 추가, 삽입, 삭제, 정렬과 같은 것들은 오직 변경가능한 콜렉션 객체들에서만 동작한다. 이러한 작업에 대해서는 "변경가능한 콜렉션의 사용" 을 보기 바란다.
특히 재밌는 것으로 applier 함수가 있다. 대부분의 콜렉션 객체는 콜백 함수를 정의하는 것을 허용한다. 이름에 ApplyFunction이 포함된 콜렉션의 함수에 콜백 함수 포인터를 전달한다. 콜백 함수에 정의된 내용이 차례로 콜렉션의 각각의 요소에 적용된다. 이러한 방식은 for 루프를 돌며 콜렉션의 요소를 하나하나 처리하는 방식과 본질적으로 같다.
[편집] 콜렉션의 종류
Array, dictionary, set, bag, tree와 같은 Core Foundation의 주요 콜렉션 타입은 서로 비슷한 특성을 가지고 있지만, 서로 다른 방식으로 데이터 요소를 조직화하고, 어떤 경우는 포함할 요소를 제한시키기도 한다.
[편집] Array
Opaque Type CFArray 객체인 array는 간단한 순차적 컨테이너이다. 값은 들어간 차례대로 저장되며, 그 순서중에 원하는 값의 위치를 나타내는 integer 값(index)이 key가 된다. 인덱스값의 범위는 0부터 n-1까지이다(n은 array가 포함하고 있는 값의 갯수). 인덱스 0은 array의 첫번째 값을 가리킨다. array를 번호가 붙은 슬롯(slot) 정도로 생각하면 될 것이다.
array는 어떤 기준에 의해 순위가 매겨진 항목들의 목록이나 배열안의 값들과 메뉴와 같은 list형의 컨트롤 사이에 맵핑이 이루어지는 경우같은 순차적인 자료의 저장이 필요한 상황에 이상적이다. 그러나 수집된 값들을 저장하고, 각각의 요소를 반복해서 처리하는(iterating) 일반적인 목적의 컨테이너로 사용해도 무방하다.
array는 자료를 빽빽하게 보관한다. 변경가능한 array의 경우 값을 삭제하여도 array안에 빈 공간이 생기지 않는다. 삭제된 값보다 큰 인덱스를 갖는 값들은 인덱스를 하나씩 줄인다. 값을 추가하는 경우는, 그 뒤의 모든 값들의 인덱스를 하나씩 증가시킨다. 이러한 특성 때문에, 특정한 객체에 접근하는 key 값은 값이 추가되고 삭제될때마다 변경된다. 그러나 유효한 인덱스 값의 범위는 항상 0부터 n-1 까지이다.
값에 접근하는 경우 어떤 구현에서든 최악의 경우 O(log N)의 성능이 보장된다.(역자 주: O(log N)은 보통 트리 구조(Core Foundation의 tree가 아닌 추상자료형으로서의)에서 값에 접근하는데 걸리는 시간이다. 이 말의 뜻은 CFArray가 현재든 앞으로든 내부적으로 어떤 구현을 사용하던지간에 최악의 경우 O(log N)은 보장해주겠다는 뜻이다.) 하지만 보통은 O(1)의 시간이 걸린다. 선형적인 탐색(linear search)의 경우 비슷하게 최악의 경우 O(N*log N)의 성능이 보장된다, 그러나 대부분의 경우 그것보다 빠르다. 추가와 삭제의 경우 일반적으로 선형적 시간이 소요되지만 어떤 구현에서는 최악의 경우 O(N*log N)의 시간이 걸린다. array 에는 특별히 성능에 유리한 위치가 없다, 예를 들면 인덱스가 작은 값에 접근하는 것이나 높은 인덱스의 값을 추가하거나 삭제하는게 꼭 더 빠른것은 아니다.
[편집] Dictionary
CFDictionary 타입의 객체인 dictionary는 해쉬 기반의 콜렉션이다. dictionary의 key는 임의의 값이나 프로그램에서 정의된 데이터의 일부(또는 데이터를 가리키는 포인터)를 사용할 수 있다. key 값으로 보통 string (또는 Core Foundation의 CFString 객체) 이 사용되지만, 포인터의 크기에만 맞게 들어갈 수 있다면 integer, Core Foundation 객체에 대한 참조, 심지어 자료구조에 대한 포인터등 어떤 값이든 사용가능하다. dictionary의 key는 다른 콜렉션 객체의 key와 다르다. 개념적으로 dictionary의 key 값은 value 값과 함께 콜렉션에 담겨 있다. Dictionary는 유저 인터페이스의 텍스트 필드에서 받아온 값처럼 데이터에 이름표를 붙여 관리하는 경우 유용하다.
Core Foundation에서 dictionary는 특정 값에 접근하는데 사용되는 key가 값이 추가되고 삭제되는 경우에도 변경되지 않는다는 점에서 array와 다르다. array에서는 값이 추가되고 삭제되는 경우에 key(index)가 변경되었다. 그리고 array와 다르게, dictionary는 값을 순차적으로 넣지 않는다. 나중에 값을 빼내오기 위해서는, key와 그에 해당하는 값이 일정한 짝을 이루어야 한다. 만약 값을 넣은 다음에 key가 변경되어 버리면, 그 값을 빼낼 방법이 없다. dictionary에서 key들은 집합(set)을 형성한다. 달리 표현하자면 key는 dictionary 안에서 중복된 값이 없다는 것이 보장된다.
사용자 정의된 CFDictionary는 해쉬를 사용하지 않을 수도 있고, 값을 저장하기 위해 해쉬 테이블을 사용하지 않을 수도 있지만, key는 해쉬 코드를 생성하는 함수와 해쉬 코드에 기반해서 두개의 키가 동일한지 테스트 하는 두개의 함수를 필요로 한다. 이 두개의 함수는 다음의 불변식을 만족하여야 한다.
if equal(X,Y) then
hash(X) == hash(Y)
이 식의 역은 보통 참이 되지 않지만, 대우는 다음의 논리식이 요구된다.
if hash(X) != hash(Y),
then !equal(X,Y)
해쉬 계산과 key를 비교하는 콜백함수가 NULL로 설정되면, key는 포인터 크기의 integer로써 사용되고 포인터 비교가 사용된다. 최상의 성능을 위해서 key의 집합에 대해서 충분히 분포된 해쉬 값을 계산해 내는 콜백함수를 제공하는 것이 좋다.
CFDictionary 객체에서 값에 접근하는데 걸리는 시간은 최악의 경우 O(log N)이 걸리지만, 보통은 O(1)의 상수 시간이 걸린다. 삽입과 삭제 연산은 일반적으로 상수 시간이 걸린다, 하지만 최악의 경우 O(N*log N)이 걸린다. key를 통해 값에 접근하는 것이 직접 접근하는 것보다 빠르다. Dictionary의 경우 같은 갯수의 값에 대해서 array보다 더 많은 메모리를 차지한다.
[편집] Set 과 Bag
Set과 bag은 연관 타입 콜렉션이다. 두 타입의 공통점은 콜렉션의 값에 접근하는 key가 값 자체라는 점이다. Set과 bag 사이의 차이점은 값을 포함하는 방식에 있다. Set에서는 값이 이미 콜렉션에 들어있다면, 동일한 값은 set에 추가될 수 없다. 반대로 bag에서는 동일한 값을 추가하는게 가능하다.
이런 "유일한" 속성은 쓸모가 많다. 예를 들어, 여러분의 프로그램이 인터넷을 검색하고 URL을 보관하게 하고 싶은데 중복되게 하고 싶지는 않다고 하자. 이런 상황에 set은 아주 적절한 선택이다. Bag은 통계를 구하는데 유용하다. 수집된 모든 값을 bag에 넣고 각각의 값이 몇번 나타나는지 계산하면 된다.
key와 포함된 값의 동일성은 정의된 콜백함수에 의해 결정된다. 이 함수는 동일함을 위해 어떤 규칙을 설정한다. 예를 들면, set이나 bag의 값이 고객의 레코드를 표현하는 사용자 구조체가 될 수 있다. 외부의 값(key)과 내부의 값이 서로 같은지를 확인하기 위해 고객번호 필드가 같은지 확인할 수 있다. (다른 모든 콜렉션에서와 마찬가지로, Core Foundation 객체가 set이나 bag의 값으로 들어갈때 기본 콜백 함수를 정의할 수 있다) Set의 경우 (dictionary와 마찬기지로), equal 콜백 함수는 값이 콜렉션에 들어가기 적합한지를 결정한다.
[편집] Tree
CFTree 타입의 tree는 계층적으로 조직화된 구조(파일 시스템에서의 파일과 디렉토리 같은)에서의 노드(node)를 나타내는데 사용된다. 이 tree 구조에서 각각의 노드는 tree 객체이고, 이 객체들은 다른 tree 객체와 연관성을 갖는다. 개념적으로 CFTree 형의 객체는 다른 Core Foundation의 콜렉션 객체와 확연히 다르다. Array, dictionary, set, bag은 여러값들을 담는 컨테이너이다, 그러나 tree 객체는 데이터와 일대일 관계를 갖는다. 다른 말로, tree 객체는 오직 하나의 포인터크기의 값과 연결되어있다 (그 값이 다양한 멤버를 포함하는 구조체거나 심지어 콜렉션 객체의 참조일수도 있다). 그러나 tree 객체의 중요한 특정으로 tree 객체는 많은 하위트리에 대한 참조를 포함하고 있다는 것이다. 이런식으로 내부적으로 연결된 tree 객체들의 그룹이라는 관점에서 볼때 하나의 콜렉션으로 생각할 수 있다.
부모-자식의 패러다임은 tree 객체를 이해하는데 유용하다. 위에서 언급했듯이, 이 객체들은 서로 계층적인 관계를 갖고 있다, 그리고 계층관계의 최상위에는 "root" tree가 있다 . Root 트리는 다른 tree의 하위트리가 아니며, 부모트리도 갖고 있지 않다. 하지만 이것은 하나 이상의 하위트리나 자식트리를 갖는다. 그리고 이 자식트리들은 다른 트리의 부모가 될 수 있다.
Tree 객체는 객체간의 관계를 관리하기 위한 규칙을 가지고 있다. Tree에 자식을 추가하면, 부모는 그것을 retain 한다, 하지만 자식은 부모를 retain하지 않는다. 추가적으로, tree 객체 자신이 자식으로 속해 있는 트리를 자식트리로 갖지 않는다. 트리와 그 서브트리에서 사이클 관계가 형성되는 것은 허용되지 않는다. 같은 부모를 갖는 2개의 트리를 서로 "siblings" 관계에 있다고 한다. 개념적으로 sibling 은 링크드 리스트에서 순차적으로 있다고 할 수 있다. 트리의 자식에서 행해지는 연산은 오직 직접적으로 하위관계에 있는 것들에만 적용된다. 달리 말하자면, 연산이 재귀적으로 자식들의 서브트리에까지 적용되지 않는다.
다른 콜렉션 객체와 다르게, Tree는 포함하는 값들을 처리하는 연산에 필요한 콜백함수를 필요로 하지 않는다, 구조적으로 tree의 값들은 오직 CFTree 타입이여야 하기 때문이다. 그러나, tree 객체를 좀 더 유용하게 쓰기 위해서 데이터를 tree와 연관시키는게 가능하다. 그리고 이 데이터는 다른 트리들과의 관계를 정의할 수 있다. Core Foundation 트리 객체를 생성하면, 그 트리를 위한 "내용(context)"을 정의해야 한다. Context는 연관된 data를 나타내며 retain, release, 비교, 설명하는 등 그 데이터에 필요한 연산을 수행하는데 필요한 콜백함수를 정의한다.
[편집] 콜렉션을 사용자 정의화하기
Core Foundation은 값에 대한 처리를 사용자 정의 할 수 있는 방법을 제공한다. 콜렉션 객체를 생성할때 값을 처리할 콜백 함수들을 담고있는 구조체를 넘겨주어 콜렉션의 작동 방식을 설정할 수 있다. Tree 객체에 넘겨지는 구조체의 타입은 다른 타입의 콜렉션 객체에 넘겨지는 구조체와 다르다.
Array, dictionary, set, bag의 생성 함수에 콜백 구조체(예를들어 CFArrayCallBacks 같은) 포인터를 넘겨준다.이 구조체는 Core Foundation에 의해 호출되어 콜렉션에 넣어진 값을 retain, release하고 설명하고 비교하는 함수에 대한 포인터를 포함하고 있다. 콜렉션에 값을 넣은 코드에서 나중에 그 값을 해제할 수도 있기 때문에, 콜렉션에 값이 계속 남아있도록 하기 위해서 어쨌든 값을 retain 하는 것이 이상적이다. (콜렉션에 값이 추가될때 retain 하지 않도록 할 수도 있지만, 프로그램에 정의되지 않은 작동을 초래할 수도 있다.) 콜렉션에서 값이 제거될때 이전에 retain 하였다면 release 해주고, 이전에 할당하였다면 해제해준다. 값을 비교하는 콜백 함수는 정렬(sorting) 이나 검색 연산에 사용된다. 값을 설명해주는 콜백 함수는 CFCopyDescription(이 함수는 CFShow에 의해 호출된다)에 의해 호출되어 콘솔에 디버깅 정보를 출력해준다.
Tree의 생성 함수에는 초기화된 컨텍스트 구조체(CFTreeContext)를 넘겨주어야 한다. 이 구조체는 retain, release, 비교, 설명하는 콜백 구조체와 같은 종류의 함수들에 대한 포인터를 갖고 있다. 그러나 이 함수들은 컨텍스트 구조체의 tree에 연관된 데이터나 그 포인터 값을 담고 있는 info 멤버에 대해서만 작동한다.
콜렉션의 값에 대해 특정 타입의 자료를 retain 하는 것과 같은 특별한 연산이 필요하지 않다면 콜백이나 컨텍스트 함수 포인터에 NULL을 넣을수도 있다. array, dictionary, set, bag과 같은 콜백 구조체와 함께 생성되는 콜렉션 객체는 콜렉션이 Core Foundation 객체를 값으로 포함할때 미리 정의된 기본 콜백 함수로 초기화된 구조체를 지정할 수 있도록 하고 있다.
[편집] 콜렉션의 생성과 복사
다른 Core Foundation 타입에 비해 콜렉션 객체는 생성과 복사에 많은 옵션이 있다. 콜렉션 객체는 변경불가능할 수 있고, 변경가능할 수도 있다, 변경가능한 경우 고정크기와 가변크기로 다시 나뉘어진다 (변경불가능한 객체는 당연히 고정크기이다). 이러한 다양한 객체들은 각자 가능한 것과 제한사항이 있다.
변경불가능한 콜렉션에서는 일단 생성된 후에는 값(dictionary의 key포함)을 변경할 수 없기 때문에, 객체를 생성할때 필요한 값을 모두 입력해야 한다. 초기값은 C언어의 배열(여러개의 값일 경우)의 형태를 통해 입력받을 수 있다. 입력 매개변수에 반드시 C 배열의 주소값을 명시해 주어야 한다. "목록 1"은 CFArray 객체가 생성되는 것을 보여준다.
목록 1. 변경불가능한 CFArray 객체의 생성
CFStringRef strs[3];
CFArrayRef anArray;
strs[0] = CFSTR("String One");
strs[1] = CFSTR("String Two");
strs[2] = CFSTR("String Three");
anArray = CFArrayCreate(NULL, (void *)strs, 3, &kCFTypeArrayCallBacks);
CFShow(anArray);
CFRelease(anArray);
kCFTypeArrayCallBacks의 주소를 CFArrayCreate의 마지막 매개변수로 넘기는 것에 주목하자, 이 상수는 CFArray를 위한 미리 정의된 콜백 구조체이다. Array, dictionary, set과 bag 같은 콜렉션 객체를 생성하고 복사하는 함수는 콜백 구조체를 매개변수로 받는다. 이 구조체는 값(그리고 key)을 보관하고, 평가하고, 설명하는데 사용되는 콜백 함수에 대한 포인터를 포함하고 있다. 위에서 나열된 각각의 콜렉션 타입은 값으로 Core Foundation 객체가 들어올 경우 사용할 수 있는 하나 이상의 미리정의된 콜백 구조체를 정의하고 있다. 모든 미리 정의된 콜백 구조체는 retain 하는데 CFRetain을 사용하고 release 하는데 CFRelease를 사용하여 객체가 콜렉션에 추가되면 retain을 수행하고, 제거되면 release 를 수행한다.
변경불가능한 dictionary 객체를 생성하는 CFDictionaryCreate 함수는 다른 콜렉션 함수와 조금 다르다. 이 함수는 값은 물론 그 값들에 연관된 key의 집합을 필요로 한다. 값과 key의 목록을 지정하는 일반적인 방법은 2개의 배열을 이용하는 것이다. "목록 2"에 간단한 예를 보여주고 있다.
목록 2. 변경가능한 CFDictionary 객체의 생성
CFStringRef keys[3];
CFStringRef values[3];
CFDictionaryRef aDict;
keys[0] = CFSTR("Key1");
keys[1] = CFSTR("Key2");
keys[2] = CFSTR("Key3");
values[0] = CFSTR("Value1");
values[1] = CFSTR("Value2");
values[2] = CFSTR("Value3");
aDict = CFDictionaryCreate(NULL, (void *)keys, (void *)values, 3, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFShow(aDict);
CFRelease(aDict);
하나의 배열에 담긴 key들은 다른 배열에 담긴 값들과 순서대로 차례 차례 대응된다. 그러므로, 위의 예제에서는 key를 담고 있는 배열에서의 3번째 요소가 값을 담고 있는 array의 3번째 요소의 값에 접근하는 key가 된다. Dictionary 객체를 만들기 위해서 key와 값 모두를 위해 초기화된 콜백 구조체를 명시해 주어야 하는 점을 주의하자.
변경가능한 콜렉션 객체를 만들기 위해서 필요한 타입에 맞는 CreateMutable 함수를 호출하면 된다. 함수를 호출하면 값을 추가할 수 있는 빈 콜렉션을 생성해 준다.
CFMutableDictionaryRef myDictionary = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(myDictionary, CFSTR("Age"), CFSTR("35"));
변경가능한 객체의 복사본을 만드는데 비슷한 인터페이스가 존재한다. 하지만 이 함수들은 콜백 함수를 지정해줄 필요가 없다. 원본 객체에서 사용되었던 콜백 함수가 복사본에서 그대로 사용되기 때문이다.
/* props is an existing dictionary */ CFMutableArrayRef urls = CFArrayCreateMutableCopy(NULL, 0, (CFArrayRef)CFDictionaryGetValue(props, kCFURLFileDirectoryContents));
변경가능한 콜렉션 객체를 생성하거나 복사하는 함수는 두번째 매개변수로 콜렉션의 용량(capacity)을 지정하는 integer 값을 받는다. 변경가능한 콜렉션을 만들때 capacity 값을 0보다 크게 설정하면 고정 크기가 된다. 위의 예제에서처럼 0으로 설정하면, 가변 길이의 콜렉션이 생성된다. 가변길이 콜렉션은 메모리가 허용하는 한도에서 어떤 갯수의 값도 포함할 수 있다.
[편집] 사용자 정의 콜백 함수 만들기
위의 예제 코드에서는 모두 미리 정의된 콜백함수 구조체를 사용하여 콜렉션을 생성했다. 그러나, 여러분의 콜렉션 객체를 위한 콜백 함수를 직접 정의할 수 있다. 적어도 다음의 두가지 경우에 콜백을 직접 정의하는 것이 필요할 것이다. 한가지는 콜렉션에 들어가는 값이 사용자정의 자료 구조체여서 직접 retain, release, 동일성 검사와 그 밖의 값을 처리하는 방식을 직접 정의해야 하는 경우이다. 또다른 경우는 미리정의된 콜백 구조체를 사용하지만 어떤 부분의 처리 방식을 변경하고 싶을 때다.
"목록 3"은 후자의 경우의 예를 보여주고 있다. 미리정의된 kCFTypeDictionaryValueCallBacks 구조체에 기반하여 사용자 정의 CFDictionaryValueCallBacks 구조체를 정의하고 있다. 그러나 retain과 release 함수 포인터를 NULL로 설정한다. 이 콜렉션에 추가되고 제거되는 값들은 retain 이나 release 되지 않을 것이다. 어떤 형의 자료에 대해서는 이러한 처리 방식이 적합할 수 있다.
목록 3. 미리정의된 콜백 함수를 수정하여 CFDictionary 객체 생성
CFMutableDictionaryRef bundlesByURL;
{CFDictionaryValueCallBacks nonRetainingDictionaryValueCallbacks = kCFTypeDictionaryValueCallBacks;
nonRetainingDictionaryValueCallbacks.retain = NULL;
nonRetainingDictionaryValueCallbacks.release = NULL;
bundlesByURL = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &nonRetainingDictionaryValueCallbacks);
/* assume url and bundle come from somewhere */
CFDictionarySetValue(bundlesByURL, url, bundle);
변경가능한 CFDictionary 객체 생성에 관한 좀 더 자세한 코드 예제가 "목록 4"에 나와있다. key는 integer 형이고, value의 타입은 프로그램에서 정의한 구조체이다, 사용자정의 콜백이 값과 key 모두를 위해 정의되어 있다.
목록 4. value 와 key에 대하여 사용자정의 콜백을 사용한 CFDictionary의 생성
typedef struct {
int someInt;
float someFloat;
} MyStructType;
const void *myStructRetain(CFAllocatorRef allocator, const void *ptr) {
MyStructType *newPtr = (MyStructType *)CFAllocatorAllocate(allocator, sizeof(MyStructType), 0);
newPtr->someInt = ((MyStructType *)ptr)->someInt;
newPtr->someFloat = ((MyStructType *)ptr)->someFloat;
return newPtr;
}
void myStructRelease(CFAllocatorRef allocator, const void *ptr) {
CFAllocatorDeallocate(allocator, (MyStructType *)ptr);
}
Boolean myStructEqual(const void *ptr1, const void *ptr2) {
MyStructType *p1 = (MyStructType *)ptr1;
MyStructType *p2 = (MyStructType *)ptr2;
return (p1->someInt == p2->someInt) && (p1->someFloat == p2->someFloat);
}
CFStringRef myStructCopyDescription(const void *ptr) {
MyStructType *p = (MyStructType *)ptr;
return CFStringCreateWithFormat(NULL, NULL, CFSTR("[%d, %f]"), p->someInt, p->someFloat);
}
Boolean intEqual(const void *ptr1, const void *ptr2) {
return (int)ptr1 == (int)ptr2;
}
CFHashCode intHash(const void *ptr) {
return (CFHashCode)((int)ptr);
}
CFStringRef intCopyDescription(const void *ptr) {
return CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), (int)ptr);
}
void customCallBackDictionaryExample(void) {
CFDictionaryKeyCallBacks intKeyCallBacks = {0, NULL, NULL, intCopyDescription, intEqual, intHash};
CFDictionaryValueCallBacks myStructValueCallBacks = {0, myStructRetain, myStructRelease, myStructCopyDescription, myStructEqual};
MyStructType localStruct;
CFMutableDictionaryRef dict;
CFTypeRef value;
/* Create a mutable dictionary with int keys and custom struct values
** whose ownership is transferred to and from the dictionary. */
dict = CFDictionaryCreateMutable(NULL, 0, &intKeyCallBacks, &myStructValueCallBacks);
/* Put some stuff in the dictionary
** Because the values are copied by our retain function, we just
** set some local struct and pass that in as the value. */
localStruct.someInt = 1000; localStruct.someFloat = -3.14;
CFDictionarySetValue(dict, (void *)42, &localStruct);
localStruct.someInt = -1000; localStruct.someFloat = -3.14;
CFDictionarySetValue(dict, (void *)43, &localStruct);
/* Because the same key is used, this next call ends up replacing the earlier value (which is freed). */
localStruct.someInt = 44; localStruct.someFloat = -3.14;
CFDictionarySetValue(dict, (void *)42, &localStruct);
show(CFSTR("Dictionary: %@"), dict);
value = CFDictionaryGetValue(dict, (void *)43);
if (value) {
MyStructType result = *(MyStructType *)value;
CFStringRef description = myStructCopyDescription(&result);
show(CFSTR("Value for key 43: %@"), description);
CFRelease(description);
}
CFRelease(dict);
}
CFArray, CFDictionary, CFSet, CFBag 콜렉션 타입은 콜백으로 다음의 구조체를 선언한다.
- CFArrayCallBacks
- CFDictionaryKeyCallBacks
- CFDictionaryValueCallBacks
- CFSetCallBacks
- CFBagCallBacks
이 구조체의 함수 포인터 멤버는 어떻게 자료를 처리할 것인지를 구현해 놓은 함수에 대한 포인터와 경고처리를 값으로 받는다. "표 1"에 이 콜백들의 일반적인 특성을 나타내었다. 더 자세한 정보가 필요하면, 콜백 구조체에 대한 레퍼런스 문서를 보길 바란다.
표 1.
| 함수포인터 | 콜렉션 타입 | 콜백 함수 설명 |
|---|---|---|
| retain | 모든 타입 | 콜렉션에 추가되었을때 값을 retain 하기 위해 호출된다. 레퍼런스 카운팅의 특성은 데이터의 형식과 콜렉션의 목적에 따라 다르다. 예를 들면 이것은 레퍼런스 카운트값을 증가시킬 수 있다. 이 함수는 보통 입력받은 값을 리턴하여 콜렉션에 저장할 수 있도록 한다. 하지만 다른 값이 저장되어야 한다면 다른 값을 리턴할 수도 있다. 함수의 포인터로 NULL이 들어갈 수 있다. |
| release | 모든 타입 | 콜렉션에서 값이 삭제될때 호출된다. 이것은 retain 콜백과 반대의 일을 수행한다. 예를 들면 레퍼런스 카운트값을 감소하거나 값에 할당된 메모리를 해제한다. 함수의 포인터로 NULL이 들어갈 수 있다. |
| equal | 모든 타입 | 2개의 값을 비교하는 콜백 함수이다. 콜렉션안의 값의 비교가 필요한 연산에서 호출된다. 콜렉션의 value에 대해서 함수의 포인터 값이 NULL이 될 수 있다. 콜렉션의 key에 대해서는 비교연산이 단순히 포인터 값의 비교가 될경우 NULL이 가능하다. |
| copyDescription | 모든 타입 | 콜렉션의 각 값에 대한 설명(description)을 담은 CFString 객체를 생성하여 리턴한다. 이 콜백함수는 CFCopyDescription과 CFShow 함수에 의해 호출된다. 함수의 포인터로 NULL이 들어갈 수 있다. 이경우 콜렉션은 간단한 설명을 생성해 낸다. |
| hash | CFDictionary keys, CFSet, CFBag | 콜렉션의 값에 접근하고, 추가하고, 삭제하는데 필요한 key의 해쉬 코드를 계산하는데 호출된다. NULL이 입력되면, 기본으로 포인터 주소값이 해쉬 코드로 사용된다. equal과 hash 콜백 함수의 관계에 대해 더 알고싶으면 "Dictionary"를 참고하라. |
[편집] 콜렉션에서 값(value) 가져오기
"콜렉션의 일반적인 특성"에서 다루었듯이 CFArray, CFDictionary, CFSet, CFBag 콜렉션 객체는 저장된 값을 빼오기 위해 key를 사용한다. key의 타입은 콜렉션의 타입에 따라 다르다.
CFArray 객체에서는 key가 index를 나타내는 integer 값이고, 배열에서의 위치를 나타낸다. CFDictionary 객체의 경우 어떤 값도 key로 사용 가능하지만, dictionary의 value와 연관된 상수 데이터이다. "key"는 일반적으로 이 연관된 데이터를 가리키는 용어로 쓰인다. CFSet과 CFBag 객체에서는 key가 value 그 자체이다. 모든 콜렉션 객체는 이름에 "GetValue"를 포함하는 값을 얻어오는 함수를 정의하고 있다. 이 함수들은 적절한 형태의 key를 매개변수로 취한다.
Array의 값에 접근하는 일반적인 것으로 루프를 돌면서 index 값을 하나씩 증가시켜 값을 하나하나 처리하는(iterate) 방법이 있다. 루프 안에서는 현재의 index값을 key로 이용하여 값에 접근하고 필요한 처리를 한다. "목록 1"은 이러한 방법을 보여주고 있다.
목록 1. CFArray 객체에서 값 얻어오기
if (URLs != NULL) { /* URLs is a CFArray object */
CFIndex i, c = CFArrayGetCount(URLs);
CFURLRef curURL;
CFBundleRef curBundle;
for (i=0; i<c; i++) {
curURL = CFArrayGetValueAtIndex(URLs, i);
curBundle = CFBundleCreate(alloc, curURL);
if (curBundle != NULL) {
CFArrayAppendValue(bundles, curBundle);
CFRelease(curBundle);
}
}
CFRelease(URLs);
}
CFDictionary 객체에서 값을 얻어오는데 주로 CFDictionaryGetValue 함수가 사용된다. 이 함수는 값에 대한 key를 입력받는다. "목록 2"에 예제가 나와있다.
목록 2. CFDictionary 객체에서 값 얻어오기
CFStringRef theName = mappingTable ? (CFStringRef)CFDictionaryGetValue(mappingTable, (const void*)encoding) : NULL;
CFSet과 CFBag 객체에서 값을 얻어오기 위해서, 값 그 자체를 key로 사용하면 된다. 이것은 이상하게 보일수도 있으나, 생성된 콜렉션 객체에대해 동일성을 결정하는 콜백 함수를 정의할 수 있다는 점을 기억하자(hash 와 equal). 그러므로 key로 사용되는 value 는 저장된 value와 정확하게 일치하지 않아도 된다.
CFSet, CFBag, CFDictionary는 모두 오직 콜렉션에 값이 있을 경우에만 특정 값을 얻어오는 함수들을 정의하고 있다. 이러한 콜렉션에서는 NULL이 유효한 값이 될 수 있기 때문에, CFTypeGetValueIfPresent 함수를 이용하여 정확하게 포함된 값의 존재를 확인할 수 있다. "목록 3"은 CFSet 객체가 문자열이 유일한지 검사하는 함수에서 CFSetGetValueIfPresent를 사용하는 것을 보여주고 있다.
목록 3. CFSet 을 이용한 유일한(unique) 값 처리
static CFMutableSetRef uniquedStrings = NULL;
CFStringRef uniqueString(CFStringRef string, Boolean addIfAbsent) {
CFStringRef member = NULL;
Boolean present;
if (!string) {
return NULL;
}
if (!uniquedStrings) {
if (addIfAbsent) {
uniquedStrings = CFSetCreateMutable(NULL, 0, & kCFTypeSetCallBacks);
} else {
return NULL;
}
}
present = CFSetGetValueIfPresent(uniquedStrings, string, (void **)&member);
if (!present) {
if (addIfAbsent) {
string = CFStringCreateCopy(NULL, string);
CFSetAddValue(uniquedStrings, string);
CFRelease(string);
}
member = string;
}
return member;
}
CFArray, CFDictionary, CFSet, CFBag 콜렉션 타입은 그 밖의 Get 함수를 포함하고 있다. 콜렉션의 모든 값(그리고 key)을 얻어오는 함수, 콜렉션의 모든 값(또는 key)의 갯수를 얻어오는 함수, 특정 값에 대한 index나 key를 얻어오는 함수들이 있다.
[편집] 콜렉션 검색하기
Core Foundation은 콜렉션 객체에서 값을 찾는 몇가지 프로그래밍 인터페이스를 제공하고 있다. "콜렉션에서 값 가져오기" 에서 설명한 CFTypeGetValueIfPresent 함수는 dictionary, set, bag에서 값의 존재유무를 알려준다. 콜렉션이 어떤 값이나 key를 포함하고 있는지 확인하기 위해 이름에 "Contains"가 포함된 함수를 사용할 수도 있다. "목록 1"은 CFDictionaryContainsKey가 어떻게 사용되는지 보여준다.
목록 1. CFDictionary 객체에서 key를 검색
if (CFDictionaryContainsKey(mappingTable, (const void*)lowerCharsetName)) {
result = (CFStringEncoding)CFDictionaryGetValue(mappingTable, (const void*)lowerCharsetName);
}
CFArray 객체에서 CFArrayBSearchValues 함수는 더 정교한 검색 옵션을 제공한다. 이 함수는 이분 검색(binary search) 알고리즘을 사용하여 정렬된 array에서 특정한 값을 찾는다. 콜렉션에서 값이 없으면, 이 함수는 이 값이 어디로 가야하는지 말해준다. 그래서 CFArrayBSearchValues는 정렬된 변경가능한 array 를 정렬된 상태로 유지하는데 도움을 준다. "목록 2"에서 이 함수가 호출되는 것을 보여준다.
목록 2. CFArray 객체에서 값을 검색
CFIndex position = CFArrayBSearchValues(aMutArray, CFRangeMake(0,CFArrayGetCount(anArray)), (const void *)CFSTR("String Three"), CFStringCompare, 0);
CFArrayBSearchValues의 네번째 매개변수로 CFComparatorFunction 형을 따르는(conform) 함수의 포인터가 와야한다. 이 비교 함수는 array의 값을 어떻게 비교할 것인지 알고 있다. 주어진 예제에서 CFStringCompare가 사용되었다. 이것은 CFComparatorFunction을 따르고(conform) 있으며, 어떻게 CFString 값을 비교할 것인지 알고 있다. CFNumberCompare와 CFDateCompare같은 다른 미리정의된 Core Foundation 비교 함수들이 있다.
위의 호출에 대한 리턴으로, CFIndex 결과는 다음중의 하나를 가리킨다
- 값이 존재한다면, array의 인덱스 값
- 지정된 값이 범위내의 모든 값보다 크다면 범위의 끝 경계와 같거나 큰 index 값
- 지정된 값이 범위내의 두 값 사이에 존재하거나 범위내의 모든 값보다 작다면 지정된 값보다 큰 index 값
CFArrayContainsValue를 사용하여 위에 나열된 경우중에 첫번째 경우인지를 검사할 수 있다.
CFTree형은 포함하는 값(즉 서브트리)을 찾는 별도의 함수들을 정의하고 있다. 더 많은 정보가 필요하다면 "콜렉션의 생성과 복사"를 보기 바란다.
[편집] 변경가능한 콜렉션의 사용
콜렉션 opaque type인 CFArray, CFDictionary, CFSet, CFBag은 변경가능한 콜렉션에 포함된 값을 다루는(값의 추가, 삭제, 치환등) 함수 세트를 서로 비슷하게 제공하고 있다. 콜렉션이 key와 value의 유일성을 보장하는지의 여부에 따라 작동 방식에 약간의 차이가 있다. "표 1"에 변경 속성 함수들의 특성을 요약하였다. '연산' 컬럼은 변경 속성 함수들에서 찾을 수 있는 매개변수를 사용하는 연산의 형태를 나타낸다. 예를 들어 CFDictionaryReplaceValue에서의 "Replace"이다.
표 1. 변경 가능한 콜렉션 연산의 의미
| 연산 | 콜렉션 타입 | 연산의 내용 |
|---|---|---|
| Append | CFArray | 다른 모든 값들의 뒤에 값을 삽입한다. (index == count) |
| Insert | CFArray | 콜렉션의 주어진 index에 값을 삽입한다. |
| Add | CFArray를 제외한 모든 타입 | CFDictionary와 CFSet에서 값이 없으면 추가하고, 있으면 아무것도 하지 않는다. CFBag은 이미 값이 있더라도 추가한다. |
| Replace | 모든 타입 | 주어진 값이 존재한다면, 다른 값으로 치환한다. 그렇지않다면 아무것도 하지 않는다. |
| Set | 모든 타입 | 값이 없으면 추가하고, 있으면 덮어쓴다. |
| Remove | 모든 타입 | 값이 있으면 제거하고, 없으면 아무것도 하지 않는다. |
고정크기의 변경가능한 콜렉션을 사용할때는 값을 추가할때 용량(capacity)의 한도를 초과하지 않는지 신경써야 한다. 고정크기의 콜렉션은 원하는 만큼의 많은 값을 추가할 수 있게 한다, 그러나 한도를 초과했을때 어떤 주의도 주지 않는다. 그러나 그렇게 하면 정의되지 않은 결과를 초래할 수 있다.
CFArray 형의 변경 속성의 연산중에 한가지 특별한게 있다. CFArraySortValues 함수를 이용하여 array에 담긴 값들을 정렬할 수 있다. CFComparatorFunction 형을 따르는(conform) 비교 함수가 값의 비교에 사용된다. "목록 1"은 CFArraySortValues 함수의 사용을 보여준다.
목록 1. array의 정렬
CFMutableArrayRef createSortedArray(CFArrayRef anArray) {
CFIndex count = CFArrayGetCount(anArray);
CFMutableArrayRef marray = CFArrayCreateMutableCopy(NULL, count, anArray);
CFArraySortValues(marray, CFRangeMake(0, count), (CFComparatorFunction)CFStringCompare, NULL);
return marray;
}
CFStringCompare 함수가 사용되는 것에 주목하자, 이 경우에는 CFString 객체의 비교에 사용되고 있다. Core Foundation은 CFComparatorFunction 타입의 다른 비교 함수도 제공하고 있다. 주목말 만한 것으로는 CFDateCompare와 CFNumberCompare가 있다. array가 Core Foundation 객체를 담고 있다면, 미리정의된 비교 함수중 적절한 것을 CFArraySortValues에 넘겨주어 이 객체들을 정렬할 수 있다.
[편집] 프로그램 정의 함수를 콜렉션에 적용하기
콜렉션의 기능중에 특별히 유용한 것으로 프로그램에서 정의한 함수를 콜렉션 객체의 각각의 값에 적용시키는 기능이 있다. 이 applier 함수는 반드시 각각의 콜렉션 타입에서 정의한 원형을 따라야(conform) 한다. 각각의 포함된 값에 대해서 실행시킬 콜렉션 함수(CFTypeApplyFunction의 형태)에서 이 applier 함수에 대한 포인터를 지정해주어야 한다.
"목록 1"은 문자의 갯수를 세는 함수를 CFArray 객체에 저장된 CFString 객체에 적용하는 간단한 예제를 보여준다. 이 함수는 CFArrayApplierFunction 타입이다. 이 타입의 원형은 2개의 매개변수를 갖는다. 첫번째 매개변수는 array안의 값이고(또는 값에 대한 포인터) 두번째 매개변수는 어떤 프로그램 정의된 값(또는 값에대한 포인터)이다.
목록 1. 함수를 array에 적용하기
void countCharacters(const void *val, void *context) {
CFStringRef str = (CFStringRef)val;
CFIndex *cnt = (CFIndex *)context;
CFIndex numchars = CFStringGetLength(str);
*cnt += numchars;
}
void countCharsInArray() {
CFStringRef strs[3];
CFArrayRef anArray;
CFIndex count=0;
strs[0] = CFSTR("String One");
strs[1] = CFSTR("String Two");
strs[2] = CFSTR("String Three");
anArray = CFArrayCreate(NULL, (void *)strs, 3, &kCFTypeArrayCallBacks);
CFArrayApplyFunction(anArray, CFRangeMake(0,CFArrayGetCount(anArray)), countCharacters, &count);
printf("The number of characters in the array is %d", count);
CFRelease(anArray);
}
종종 applier 함수를 사용하여 변경가능한 콜렉션에서 반복해서 값을 처리하여(iterate) 어떤 기준에 맞는 객체를 제거하기도 한다. applier 함수가 반복해서 값을 처리(iterate)하는 동안에 콜렉션을 변경하는 것은 안전하지 못하다. 그러나 콜렉션을 변경하기 위해 applier 함수를 사용하는 안전한 방법이 있다:
- 반복처리 후에 변경을 하라. 적용된 함수를 사용하여 콜렉션에서 어디가 변경이 필요한지를 기록한다, 그리고 난 후에 applier 함수가 실행을 끝난 다음 콜렉션을 변경한다.
- 원본을 변경하라. 변경가능한 콜렉션이라면, 콜렉션의 복사본을 만들어서 applier 함수를 적용하여 반복처리 하고, 원본에 변경할 내용을 적용한다.
어떤식으로 접근하는 것이 더 쉬운지는 상황에 따라 다르다. 원본 콜렉션이 변경불가능하다면, 다음과 같은 응용이 가능하다:
- 복사본을 변경하라. 콜렉션의 변경가능한 복사본을 만들고 applier 함수를 원본 콜렉션에 적용하여 반복 처리한 다음 복사본을 변경한다.
[편집] 트리 구조체의 생성과 사용
CFTree opaque type을 이용하여 tree 구조체를 생성하여 메모리내에 정보를 계층적으로 조직화하여 표현할 수 있다. 이 구조체에서는 각각의 노드(node)가 정확하게 하나의 부모 트리를 갖고(root트리는 제외), 여러개의 자식 트리를 가질 수 있다. 확장된 구조에서 각각의 CFTree 객체는 관련된 내용(context)를 갖는다. 이 context는 데이터를 처리할 콜백과 같은 프로그램 정의 데이터를 포함하고 있다. 프로그램 정의 데이터는 종종 각각의 노드에 관한 정보를 저장하는데 사용된다.
이 절에서는 어떻게 CFTree 객체를 생성하고, 트리 구조체에 포함시키고, 나중에 찾아서 사용하고 수정하는지를 다룬다. 대부분의 CFTree 함수의 첫번째 매개변수는 부모 CFtree 객체에 대한 참조이다. 연산은 보통 그 부모의 하나 이상의 자식을 포함하여 처리된다. 즉, 대부분의 CFTree 루틴은 주어진 부모로부터 계층구조의 "아래로" 내려가면서 처리된다. 그러나 오직 그 부모의 자식에만 영향을 미친다.
CFTree 객체를 사용함에 있어서 한가지 주의해야 할 점은 자식에 대하여 처리하는 함수는 재귀적이지 않다는 것이다. 바로 인접하고 있는 자식 트리에 대해서만 처리를 한다. 어떤 주어진 부모 노드로부터 모든 하위트리에 접근하여(traverse) 처리하길 원한다면, 여러분은 코드에 그러한 처리를 포함하여 한다.
[편집] CFTree 객체의 생성
CFTree 객체를 생성하기 전에, 반드시 context를 정의해야 한다. 이것은 CFTreeContext 구조체를 선언하고 초기화해야 한다는 것을 의미한다. 이 구조체는 다음과 같이 정의되어 있다:
typedef struct {
CFIndex version;
void *info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFTreeContext;
info 멤버는 여러분이 정의한 데이터를 가르킨다. 다른 멤버는 (version 멤버를 제외하고) info 포인터를 매개변수로 받아 관련 데이터에 대한 retain, release, 설명(describing)같은 특정한 처리를 한다.
제대로 CFTreeContext 구조체를 초기화하였다면, 구조체의 포인터를 넘기면서 CFTreeCreate 함수를 호출한다. "목록 1"에 예제가 나와있다.
목록 1. CFTree 객체 생성
static CFTreeRef CreateMyTree(CFAllocatorRef allocator) {
MyTreeInfo *info;
CFTreeContext ctx;
info = CFAllocatorAllocate(allocator, sizeof(MyTreeInfo), 0);
info->address = 0;
info->symbol = NULL;
info->countCurrent = 0;
info->countTotal = 0;
ctx.version = 0;
ctx.info = info;
ctx.retain = AllocTreeInfo;
ctx.release = FreeTreeInfo;
ctx.copyDescription = NULL;
return CFTreeCreate(allocator, &ctx);
}
이 예제에서 처럼 CFTree 객체의 context에 대해 콜백 함수를 정의하고 싶지 않다면 CFTreeContext 구조체의 함수 포인터 멤버를 NULL로 초기화하는 것이 가능하다.
[편집] 부모에 Tree 추가하기
어떤식으로 사용하던지 CFTree 객체는 트리 구조체에 삽입되어야 한다. 이 객체는 다른 CFTree 객체들과 계층적 관계에 놓여야 한다. 이렇게 하기 위해서, 다음의 CFTree 함수를 사용하여 객체를 어떤 다른 트리에 대해 자식(child)이나 형제(sibling) 관계가 되도록 해야 한다.
- CFTreeAppendChild
- CFTreePrependChild
- CFTreeInsertSibling
“목록 2”는 자식 트리가 부모 트리에 추가되는 과정을 보여준다.
목록 2. Adding a child CFTree to its parent
/* assume anAddress and curTree already exist */
CFTreeRef child = FindTreeChildWithAddress(curTree, anAddress);if (NULL == child) {
CFTreeContext ctx;
child = CreateMyTree(CFGetAllocator(curTree));
CFTreeGetContext(child, &ctx);
((MyTreeInfo *)ctx.info)->address = anAddress;
CFTreeAppendChild(curTree, child);
CFRelease(child);
}
이 코드는 CFTree 프로그래밍 인터페이스의 또 다른 측면을 보여주고 있다. 때때로 CFTree 객체와 함께 이미 생성된 연관된 프로그램 정의 데이터를 변경할 필요가 생긴다면, 그 객체의 CFTreeGetContext 함수를 호출하여 tree의 context를 얻어온다. 일단 이 구조체를 가져왔다면, info 포인터를 통해 프로그램 정의 데이터에 접근하는 것이 가능하다.
[편집] 자식 트리 얻어오기
CFTree 형은 자식 트리를 얻어올 수 있는 몇가지 함수를 제공한다. 형제(sibling) 트리는 순차적으로 되어있기 때문에, 일반적으로 CFTreeGetFirstChild와 CFTreeGetNextSibling 두개의 함수만 사용하면 부모의 모든 자식 트리에 접근할 수 있다. "목록 3"에서 어떻게 하는지 보여주고 있다.
목록 3. 부모 트리의 자식 트리에 접근하기
static CFTreeRef FindTreeChildWithAddress(CFTreeRef tree, UInt32 addr) {
CFTreeRef curChild = CFTreeGetFirstChild(tree);
for (; curChild; curChild = CFTreeGetNextSibling(curChild)) {
CFTreeContext ctx;
CFTreeGetContext(tree, &ctx);
if (((MyTreeInfo *)ctx.info)->address == addr) {
return curChild;
}
}
return NULL;
}
모든 CFTree 함수가 자식 트리에 대해 동작하거나 자식 트리를 반환하는 것은 아니다. 예를 들어 CFTreeGetParent 함수는 주어진 트리의 부모 트리를 얻어온다. CFTreeFindRoot 함수는 현재 트리 구조체의 최상위(root) CFTree 객체를 얻어온다.
[편집] CFTree 구조체의 다른 연산들을 이용하기
CFTtree 타입은 다른 콜렉션 함수들과 매우 유사한 두개의 함수를 포함하고 있다. CFTreeApplyFunctionToChildren 함수는 프로그램 정의된 applier 함수를 부모 CFTree 객체의 자식들에 적용한다. CFTreeSortChildren 함수는 CFComparatorFunction 타입을 따르는 정의된 비교 함수를 사용하여 부모 CFTree 객체의 자식들을 정렬한다.
- CFTreeApplyFunctionToChildren의 사용에 도움이 되는 내용은 "프로그램 정의 함수를 콜렉션에 적용하기" 에서 찾아 볼 수 있다.
- CFArraySortValues 함수의 사용에 대한 더 많은 정보가 필요하면 "변경가능한 콜렉션의 사용"을 읽기 바란다. 이 내용의 많은 부분이 CFTreeSortChildren 함수에도 적용된다.
| 번역자 | 사용자:sunil |
| 원본문서링크 | Introduction to Collections (Last Updated - 2003-08-07) |




