ProgrammingInObjectiveC:Chapter 15: Difference between revisions

From 흡혈양파의 번역工房
Jump to navigation Jump to search
(오타수정)
(오타수정)
 
(One intermediate revision by the same user not shown)
Line 359: Line 359:




이런 객체는 '수정 불가능한(immutable)'' 객체라고 부른다 NSString 클래스는 수정할수 없는 객체를 다룬다. 그런데 스트링 내 문자를 바꿔야한다면 어떻게 할까? 예를 들어, 스트링에서 문자 몇 개를 삭제하거나, 검색한 뒤 대치하는 작업을 해야 할 때가 있다. 이런 스트링은 NSMutableString 에서 처리한다.
이런 객체는 '수정 불가능한(immutable)' 객체라고 부른다 NSString 클래스는 수정할수 없는 객체를 다룬다. 그런데 스트링 내 문자를 바꿔야한다면 어떻게 할까? 예를 들어, 스트링에서 문자 몇 개를 삭제하거나, 검색한 뒤 대치하는 작업을 해야 할 때가 있다. 이런 스트링은 NSMutableString 에서 처리한다.


프로그램 15.3 에는 프로그램에서 수정할 수 없는 문자 스트링을 다루는 기본 방법이 나와있다.
프로그램 15.3 에는 프로그램에서 수정할 수 없는 문자 스트링을 다루는 기본 방법이 나와있다.
Line 1,958: Line 1,958:
주소록에 등록된 사람들이 많다면 알파벳 순서대로 정렬하면 편리할 것이다. AddressBook 클래스에 sort 메서드를 추가하고 NSMutableArray 의 메서드인 sortUsingSelector: 를 사용하면 쉽게 정렬할 수 있다. 이 메서드는 자신이 두 원소를 비교할 때 사용하는 셀렉터를 인수로 받는다. 배열에는 데이터 형에 관계없이 모든종류의 객체가 들어갈 수 있다. 그러니 일반 정렬 메서드를 구현하는 유일한 방법은 프로그래머가 배열 내 원소들을 차례대로 놓을지 말지를 정하는 것이다. 이를 위해서, 배열의 두 원소를 비교할 메서드를 추가해야 한다.<ref name="주석12">sortUsingFunction:context: 메서드를 사용하면 메서드 대신 함수로 비교할 수 있다.</ref> 이 메서드가 반환하는 결과는 NSComparisonResult 형이다. 만일, 배열에서 첫째 원소를 둘째 원소보다 앞에 위치하게 하려면 정렬하는 메서드는 NSOrderedAscending 을 반환하고, 두 원소가 동일하다고 여길 때는 NSOrderedSame 을, 그리고 첫째 원소가 둘째 원소보다 뒤에 나와야 하는 경우에는 NSOrderedDecending 을 반환한다.
주소록에 등록된 사람들이 많다면 알파벳 순서대로 정렬하면 편리할 것이다. AddressBook 클래스에 sort 메서드를 추가하고 NSMutableArray 의 메서드인 sortUsingSelector: 를 사용하면 쉽게 정렬할 수 있다. 이 메서드는 자신이 두 원소를 비교할 때 사용하는 셀렉터를 인수로 받는다. 배열에는 데이터 형에 관계없이 모든종류의 객체가 들어갈 수 있다. 그러니 일반 정렬 메서드를 구현하는 유일한 방법은 프로그래머가 배열 내 원소들을 차례대로 놓을지 말지를 정하는 것이다. 이를 위해서, 배열의 두 원소를 비교할 메서드를 추가해야 한다.<ref name="주석12">sortUsingFunction:context: 메서드를 사용하면 메서드 대신 함수로 비교할 수 있다.</ref> 이 메서드가 반환하는 결과는 NSComparisonResult 형이다. 만일, 배열에서 첫째 원소를 둘째 원소보다 앞에 위치하게 하려면 정렬하는 메서드는 NSOrderedAscending 을 반환하고, 두 원소가 동일하다고 여길 때는 NSOrderedSame 을, 그리고 첫째 원소가 둘째 원소보다 뒤에 나와야 하는 경우에는 NSOrderedDecending 을 반환한다.


먼저, AddressBook 클래스의 새 정렬 메서드를보자.
먼저, AddressBook 클래스의 새 정렬 메서드를 보자.


<syntaxhighlight lang="objc">
<syntaxhighlight lang="objc">

Latest revision as of 08:05, 3 August 2013

15장
숫자, 스트링, 컬렉션

15장 :: 숫자, 스트링, 컬렉션

이 장에서는 Foundation 프레임워크가 제공하는 기본 객체를 어떻게 다룰지 설명한다. 객체에는 숫자와 스트링, 컬렉션이 포함되는데, 컬렉션은 배열, 딕셔너리, 세트의 형태로 객체를 묶어서 다룰수 있는 것이다.


Foundation 프레임워크는 많은 클래스, 메서드, 함수를 제공한다. Mac OS X 에서는 헤더파일이 125 개 가까이 제공된다. 간편하게 다음처럼 임포트해도 된다.

#import <Foundation/Foundation.h>


Foundation.h 파일이 다른 Foundation 헤더파일을 거의 모두 임포트하기 때문에, 올바른 헤더파일을 임포트하는지 염려할 필요가 없다. 지금까지 살펴본 각 예제에서 그랬듯이 Xcode 는 자동으로 이 헤더파일을 프로그램에 넣어 준다. 이 명령문을 쓰면 컴파일 시간이 많이 늘어난다. 그러나 '미리 컴파일된' 헤더를 사용하면 이 추가 시간을 없앨 수 있다. 이 파일은 컴파일러가 미리 처리한 파일들이다. 모든 Xcode 프로젝트는 미리 컴파일된 헤더를 사용하도록 기본 설정되어 있다.

이 장에서는 각 객체의 헤더파일을 사용한다. 이를 통해 각 헤더파일에 포함된것과 더 친숙해질 것이다.


objc2_notice_01
원한다면 계속 Foundation.h 파일만 임포트해도 된다. 만일 각 예제에서 나오는 각 파일을 임포트하려면, 새 Foundation Tool 프로젝트를 생성할 때 Xcode 가 자동으로 생성하는 project_name_Prefix.pch 파일을 삭제해야 한다. 프로젝트에서 이 파일을 삭제할 때 Xcode가 띄우는 창에서 'Delete References'도 선택하자.


숫자객체

지금까지 다룬 정수, 부동소수점 수, long 같은 모든 숫자 형은 Objective-C 언어의 기본 데이터 형이었다. 다시 말해, 이것들은 객체가아니었다. 그러므로 이것에 메시지 같은 것을 보내는 일은 불가능하다. 그런데 이런 형 값을 객체로 다뤄야 할 때가 있다. 예를 들어, Foundation 객체인 NSArray 는 값을 저장할 수 있는 배열을 생성하도록 해준다. 이 값들은 객체여야 하기 때문에 이 배열들에 기본 데이터 형을 바로 저장할 수는 없다. 그 대신, 기본 숫자 데이터 형을 저장하려면(char 데이터 형도 포함) NSNumber 클래스를 사용하여 이 데이터 형들에게서 객체를 생성할 수 있다(프로그램 15.1을 보라).


프로그램 15.1


// 숫자 작업하기

#import <Foundation/NSObject.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSValue.h>

#import <Foundation/NSString.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSNumber *myNumber, *floatNumber, *intNumber;
    NSInteger myInt;

    // integer형 값

    intNumber = [NSNumber numberWithInteger: 100];
    myInt = [intNumber integerValue];
    NSLog (@"%li", (long) myInt);

    // long형 값

    myNumber = [NSNumber numberWithLong: 0xabcdef];
    NSLog (@"%lx", [myNumber longValue]);

    // char형 값

    myNumber = [NSNumber numberWithChar: 'X'];
    NSLog (@"%c", [myNumber charValue]);

    //float형 값

    floatNumber = [NSNumber numberWithFloat: 100.00];
    NSLog (@"%g", [floatNumber floatValue]);

    // double형 값

    myNumber = [NSNumber numberWithDouble: 12345e+15];
    NSLog (@"%lg", [myNumber doubleValue]);

    // 잘못된 접근

    NSLog (@"%i", [myNumber integerValue]);

    // 두 숫자가 같은지 비교

    if ([intNumber isEqualToNumber: floatNumber] == YES)
        NSLog (@"Numbers are equal");
    else
        NSLog (@"Numbers are not equal");

    // 두 숫자의 크기 비교

    if ([intNumber compre: myNumber] == NSOrderedAscending)
        NSLog (@"First number is less than second");

    [pool drain];
    return 0;
}

프로그램 15.1 의 출력결과


100
abcdef
x
100
1.2345e+19
0
Numbers are equal
First number is less than second


NSNumber 클래스의 객체를다루려면 Foundation/NSValue.h 파일이 필요하다.


오토릴리스 풀 훝어보기

프로그램 15.1 의 첫째 줄은 모든 프로그램 예제에서 등장했다. 다음은 pool 에 할당할 오토릴리스 풀 이 차지할 메모리 공간을 예약해 주는 코드다.

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];


오토릴리스 풀은 이 풀에 추가된 객체가 쓰는 메모리를 자동드로 릴리스 하기 위해 제공된다. 객체가 autorelease 메시지를 받으면 오토릴리스 풀에 추가된다. 이 풀이 릴리스되면 그 안에든 객체도 모두 릴리스된다. 따라서, ('레퍼런스카운트' 로 지시하여)오토릴리스 풀 범위의 바깥에 있도록 지정되지 않는한, 이 객체들은 모두 파괴된다.

일반적으로, Foundation 메서드가 반환하는 객체를 릴리스하는 것은 크게 염려하지 않아도된다. 때때로 객체를 반환하는 메서드가 그 객체를 소유하기도 한다. 혹은 메서드가 객체를 새로 생성하고 오토릴리스 풀에 추가하기도 한다. 단, 1부 「Objective-C 2.0」 에서 상세히 설명했던 것처럼, alloc 으로 직접 생성한 객체들은 다 사용하고 나서 릴리스 해줄 책임이 있다.


objc2_notice_01
17장에서 배울 copy 메서드로 생성한 객체도 릴리스해 주어야 한다.


17장 「메모리 관리」에서 레퍼런스 키운트와 오토릴리스 풀 그리고 '가비지 컬렉션'에 대해 상세히 다룰 것이다.

프로그램 15.1 로 돌아가자. NSNumber 클래스에는 초기값이 있는 NSNumber 객체를 생성하는 메서드가 많다.다음 예를 보자.

intNumber = [NSNumber numberWithInteger: 100];


이 코드는 값이 100 인 정수에게서 객체를 생성한다.

NSNumber 객체에서 받아오는 값은 그 안에 저장된 값과 데이터 형이 일치해야 한다. 자, 프로그램에서 다음 메시지 표현식 을보자.

myInt = [intNumber integerValue] ;


이는 intNumber 에 저장된 정수 값을 받아 NSInteger 형 변수 myInt 에 저장한다는 의미다. 여기서 NSInteger 는 객체가 아니고 기본 데이터 형의 typedef 다. 64비트 빌드의 경우 long 형의 typedef 이고, 32비트 빌드이면 int 형의 typedef 다. unsigned integer 를 다룰 때도 비슷한 typedef 인 NSUInteger 가 존재한다.

NSLog 호출에서 NSInteger myInt 를 long 형으로 형 변환하고, 포맷 캐릭터 %li 를 사용하여 32비트 아키텍처에서 컴파일되더라도 값이 잘 건네지고 표시되도록 하였다.

각 기본 값에 대해서는 NSNumber 객체를 생성하고 지정한 값으로 설정하는 클래스 메서드가 존재한다. 이 메서드들은 numberWithLong: 과 numberWithFloat: 처럼 numberWith 로 시작하고, 그 뒤에 데이터 형이 붙는다. 게다가 인스턴스 메서드를 사용하여 이미 생성해 놓은 NSNumber 객체의 값을 원하는 대로 지정할 수 있다. 인스턴스 메서드들은 initWithLong: 과 initWithFloat: 처럼 모두 initWith 로 시작한다.

표 15.1은 NSNumber 객체의 값을설정하는 클래스와 인스턴스 메서드, 그에 해당하는 값을 받는 인스턴스 메서드의 목록이다.

생성 및 초기화 클래스 메서드 초기화 인스턴스 메서드 값을 받는 인스턴스 메서드
numberWithChar: initWithChar: charValue
numberWithUnsignedChar: initWithUnsignedChar: unsignedCharValue
numberWithShort: initWithShort: shortValue
numberWithUnsignedShort: initWithUnsignedShort: unsignedShortValue
numberWithInteger: initWithInteger: integerValue
numberWithUnsignedInteger: initWithUnsignedInteger: unsignedIntegerValue
numberWithInt: initWithInt: intValue
numberWithUnsignedInt: initWithUnsignedInt: unsignedIntValue
numberWithLong: initWithLong: longValue
numberWithUnsignedLong: initWithUnsignedLong: unsignedLongValue
numberWithLongLong: initWithLongLong: longlongValue
numberWithUnsignedLongLong: initWithUnsignedLongLong: unsignedLongLongValue
numberWithFloat: initWithFloat: floatValue
numberWithDouble: initWithDouble: doubleValue
numberWithBool: initWithBool: boolValue
표 15.1 NSNumber 생성과 값을 받는 메서드


프로그램 15.1로 돌아가자. 프로그램은 이제 클래스 메서드를 사용하여 long, char, float, double 형 NSNumber 객체를 생성한다. 다음 코드로 double형 객체를 생성한다.

myNumber = [NSNumber numberWithDouble: 12345e+15];


다음 코드로 값을(틀리게) 받아내기를 시도한다.

NSLog (@"%i" , [myNumber integerValue]);


그러면 다음과 같은 결과가 출력된다.

 0


게다가 시스템은 아무런 오류 메시지도 생성하지 않는다. 일반적으로 NSNumber 객체에 값을 저장하고 나서 값을 받을 때 일치하는 형으로 받는 것은 여러분에게 달려있다.

if 문 안에서 다음 메시지 표현식을 적어 넣는다.

[intNumber isEqualToNumber: floatNumber]


이 메시지 표현식은 isEqualToNumber: 를 사용하여 NSNumber 객체 두 개에 담긴 숫자를 비교한다. 프로그램은 여기서 반환되는 불리언 값을 보아 두 값이 같은지 확인한다.

compare: 메서드를 사용하여 한 숫자값이 다른 숫자값보다 작은지, 큰지, 아니면 같은지를 테스트할 수 있다.

[intNumber compare: myNumber]


이 메시지 표현식은 intNumber 에 저장된 숫자 값이 myNumber 에 저장된 숫자값보다 작다면 NSOrderedAscending 값을 반환하고, 두 숫자가 같을 때에는 NSOrderedSame 값을 반환하고, 첫 번째 숫자가 두 번째 숫자보다 크다면 NSOrderedDescending 을 반환한다. 반환되는 값들은 NSObject.h 헤더파일에 정의되어 있다.


앞에서 생성한 NSNumber 객체의 값은 다시 초기화할 수 없으니, 이에 주의하자. 예를 들어, 다음 명령문으로 NSNumber 객체인 myNumber 에 저장된 정수 값을 설정해줄 수는 없다.

[myNumber initWithInt: 1000];


프로그램이 실행되면 이 명령문은 오류를 발생시킨다. 모든 숫자 객체는 새로 생성되어야만 한다. 이 말은, 표 15.1 의 첫째 열에 나열된 NSNumber 클래스 메서드중 하나를 호출하거나 alloc 의 결과에서 둘째 열에 나열된 메서드중 하나를 호출해야만 한다는 것을 의미한다.

myNumber = [[NSNumber alloc] initWithInt: 1000];


물론 이전에 논의했던 대로 myNumber 를 이런 식으로 생성하면 이 객체를 다 사용하고 나서 다음 명령문으로 릴리스해 줘야 한다.

[myNumber release];


앞으로 이 장에서 NSNumber 객체를 다시 만나게 될 것이다.


스트링 객체

스트링 객체는 이미 이전에 만났다. Objective-C 에서는 다음과 같이 문자 스트링을 따옴표로 묶으면 문자스트링 객체를 생성하게 된다.

@"Programming is fun"


Foundation 프레임워크에서 제공하는 NSString 이라는 클래스를 이용하면 문자 스트링 객체를 다룰 수 있다. C 스타일 스트링은 char 문자로 구성되어 있는 반면, NSString 객체는 unichar 문자로 이루어져 었다 unichar 문자는 유니코드 표준에 따른 멀티바이트 문자다.이것을 사용하여 문자를 수백만개 담는 문자세트를 다룰 수도 있다. 다행스럽게도 NSString 클래스가 문자의 내부 표현을 자동드로 처리해 주기 때문에 이를 고민할 필요가 없다.[1] 이 클래스가 보유한 메서드를 사용하면 '지역화되는(1ocalized) ' 응용프로그램을더 손쉽게 개발할수있다. 지역화란, 응용프로그램이 전 세계의 다양한 언어로 동작하도록 만드는 작업이다.

이미 알다시피, Objective-C 에서는 따옴표로 묶인 문자 스트링 앞에 @ 을 붙여서 문자 스트링 상수 객체를 생성할 수 있다. 따라서 다음 표현식은 문자 스트링 상수 객체를 생성한다.

@"Programming is fun"


특히, 유의할 점은 이 문자 스트링 상수 객체가 NSConstantString 클래스에 속한다는 것이다. NSConstantString 클래스는 스트링 객체 클래스인 NSString 의 서브클래스다. 스트링 객체를 프로그램에서 사용하려면 다음코드를 추가해 준다.

#import <Foundation/NSString.h>


NSLog 함수에 대하여

프로그램 15.2는 NSString 객체를 정의하고 초기값을 설정하는 방법을 보여 준다. 또한, 포맷 문자 %@ 를 사용하여 NSString 객체를 표시하는 방법도 알려 준다.


프로그램 15.2


#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSString *str = @"Programming is fun";

    NSLog (@"%@", str);

    [pool drain];
    return 0;
}

프로그램 15.2 의 출력결과


Programming is fun


다음 코드를 작성해서 스트링 상수 객체인 "Programming is fun"을 NSString 변수인 str 에 대입한다.

NSString *str = @"Programming is fun";


그다음 NSLog 를사용하여 이 변수의 값을 표시한다.

NSLog 포맷 문자인 %@ @는 NSString 객체뿐 아니라 다른 객체를 표시하는 데도 사용할 수 있다. 예를들어 다음과 같은 코드가 있다고하자.

NSNumber *intNumber = [NSNumber numberWithInteger: 100];


다음과 같이 NSLog 를 호출한다.

NSLog (@"@%", intNumber);


다음과 같은 결과가 출력된다.

100


심지어 %@ 포맷 문자를 사용하여 배열, 딕셔너리, 세트에 담긴 전체 내용을 표시할 수 있다. 사실, 직접 만든 클래스에서도 상속받은 description 메서드를 재정의 해주기만 하면 이 포맷 문자로 객체를 표시할 수 있다. 만일 이 메서드를 재정의하지 않으면 NSLog 는 클래스 이름과 객체의 메모리 주소를 표시한다(NSObject 클래스에서 상속받은 description 메서드는 기본적으로 이렇게 구현되어 있다).


수정 가능한 객체와 수정 불가능한 객체

다음과 같은 표현식으로 내용을 바꿀 수 없는 객체가 생성된다.

@"Programming is fun"


이런 객체는 '수정 불가능한(immutable)' 객체라고 부른다 NSString 클래스는 수정할수 없는 객체를 다룬다. 그런데 스트링 내 문자를 바꿔야한다면 어떻게 할까? 예를 들어, 스트링에서 문자 몇 개를 삭제하거나, 검색한 뒤 대치하는 작업을 해야 할 때가 있다. 이런 스트링은 NSMutableString 에서 처리한다.

프로그램 15.3 에는 프로그램에서 수정할 수 없는 문자 스트링을 다루는 기본 방법이 나와있다.


프로그램 15.3


// 기본 스트링 작업

#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString *str1 = @"This is string A";
    NSString *str2 = @"This is string B";
    NSString *res;
    NSComparisonResult compareResult;

    // 문자 개수 세기

    NSLog (@"Length of str1: %lu", [str1 length]);

    // 스트링 복사하기

    res = [NSString stringWithString: str1];
    NSLog (@"copy: %@", res);

    // 스트링을 다른 스트링 뒤에 붙여 복사하기

    str2 = [str1 stringByAppendingString: str2];
    NSLog (@"Concatentation: %@", str2);

    // 두 개의 스트링이 같은지 비교

    if ([str1 isEqualToString: res] == YES)
        NSLog (@"str1 == res");
    else
        NSLog (@"str1 != res");

    // 두 개의 스트링 크기 비교

    compareResult = (str1 compare: str2];

    if (compareResult == NSOrderedAscending)
        NSLog (@"str1 < str2");
    else if (compareResult == NSOrderedSame)
        NSLog (@"str1 == str2");
    else // NSOrderedDescending
        NSLog (@"str1 > str2");

    // 대문자로 변환

    res = [str1 uppercaseString];
    NSLog (@"Uppercase conversion: %s", [res UTF8String]);

    // 소문자로 변환

    res = [str1 lowercaseString];
    NSLog (@"Lowercase conversion: %@", res);
    NSLog (@"Original string: %@", str1);

    [pool drain];
    return 0;
}

프로그램 15.3 의 출력결과


Length of str1: 16
Copy: This is string A
Concatentation: This is string AThis is string B
str1 == res
str1 < str2
Uppercase conversion: THIS IS STRING A
Lowercase conversion: this is string a
Original string: This is string A


프로그램 15.3 은 먼저 수정 불가능한 NSString 객체 세 개 (str1, str2, res)를 선언한다. 처음에 나오는 객체 두 개는 문자 스트링 상수 객체로 초기화된다. 이는 다음과 같이 선언한다.

NSComparisonResult compareResult;


이렇게 compareResult를 선언하여 프로그램에서 이후에 스트링을 비교한 결과를 담는다.

length 메서드를 사용하여 스트링에 들어있는 문자 개수를 셀 수 있다. 이 메서드는 NSUInteger 형의 unsigned 정수값을 반환한다. 출력 결과에서 다음 스트링에 문자가 16 개 있음을 확인할 수 있다.

@"This is string A"


다음 명령문은 다른 스트링의 내용물로 새로운 문자 스트링을 생성한다.

res = [NSString stringWithString: str1];


이렇게 생성된 NSString 객체는 res 에 대입되고 결과를 확인하고자 화면에 표시된다. 스트링 내용은 메모리상의 동일한 스트링을 향한 다른 참조를 만들 때가 아니라, 바로 이때 복사된다. 즉, str1 과 res 는 다른 스트링 객체 두 개를 참조하고 있기 때문에 다음처럼 간단하게 대입 할때와는 다르다.

res = str1;


이 코드는 그저 메모리상의 동일한 객체에 대한 다른 참조를 생성할 뿐이다.

stringByAppendingString: 메서드로 문자 스트링 두 개를 합칠 수 있다. 다음 표현식을 보자.

[str1 stringByAppendingString: str2]


str1 의 문자에 str2 의 문자를 이어서 구성한 스트링 객체를 새로 생성하고 그 객체를 반환한다. 원본 스트링 객체인 str1과 str2는 이 작업에서 영향을 받지 않는다(이것들은 수정 불가능한 스트링 객체이기 때문에 영향을 받을 수도 없다).

그 다음에는 isEqualToString: 메서드를 사용하여 두 문자 스트링이 동일한지 테스트한다. 동일하다는 의미는 두 스트링이 똑같은 문자를 갖고 있다는 얘기다. compare: 메서드를 사용해서 두 문자 스트링의 순서를 정할 수도 있다. 이 메서드는 예를 들어, 스트링 배열을 정렬하고 싶을 때 사용한다. 비교 결과는 앞서 두 NSNumber 객체를 비교하는 데 사용했던 compare: 와 마찬가지로 나온다. 비교한 결과, 첫 번째 스트링이 어휘순에서 앞선다면 NSOrderedAscending 이 될 것이고, 두 스트링이 같다면 NSOrderedSame 이 되고, 첫째 스트링이 둘째 스트링보다 어휘순으로 뒤라면 NSOrderedDecending 이 된다. 대소문자를 구별하지 않고 두 스트링을 비교하고 싶다면 compare: 대신 caseInsensitiveCompare: 를 사용한다. 이때 스트링 객체 @"Gregory" 와 @"gregory" 는 caseInsensitiveCompare: 으로 비교하면 동일한 스트링으로 간주된다.

프로그램 15.3 에서 마지막으로 사용했던 NSString 의 메서드 uppercaseString 과 lowercaseString 은 각각 문자를 대문자로 만들고 소문자로 만든다. 다시 말하지만, 출력결과의 마지막에서 확인할 수 있듯이, 원본 스트링에는 이 변환작업이 영향을 끼치지 않는다.

프로그램 15.4 는 스트링을 다루는 추가 메서드들을 설명한다. 이 메서드들을 사용하여 스트링에서 일부 스트링을 뽑아낼 수 있고, 특정 스트링이 다른 스트링에서 나타나는지 검색할 수도 있다.

어떤 메서드는 범위를 지정하여 스트링의 일부를 표시해 주어야 한다. 범위는 시작 지점의 인덱스 번호와 문자수로 구성된다. 스트링의 첫 세 글자를 지정하려면 {0, 3} 으로 표현할 수 있다. 인덱스 번호는 0 에서 시작한다. NSString 클래스의 일부 메서드는 (다른 Foundation 클래스들도) NSRange 데이터 형을 사용해서 범위를 나타낸다. NSRange 는 Foundation/NSRange.h 파일에 정의되어 있는데 (Foundation/NSString.h 에 포함된다), NSUInteger 형으로 정의된 location, length 라는 두 멤버를 갖는 구조체를 typege 로 정의한 것이다.

프로그램 15.4는 이 데이터 형을 사용한다.


objc2_notice_01
13장 「하부 C 언어 기능」에서 구조체에 대해 설명한다. 그러나 이 장에서 설명하는 내용만으로도 구조체를 다루기에 충분한 정보를 얻을수 있다.



프로그램 15.4


// 기본 스트링 작업 - 계속

#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString *str1 = @"This is string A";
    NSString *str2 = @"This is string B";
    NSString *res;
    NSRange subRange;

    // 스트링에서 처음부터 세 번째까지 문자 추출하기

    res = [str1 substringToIndex: 3];
    NSLog (@"First 3 chars of str1: %@", res);

    // 인덱스 5부터 끝까지 문자 추출하기

    res = [str1 substringFromIndex: 5];
    NSLog (@"Chars from index 5 of str1: %@", res);

    // 인덱스 8부터 13까지 (6개 문자) 추출하기

    res = [[str1 substringFromIndex:8] substringToIndex: 6];
    NSLog (@"Chars from index 8 through 13: %@", res);

    // 동일한 작업을 쉽게 하는 방법

    res = [str1 substringWithRange: NSMakeRange (8, 6)];
    NSLog (@"Chars from index 8 through 13: %@", res);

    // 스트링 안에 스트링 찾기

    subRange = [str1 rangeOfString: @"string A"];
    NSLog (@"String is at index %lu, length is %lu",
            subRange.location, subRange.length);

    subRange = [str1 rangeOfString: @"string B"];

    if (subRange.location == NSNotFound)
        NSLog (@"String not found");
    else
        NSLog (@"String is at index %lu, length is %lu",
            subRange.location, subRange.length);

    [pool drain];
    return 0;
}

프로그램 15.4 의 출력결과


First 3 chars of str1: Thi
Chars from index 5 of str1: is string A
Chars from index 8 through 13: string
Chars from index 8 through 13: string
String is at index 8, length is 8
String not found


substringToIndex: 메서드는 지정한 인덱스 번호 바로 앞까지 있는 스트링을 추출해 새로 생성한다. 인덱스가 0 에서 시작하기 때문에 인수를 3 으로 넘기면 스트링에서 문자 0, 1, 2 번을 추출하고, 결과 스트링을 반환한다. 인덱스 번호를 인수로 받는 스트링 메서드에 유효하지 않은 번호를 넘기 경우 "range or index out of bounds." 이라는 오류 메시지가 발생한다.

substringFromIndex: 메서드는 지정한 인덱스 위치부터 스트링의 끝까지에 있는 스트링을 추출하여 반환한다.

res = [[str1 substringFromIndex: 8] substringToIndex: 6];


이 표현식은 메서드 두 개를 결합하여 스트링의 문자 일부를 추출하는 방법을 보여 준다. 먼저 substringFromIndex: 를 사용해 인덱스 8 번부터 스트링의 끝까지 문자를 추출한다. 그 다음에 substringToIndex: 를 사용하여 처음 여섯 문자를 추출한다. 여기까지 하면 원래 스트링의 {8, 6} 범위에 있는 스트링이 추출된다.

substringWithRange: 메서드는 이 두 단계의 작업을 한 번에 해결해 준다. 이 메서드는 범위를 받아 지정 범위 내 문지를 담아 스트링으로 반환한다. 자, 이 특별한 함수를 보자.

NSMakeRange (8, 6)


위와 같은 인수로 범위를 생성하고 결과를 반환한다. 이 결과는 substringWithRange: 메서드의 인수로 주어진다.

또 다른 스트링에서 스트링 하나의 위치를 찾으려면 rangeOfString: 메서드를 쓴다. 만일 지정한 스트링이 수신자 안에서 발견되면 그 스트링이 수신자에서 정확히 어느 위치에 있는지 나타내는 범위가 반환된다. 그러나 스트링이 발견되지 않는다면 반환되는 범위의 location 멤버는 NSNotFound 로 설정된다.

그러면 다음 명령문은 어떤 결과를 낼까?

subRange = [str1 rangeOfString: @"string A"];


메서드가 반환하는 NSRange 구조체를 NSRange 변수인 subRange 에 저장한다. subRange 는 객체 변수가 아니라 구조체 변수라는 점에 주의하자(프로그램에서 subRange 를 선언할 때 별표를 사용하지 않는다). 이 구조체의 멤버는 구조체 멤버 연산자인 .(dot)을 사용하여 받을 수 있다. 따라서 subRange.location 은 구조체의 멤버 location 의 값을 건네주고 subRange.length 는 length 멤버를 건네준다. 이 값들은 NSLog 함수에 전해진 후 화면에 표시된다.


수정 가능한 스트링

NSMutableString 을 써서 문자를 바꿀수 있는 스트링 객체도 만들 수 있다. 이 클래스는 NSString 의 서브클래스이기 때문에 NSString 이 지닌 메서드를 모두 사용할 수 있다.

수정 가능한 스트링 객체와 수정 불가능한 스트링 객체에 대해 이야기할 때에는, 사실 스트링에 포함된 실제 문자를 바꾸는 작업을 말하는 것이다. 프로그램이 실행되는 동안에 수정 가능한 스트링 객체나 불가능한 스트링 객체 모두, 언제나 다른스트링 객체로 설정할 수는 있다. 다음 예를 살펴보자.

str1 = @"This is a string";
...
str1 = [str1 substringFromIndex: 5);


이때, str1 은 먼저 문자스트링 싱수 객체로 설정된다. 그리고 프로그램의 뒷부분에서 다시 str1 이 다른 스트링의 일부로 설정된다. 이런 경우, str1 은 수정 가능한 스트링 객체이거나 수정 불가능한 스트링 객체일 수 있다. 이 점을 반드시 이해하자.

프로그램 15.5 는 프로그램에서 수정 가능한 스트링을 다루는 방법을 보여 준다.


프로그램 15.5


// 기본 스트링 작업 - 수정가능한 스트링

#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSString *str1 = @"This is string A";
    NSString *search, *replace;
    NSMutableString *mstr;
    NSRange substr;

    // 수정 불가능한 스트링으로부터 수정 가능한 스트링 만들기

    mstr = [NSMutableString stringWithString: str1];
    NSLog (@"%@", mstr);

    // 문자 집어넣기

    [mstr insertString: @"mutable" atIndex: 7];
    NSLog (@"%@", mstr);

    // 맨 뒤에 넣는 경우 병합하기

    [mstr insertString: @"and string B"atIndex: [mstr length]];
    NSLog (@"%@", mstr);

    // 혹은 appendString을 직접 사용

    [mstr appendString: @"and string C"];
    NSLog (@"%@", mstr);

    // 범위로 주어진 서브스트링 지우기

    [mstr deleteCharactersInRange: NSMakeRange (16, 13)];
    NSLog (@"%@", mstr);

    // 스트링의 범위 찾아 삭제하기

    substr = [mstr rangeOfString: @"string B and "];

    if (substr.location != NSNotFound) {
        [mstr deleteCharactersInRange: substr];
        NSLog (@"%@", mstr);
    }

    // 수정 가능한 스트링 직접 설정하기

    [mstr setString: @"This is string A"];
    NSLog (@"%@", mstr);

    // 특정 범위를 다른 스트링으로 대치하기

    [mstr replaceCharactersInRange: NSMakeRange(8, 8)
                    withString: @"a mutable string"];
    NSLog (@"%@", mstr);

    // 검색과 대치

    search = @"This is";
    replace = @"An example of";

    substr = [mstr rangeOfString: search];

    if (substr.location != NSNotFound) {
        [mstr replaceCharactersInRange: substr
                        withString: replace];
        NSLog (@"%@", mstr);
    }

    // 모두 찾아 대치하기

    search = @"a";
    replace = @"X";

    substr = [mstr rangeOfString: search];

    while (substr.location != NSNotFound) {
        [mstr replaceCharactersInRange: substr
                        withString: replace];
        substr = [mstr rangeOfString: search];
    }

    NSLog (@"%@", mstr);

    [pool drain];
    return 0;
}

프로그램 15.5 의 출력결과


This is string A
This is mutable string A
This is mutable string A and string B
This is mutable string A and string B and string C
This is mutable string B and string C
This is mutable string C
This is string A
This is a mutable string
An example of a mutable string
An exXmple of X mutXble string


다음 명령문은 실행 도중에 내용을 바꿀 수 있는 문자 스트링 객체를 담는 변수 mstr 을 선언한다.

NSMutableString *mstr;


다음 명령문은 mstr 을 str1 에 있는 문자의 복사본인 "This is string A' 가 담긴 스트링 객체로 설정한다.

mstr = [NSMutableString stringwithString: str1];


stringWithStrng: 메서드가 NSMutableString 클래스에 보내지면, 수정 가능한 스트링 객체가 반환된다. 반면 프로그램 15.3 처럼 NSString 객체에 보내면 수정 불가능한 객체가 반환된다.

insertString:atIndex: 메서드는 수신자에서 지정된 인덱스 번호에 문자 스트링을 삽입한다. 여기서는, @"mutable"을 인덱스 7 번에 (혹은 8 번째 문자 앞에) 삽입한다. 수정 불가능한 스트링 객체 메서드와 달리 수신자자체가 수정되어 값이 따로 반환되지 않는다. 수신자가 수정 가능한 스트링 객체이기 때문이다.

두 번째로 호출된 insertString:atIndex: 메서드는 length 메서드를 사용하여 한 문자 스트링을 다른 스트링 뒤에 삽입한다. appendString: 메서드는 이 작업을 좀더 쉽게 수행한다.

deleteCharactersInRange: 메서드를 사용하여 지정된 번호의 문자를 스트링에서 삭제할 수 있다. 다음 스트링에 범위 {16, 13} 이 적용되면,

This is mutable string A and string B and string C


인덱스 16 번(혹은 17 번째 문자}부터 13 개짜리 문자인 'string A and"를 삭제한다.

그림 15.1은 이것을설명한다.

This is mutable string B and string C
그림 15.1 스트링의 인덱스


프로그램 15.5 에서 그 다음 줄을 보면 rangeOfString: 메서드를 사용하여 스트링을 찾고 삭제하는 방법이 나온다. 먼저 스트링 @"string B and" 가 mstr 에 있는지 확인한 뒤, deleteCharactersInRange: 메서드에 nameOfString: 이 반환하는 범위를 사용하여 문자를 지운다.

setString: 메서드를 사용하여 수정 가능한 스트링 객체의 내용을 직접 설정해 줄 수 있다. 이 메서드를 적용하여 mstr 객체를 @"This is string A' 로 설정한 다음, replaceCharactersInRange: 메서드로 스트링 일부를 다른 스트링으로 대치한다. 이때 스트링 크기가 동일하지 않아도 괜찮다. 대치될 스트링과 바꿀 스트링은 크기가 같아도 되고 달라도 된다.

[mstr replaceCharactersInRange: NSMakeRange(8, 8)
                withString: @"a mutable string"];


이 명령문은 8 글자인 "string A" 를 16 글자인 "a mutable string" 으로 대치한다.

프로그램 15.5 에서 남은 명령줄들은 검색하고 대치하는 방법을 보여 준다. 먼저 @"This is mutable string" 을 담고 있는 mstr 에서 @"This is"를 찾아낸다. 검색 스트링에서 이 스트링을 찾아서 @"An example of" 로 대치한다. 최종 결과를 보면 mstr 이 @"An example of a mutable string" 으로 바뀌어 있다.

이제 검색된 결과를 모두 바꾸는 기능을 구현한다. 검색 스트링은 @"a" 로 설정되고 대치될 스트링은 @"X" 로 설정되었다. 스트링을 바꿀 때 주의해야 할 점이 두 가지 있다. 먼저, 대체될 스트링에 검색된 스트링이 포함되어 있다면(예를 들어, "a"를 "aX"로 대체하는 경우) 반복문은 종료되지 않고 무한 반복될 것이다.

또한, 대체할 스트링이 비어 있다면, 검색된 스트링이 모두 삭제될 것이다. 빈 문자 스트링 상수 객체는 따옴표 두개를 바로 붙여서 작성해 주면된다.

replace = @"";


물론, 특정 스트링이 나을 때마다 삭제하고 싶다면 앞서 살펴본 deleteCharactersInRange: 메서드를 사용해도 된다.

마지막으로 NSMutableString 클래스는 replaceOccurrencesOfString:withString:options:range: 메서드를 포함하는데, 이 메서드를 사용하면 스트링에서 검색된 결과를 모두 바꿀 수 있다. 사실, 프로그램 15.5에 나오는 while 문은 다음과 같은 명령문 하나로 대체할 수 있다.

[mstr replaceOccurrencesOfString: search
            withString: replace
            options: nil
            range: NSMakeRange (0, [mstr length])];


이 명령문을 사용하면 동일한 결과가 나오는 것은 물론 메서드 자체가 무한 반복되는 걸 방지해 주니 훨씬 안전하다.


이 객체들은 다 어디로 가는 걸까?

프로그램 15.4 와 프로그램 15.5 에서는 NSString 과 NSMutableString 의 메서드가 생성하고 반환하는 많은 스트링 객체를 다룬다. 이 장 초반에 이야기했듯이,이 객체들이 사용하는 메모리를 여러분이 직접 릴리스해 줄 필요는 없다. 객체의 생성자가 메모리를 릴리스해야 한다. 아마 생성자들은 이 객체들을 모두 오토릴리스 풀에 추가했을 것이고, 이 풀이 릴리스될 때 객체들도 메모리에서 해제될 것이다. 그러나 혹시 임시 객체를 많이 생성하는 프로그램을 개발하고 있다면, 이 객체들이 사용하는 메모리가 축적될 수 있다. 이럴 때는 프로그램 종료때 만이 아니라 프로그램이 실행되는 동안에는 메모리가 릴리스 되도록 전략을 짜야 할 수도 있다. 17 장에서 이 개념을 설명한다. 일단은 프로그램이 실행되는 동안 이 객체들이 차지하는 메모리가 커질수 있다는 점만 알아두자.

NSString 클래스에는 수정 불가능한 스트링 객체에 쓸 수 있는 메서드가 100 개이상 있다. 표 15.2에 그중 자주 사용되는 메서드들을 요약했으며, 표 15.3 에서는 NSMutableString 클래스가 제공하는 추가 메서드를 일부 목록으로 제공한다. 다른 NSString 메서드(경로 이름을 다루거나 파일 내용을 스트링으로 읽어 오는 메서드등)는 앞으로 소개할 것이다.

먼저 표 15.2 와 표 15.3 을 보자. 파일 경로를 지정하는 url 은 NSURL 객체이고 path 는 NSString 객체다. nsstring 은 NSString 객체이고, i 는 스트링 내 유효한 문자번호를 나타내는 NSUInteger 값이다. enc 는 NSStringEncoding 객체로 문자 인코딩을 나타내 며, err 는 NSError 객체로 발생 된 오류를 나타낸다. size 와 opt 는 NSUIteger 이고 range 는 NSRange 객체로 스트링 내 유효한 문자 범위를 나타낸다.

표 15.3에 나오는 메서드는 NSMutableSring 객체를 생성하거나 수정한다.

NSString 객체는 이 책에서 앞으로도 광범위하게 사용한다. 스트링을 토큰으로 파싱할 필요가 있다면 Foundation 의 NSScanner 클래스를 살펴보라.

메서드 설명
+(id) stringWithContentsOfFile: path
encoding: enc error: err
스트링을 새로 생성하고, 그 값을 문자 인코딩 enc를 써서 path에 있는 파일의 내용물로 설정한다. 만일 오류가 발생하면 err로 오류를 반환한다.
+(id) stringWithContentsOfURL: url
encoding: enc error: err
스트링을 새로 생성하고, 그 값을 문자 인코딩 enc를 써서 url에 있는 파일의 내용으로 설정한다. 만일 오류가 발생하면 err로 오류를 반환한다.
+(id) string 빈 스트링을 새로 생성한다.
+(id) stringWithString: nsstring 새 스트링을 생성하고 nsstring으로 설정한다.
-(id) initWithString: nsstring 새로 생성된 스트링을 nsstring으로 설정한다.
-(id) initWithContentsOfFile: path
encoding: enc error: err
새로 생성된 스트링을, 문자 인코딩 enc를 써서 path에 지정된 파일의 내용으로 설정한다. 만일 오류가 발생하면 err로 오류를 반환한다.
-(id) initWithContentsOfURL: url
encoding: end error: err
새로 생성된 스트링을, 문자 인코딩 enc를 써서 url에 지정된 파일의 내용으로 설정한다. 만일 오류가 발생하면 err로 오류를 반환한다.
-(NSUInteger) length 스트링에 포함된 문자의 개수를 반환한다.
-(unichar) characterAtIndex: i 인덱스 i에 있는 유니코드 문자를 반환한다.
-(NSString *) substringFromIndex: i i번 문자부터 끝까지 이르는 스트링의 일부를 반환한다.
-(NSString *) substringWithRange: range 지정한 범위에 속하는 스트링 일부를 반환한다.
-(NSString *) substringToIndex: i 스트링의 앞부분에서 i번 문자 앞까지 이르는 스트링의 일부를 반환한다.
-(NSComparisonResult)
caseInsensitiveCompare: nsstring
대소문자는 무시하고 두 스트링을 비교한다.
-(NSComparisonResult)
compare: nsstring
두 스트링을 비교한다.
-(BOOL) hasPrefix: nsstring 스트링이 nsstring으로 시작하는지 확인한다.
-(BOOL) hasSuffix: nsstring 스트링이 nsstring으로 끝나는지 확인한다.
-(BOOL) isEqualToString: nsstring 두 스트링이 동일한지 확인한다.
-(NSString *)capitalizedString 각 단어의 첫 글자가 대문자인 스트링을 반환한다(나머지 글자는 소문자로 전환된다).
-(NSString *) lowercaseString 소문자로 변환된 스트링을 반환한다.
-(NSString *) uppercaseString 대문자로 변환된 스트링을 반환한다.
-(const char *) UTF8String UTF-8 C 스타일 문자 스트링을 반환한다.
-(double) doubleValue 스트링을 double 값으로 변환하여 반환한다.
-(float) floatValue 스트링을 float 값으로 변환하여 반환한다.
-(NSInteger) integerValue 스트링을 NSInteger 정수로 변환하여 반환한다.
-(int) intValue 스트링을 정수로 변환하여 반환한다.
표 15.2 자주 사용되는 NSString 메서드


메서드 설명
+(id) stringWithCapacity: size 문자를 size개 담은 스트링을 생성한다.
-(id) initWithCapacity: size 스트링의 초기 용량을 문자 size개로 초기화한다.
-(void) setString: nsstring 스트링을 nsstring으로 설정한다.
-(void) appendString: nsstring nsstring을 수신자의 뒤에 붙인다.
-(void) deleteCharactersInRange: range 지정한 range에 속하는 문자들을 삭제한다.
-(void) insertString: nstring atIndex: i nsstring을 수신자의 인덱스 i부터 삽입해 나간다.
-(void) replaceCharactersInRange: range withString: nstring 지정된 range에 자리 잡은 문자를 nsstring으로 대치한다.
-(void) replaceOccurrencesOfString:
nsstring withString: nsstring2 options:
opts range: range
지정한 range와 옵션 opts에 따라 나타나는 nsstring을 모두 nsstring2로 대치한다. 선택사항으로는 검색이 범위의 끝 지점부터 시작되는 NSBackwardsSearch, nsstring이 범위의 시작 지점부터만 일치해야 하는 NSAnchoredSearch, 바이트 단위의 비교를 수행하는 NSLiteralSearch와 NSCaseInsensitiveSearch가 있다.
표 15.3 자주 사용되는 NSMutableString 메서드


배열 객체

Foundation 배열은 객체의 정렬된 컬렉션이다. 배열의 원소는 주로 특정한 하나의 데이터 형으로 되어 있지만 여러가지 형일 때도있다. 수정 가능한 스트링과 불가능한 스트링이 있는 것처럼, 배열도 수정 가능한 배열과 불가능한 배열이 있다. 수정 불가능한 배열은 NSArray 클래스가 다루며, 수정 가능한 배열은 NSMutableArray 가 다룬다. 후자는 전자를 상속하기 때문에 그 메서드들도 상속받는다. 프로그램에서 배열 객체를 사용하려 할 때는 다음코드를 추가해 주면 된다.

#import <Foundation/NSArray.h>


프로그램 15.6은 매 달(month)의 이름을 저장한 배열을 설정하고 출력한다.


프로그램 15.6


#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    int i;
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // 매월의 이름을 담고 있는 배열

    NSArray *monthNames = [NSAraay arrayWithObjects:
            @"January", @"February", @"March", @"April",
            @"May", @"June", @"July", @"August", @"Septempber",
            @"October", @"November", @"December", nil ];

    // 배열 안의 모든 원소를 나열한다.

    NSLog (@"Month Name");
    NSLog (@"===== ====");

    for (i = 0; i < 12; ++i)
        NSLog (@"%2i    %@", i + 1, [monthNames objectAtIndex: i]);

    [pool drain];
    return 0;
}

프로그램 15.6 의 출력결과


Month Name
===== ====
1 January
2. February
3 March
4 April
5 May
6 June
7 July
8 Augest
9 September
10 October
11 November
12 December


클래스 메서드 arrayWithObject: 를 사용하여 객체 목록을 원소로 갖는 배열을 만들 수 있다. 이때, 객체는 순서대로 나열되고 쉼표로 구분된다. 이 문법은 좀 독특하다. 메서드에서 받는 인수의 개수가 변할 수 있다. 목록이 끝났음을 표시하려면 마지막 값으로 nil 을 지정해 주어야 하는데, 이 값은 배열에 실제로 저장되지는 않는다.

프로그램 15.7의 monthNames 는 arrayWithObjects: 에서 지정한 인수인 스트링값 12 개로 설정된다.

배열 원소들은 인덱스 번호로 식별된다. NSString 객체와 비슷하게 인덱스는 0 부터 시작한다. 따라서, 원소 12 개를 담는 배열에서 유효한 인덱스 번호는 0 부터 11 까지 있다. 인덱스 번호로 배열의 원소를 받으려면, objectAtIndex: 메서드를 사용한다.

프로그램은 for 문에서 objectAtIndex: 메서드를 사용하여 배열의 각 원소를 뽑아낸다. 읽어낸 원소는 NSLog 를 통해 표시된다.

프로그램 15.7 은 소수 표를 생성한다. 생성된 소수를 배열에 추가할 것이므로, 수정 가능한 배열이 필요하다. NSMutableArray 인 primes 는 arrayWithCapacity: 메서드를 써서 생성된다. 인수 20 은 배열의 최초 크기를 지정한다. 수정할 수 있는 배열은 프로그램이 돌아가면서 필요에 따라 자동으로 커진다.

비록 소수가 정수이지만, int 값을 배열에 직접 넣을수는 없다. 배열에는 객체만 담을 수 있으므로 NSNumber 정수 객체를 primes 배열에 넣어야 한다.



프로그램 15.7


#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSValue.h>

#define MAXPRIME 50

int main (int argc, char *argv[])
{
    int i, p, prevPrime;
    BOOL isPrime;
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // 소수를 저장할 배열 생성

    NSMutableArray *primes =
        [NSMutableArray arrayWithCapacity: 20];

    // 첫 소수 둘을 배열에 저장한다.

    [primes addObject: [NSNumber numberWithInteger: 2]];
    [primes addObject: [NSNumber numberWithInteger: 3]];

    // 남은 소수 계산

    for (p = 5; p <= MAXPRIME; p += 2) {
        // p가 소수인지 검사한다.

        isPrime = YES;

        i = 1;

        do {
            prevPrime = [[primes objectAtIndex: i] integerValue];

            if (p % prevPrime == 0)
                isPrime = NO;

            ++i;
        } while ( isPrime == YES && p / prevPrime >= prevPrime);

        if (isPrime)
            [primes addObject: [NSNumber numberWithInteger: p]];
    }

    // 결과를 표시한다.

    for (i = 0; i < [primes count]; ++i)
        NSLog (@"%li", (long) [[primes objectAtIndex: i] integerValue]);

    [pool drain];
    return 0;
}

프로그램 15.7 의 출력결과


2
3
5
7
11
13
17
19
23
29
31
37
41
43
47


MAXPRIME 은 계산하려는 최대 소수 번호를 정의하는데, 여기서는 50 이다.

primes 배열을 생성하고 나서,다음 명령문으로 이 배열의 처음 두 원소를 설정한다.

[primes addObject: [NSNumber numberWithInteger: 2]];
[primes addObject: [NSNumber numberWithInteger: 3]];


addObject: 메서드는 배열의 끝에 객체를 더한다. 여기서는 정수 값 2 와 3 으로 생성한 NSNumber 객체들을 추가했다.

그 후 프로그램은 for 문에 들어가 5부터 MAXPRIME 사이에 있는 짝수(P += 2)는 건너뛰고, 소수를 찾는다.

가능한 후보인 p 가 나을 때마다 이전에 발견된 소수로 나누어 떨어지는지 확인한다. 만일 나누어 떨어진다면 소수가 아니다. 좀더 최적화하기 위해,소수 후보가 자신의 제곱근보다 작은 소수로만 나누어 떨어지는지 확인한다. 만일 소수가 아니라면, 제곱근보다 작거나 같은 소수로 나누어 떨어진다(고등학교 수학을 기억하자!). 따라서 다음 표현식에서 prevPrime 은 p 의 제곱근보다 작을 때만 참으로 유지된다.

p / prevPrime >= prevPrime


만일 아직 isPrime 이 YES인데 do-while 문이 종료된다면, 다른 소수를 발견했다는 뜻이다. 이때, 소수 후보인 p 가 prims 배열에 추가되고 프로그램은 계속 실행된다.

이쯤에서 프로그램의 효율성에 대해 간단히 언급하겠다. Foundation 클래스중 배열을 다루는 클래스를 쓰면 편리한 점이 많다. 그 러나 숫자가 담긴 큰 배열들을 복잡한 알고리즘으로 다뤄야 할 때는 저수준 배열 구조를 쓰는 편이 메모리 사용과 실행 속도면에서 나을수 있다. 더 많은 정보는 13장의 '배열' 절을 참고하라.


주소록 만들기

주소록을 만들면서 지금까지 학습해 온 많은 내용을 모두 살펴보겠다.[2] 주소록에는 주소 카드가 있다. 단순하게 만들기 위해, 주소 카드에 사람 이름과 이메일 주소만 저장하자. 이 개념을 확장해서 주소나 전화번호같은 정보도 쉽게 담을수 있지만 그것은 연습문제로 남겨둔다.


주소카드 만들기

AddressCard 라는 클래스를 만드는 작업으로 시작해 보자. 새 주소카드를 생성하고, 이름과 전화번호를 설정하고, 그 값들을 받아 오고, 카드를 출력하는 기능이 필요하다. 그래픽 환경에서는 Application Kit 프레임워크에서 제공하는 멋진 루틴으로 화면에 카드를 그릴 수 있다. 그렇지만 여기서는 간단한 콘솔 인터페이스에서 주소카드를 표시할 것이다.

프로그램 15.8은 새 AddressCard 클래스의 인터페이스 파일을 보여 준다. 아직 접근자 메서드를 자동 생성(synthesize)하지는 않는다. 직접 작성하면서 귀중한 정보를 얻어나갈 것이다.

인터페이스 파일과 마찬가지로 구현 파일도 간단하다.


프로그램 15.8 AddressCard.h 인터페이스 파일


#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>

@interface AddressCard: NSObject
{
    NSString *name;
    NSString *email;
}

-(void) setName: (NSString *) theName;
-(void) setEmail: (NSString *) theEmail;

-(NSString *) name;
-(NSString *) email;

-(void) print;

@end



프로그램 15.8 AddressCard.m 구현 파일


#import "AddressCard.h"

@implementation AddressCard
-(void) setName: (NSString *) theName
{
    name = [[NSString alloc] initWithString: theName];
}

-(void) setEmail: (NSString *) theEmail
{
    email = [[NSString alloc] initWithString: theEmail];
}

-(NSString *) name
{
    return name;
}

-(NSString *) email
{
    return email;
}

-(void) print
{
    NSLog (@"====================================");
    NSLog (@"|                                  |");
    NSLog (@"|  %-31s |", [name UTF8String]);
    NSLog (@"|  %-31s |", [email UTF8String]);
    NSLog (@"|                                  |");
    NSLog (@"|                                  |");
    NSLog (@"|                                  |");
    NSLog (@"|       0                  0       |");
    NSLog (@"====================================");
}
@end


setName: 과 setEmail: 메서드를 다음과 같이 정의해서 각 인스턴스 변수에 객체를 직접 대입할 수도 있다.

-(void) setName: (NSString *) theName
{
    name = theName;
}

-(void) setEmail: (NSString *) theEmail
{
    email = theEmail;
}


그러나 이렇게 하면 AddressCard 객체는 자신의 멤버 객체를 소유하지 않는다. 8장 「상속」에서 Rectangle 의 객체가 origin 객체를 소유한 것에 관해 객체가 소유권을 갖는 이유를 설명했다.

두 메서드를 다음과 같이 정의하면, 정확한 접근법이 될 수 없다. AddressCard 의 메서드가 계속 이름과 이메일 객체를 소유하지 않고 NSString 객체가 소유하게 되기 때문이다.

-(void) setName: (NSString *) theName
{
    name = [NSString stringWithString: theName];
}

-(void) setEmail: (NSString *) theEmail
{
    email = [NSString stringWithString: theEmail];
}


프로그램 15.8 로 돌아가자. print 메서드는 명함 정리용 롤로덱스 카드를 닮은 모양으로 주소 카드를 예쁘게 표시하려고 노력한다. NSLog 에서 사용된 %-31s 는 31 자 길이인 UTF8 C 스트링 필드를 좌측 정렬하여 표시하라는 의미다. 이를 통해 출력 결과에서 주소 카드의 오른쪽 끝에 맞춰 정렬될 수 있다. 이것은 오로지 예쁘게 보이기 위해 사용했다.

AddressCard 클래스를 가지고 주소카드를 생성하고, 값을 설정하고, 화면에 표시하는 테스트 프로그램을 작성해 보자.


프로그램 15.8 테스트 프로그램


#import "AddressCard.h"
#import "<Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSString *aName = @"Julia Kochan";
    NSString *aEmail = @"jewls337@axlc.com";
    AddressCard *card1 = [[AddressCard alloc] init];

    [card1 setName: aName];
    [card1 setEmail: aEmail];

    [card1 print];

    [card1 release];
    [pool drain];
    return 0;
}

프로그램 15.8 의 출력결과


====================================
|                                  |
| Julia Kochan                     |
| jewls337@axlc.com                |
|                                  |
|                                  |
|                                  |
|        0                0        |
====================================


다음 명령문은 프로그램에서 주소록 카드가 쓴 메모리를 릴리스 해준다.

[card1 release];


다시 한 번 말하지만 이렇게 AddressCard 를 릴리스한다고 해서 name 과 email 멤버에 할당한 메모리까지 릴리스되지는 않는다. 이 점은 반드시 기억하자. AddressCard 가 메모리 누수를 만들지 않게 하려면, dealloc 메서드를 재정의해서 AddressCard 객체가 릴리스될 때마다 이 멤버들도 릴리스되도록 해야 한다.

다음은 AddressCard 클래스에 있는 dealloc 메서드다.

-(void) dealloc
{
    [name release];
    [email release];
    [super dealloc];
}


dealloc 메서드는 객체 자신을 파괴하기 전에 super 를 사용해 자신의 인스턴스 변수를 릴리스해야 한다. 이것은 객체가 해제되고 나서는 더는 유효하지 않기 때문이다.

AddressCard 가 메모리 누수를 일으키지 않으려면 setName: 과 setEmail: 메서드를 수정해 인스턴스 변수에 저장된 객체가 쓰는 메모리를 릴리스해 주어야 한다. 만일 누군가가 카드 이름을 바꾸면 이전 이름이 차지했던 메모리를 해제하고 새 이름으로 바꿔야 한다. 이와 마찬가지로, 이메일 주소도 새 주소로 바꾸기 전에 먼저 메모리를 릴리스해야 한다.

다음은 새로운 setName: 과 setEmail: 인데, 이 메서드들은 클래스가 메모리를 적절히 관리하도록 보장한다.

-(void) setName: (NSString *) theName
{
    [name release];
    name = [[NSString alloc] initWithString: theName];
}

-(void) setEmail: (NSString *) theEmail
{
    [email release];
    email = [[NSString alloc] initWithString: theEmail];
}


nil 객체에도 메시지를 보낼 수 있다.다음 두 표현식을 보자.

[name release];
[email release];


이 두 메시지 표현식은 name 과 email 이 설정되지 않았다면 써도 괜찮다.


자동 생성된 AddressCard 메서드

이제 setName:, setEmail: 접근자 메서드를 작성하는 올바른 방법을 설명했다. 이 중요한 원칙을 이해했으므로 시스템이 접근자 메서드를 자동 생성하도록 만들자. AddressCard 인터페이스 파일의 두 번째 버전을 살펴보도록 하자.

#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>

@interface AddressCard: NSObject
{
    NSString *name;
    NSString *email;
}

@property (copy, nonatomic) NSString *name, *email;
-(void) print;
@end


다음코드는 프로퍼티의 '속성' 인 copy, nonatomic 을 나열한다.

@property (copy, nonatomic) NSString *name, *email;


copy 속성은 여러분이 직접 작성했던 버전처럼 세터 메서드에서 인스턴스 변수의 사본을 만들라는 의미다. 기본값은 사본을 만드는 대신 단순하게 대입(기본속성인 assign)하는데, 이는 앞에서도 설명했듯이 부적절한 접근법이다.

nonatomic 속성은 게터 메서드가 값을 반환하기 전까지 인스턴스 변수를 '리테인' 하거나 '오토릴리스' 해서는 안 된다고 지시한다. 18장 에서 이에 대해 더 상세히다룬다.

프로그램 15.9 는 AddressCard 를 새로 구현해 접근자 메서드를 자동 생성하도록 만든 파일이다.


프로그램 15.9 지동 생성 접근자 메서드를 갖는 AddressCard.m 구현 파일


#import "AddressCard.h"

@implementation AddressCard

@synthesize name, email;
-(void) print
{
NSLog (@"====================================");
NSLog (@"|                                  |");
NSLog (@"|  %-31s |", [name UTF8String]);
NSLog (@"|  %-31s |", [email UTF8String]);
NSLog (@"|                                  |");
NSLog (@"|                                  |");
NSLog (@"|                                  |");
NSLog (@"|       0                  0       |");
NSLog (@"====================================");
}
@end


이렇게 AddressCard 를 새로 정의하면 자동 생성 접근자 메서드를 사용하게 된다. 연습 삼아 프로그램 15.9 의 테스트 프로그램에서 정상 동작하는지 확인해 보라.

이제 AddressCard 클래스에 메서드를 추가한다. 이름과 이메일 필드를 한꺼번에 설정하면 편리할 것이다. 이를 위해 여기에 setName:andEmail: 을 추가한다.[3] 메서드는 다음과 같다.

-(void) setName: (NSString *) theName andEmail: (NSString *) theEmail
{
    self.name = theName;
    self.email = theEmail
}


(직접 작성한 메서드에서 값을 설정하는 대신) 자동 생성된 세터 메서드에 기대어 적절한 인스턴스 변수를 설정하게 하면 추상화 단계를 하나 더 올린셈이다. 이를 통해 프로그램이 내부데이터 구조에 덜 의존하게 된다. 또한 자동생성 메서드의 속성에서 얻는 이득도 있다. 여기서는 인스턴스 변수에 값을 대입하는 대신 복사하는 속성을 사용하였다.


프로그램 15.9에서 새 메서드를 테스트한다.


프로그램 15.9 테스트 프로그램


#import <Foundation/Foundation.h>
#import "AddressCard.h"

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSString *aName = @"Julia Kochan";
    NSString *aEmail = @"jewls337@axlc.com";
    NSString *bName = @"Tony Iannino";
    NSString *bEmail = @"tony.iannino@techfitness.com";

    AddressCard *card1 = [[AddressCard alloc] init];
    AddressCard *card2 = [[AddressCard alloc] init];

    [card1 setName: aName andEmail: aEmail];
    [card2 setName: bName andEmail: bEmail];

    [card1 print];
    [card2 print];
    [card1 release];
    [card2 release];
    [pool drain];
    return 0;
}

프로그램 15.9 의 출력결과


====================================
|                                  |
| Julia Kochan                     |
| jewls337@axlc.com                |
|                                  |
|                                  |
|                                  |
|       0                 0        |
====================================


====================================
|                                  |
| Tony Iannino                     |
| tony.iannino@techfitness.com     |
|                                  |
|                                  |
|                                  |
|       0                 0        |
====================================


AddressCard 클래스는 정상 동작하는 것 같다. 그런데 AddressCard 를 여러 개 취급해야 한다면 어떻게 될까? 이제 AddressBook 이라는 새 클래스를 정의하여 여러 AddressCard 를 한데 모을 것이다. AddressBook 클래스는 주소록의 이름을 저장하고 AddressCard 묶음을 배열에 담을 것이다. 먼저 새 주소록을 생성하고, 새 주소 카드를 담고, 주소 카드가 얼마나 많이 있는지 확인하고, 내용을 표시하는 기능이 필요하다. 나중에는 주소록을 검색하고, 항목을 이동하고, 정렬하고, 내용을 복사하는 기능들도 필요할 것이다.

먼저 간단한 인터페이스 파일을 작성하자(프로그램 15.10을 보라).



프로그램 15.10 AddressBook.h 인터페이스 파일


#import <Foundation/NSArray.h>
#import "AddressCard.h"

@interface AddressBook: NSObject
{
    NSString *bookName;
    NSMutableArray *book;
}

-(id) initWithName: (NSString *) name;
-(void) addCard: (AddressCard *) theCard;
-(int) entries;
-(void) list;
-(void) dealloc;

@end


initWithName: 메서드는 주소 카드가 담긴 초기 배열을 생성하고 주소록의 이름을 저장한다. addCard: 메서드는 AddressCard 를 주소록에 추가한다. entries 메서드는 주소록에 든 주소 카드의 개수를 알려 주고, list 메서드는 전체 내용을 요약한 목록을 반환한다.


다음 프로그램 15.10 은 AddressBook 을 구현한 파일이다.


프로그램 15.10 AddressBook.m 구현 파일


#import "AddressBook.h"

@implementation AddressBook;

// AddressBook의 이름을 설정하고 빈 주소록을 생성한다.

-(id) initWithName: (NSString *) name
{
    self = [super init];

    if (self) {
        bookName = [[NSString alloc] initWithString: name];
        book = [[NSMutableArray alloc] init];
}

return self;
}

-(void) addCard: (AddressCard *) theCard
{
    [book addObject: theCard];
}

-(int) entries
{
    return [book count];
}

-(void) list
{
    NSLog (@"======== Contents of: %@ =========", bookName);

    for ( AddressCard *theCard in book )
        NSLog (@"%-20s    %-32s", [theCard.name UTF8String],
            [theCard.email UTF8String]);

    NSLog (@"===================================="
}

-(void) dealloc
{
    [bookName release];
    [book release];
    [super dealloc];
}
@end


initWithName: 메서드는 먼저 수퍼클래스에 init 메서드를 호출하여 초기화를 수행한다. 그 다음, 스트링 객체를 생성하고(alloc을 사용하므로 스트링 객체를 소유한다), 건네받은 name 으로 주소록에 들어갈 이름을 설정한다. 그후 인스턴스 변수 book에 저장될 빈배열(수정가능한 배열이다)을 생성하고 초기화한다.

그러고 나서 AddressBook 이 아닌 id 객체를 반환하도록 initWithName: 을 정의한다. 만일 AddressBook 의 서브클래스를 만들었다면 initWithName: 의 반환 형은 AddressBook 객체가 아니라 서브클래스 형일 것이다. 이런 이유로 반환 형을 일반 객체 형인 id 로 정의하였다.

initWithName: 에서 alloc 을 사용하여 bookName 과 book 인스턴스 변수를 소유한다는 점에 유의하자. 예컨대, book 배열을 다음처럼 NSMutableArray의 array 메서드를 사용하여 생성했다고 하자

book = [NSMutableArray array];


그러면 AddressBook 객체 대신 NSMutableArray 가 book 배열을 소유하게 된다. 그러므로 AddressBook 객체의 메모리를 해제할 때, 이 객체의 메모리를 해제할 수 없게 된다.

addCard: 메서드는 AddressCard 객체를 인수로 받아 주소록에 추가한다.

count 메서드는 배열의 원소 개수를 반환한다. entries 메서드는 이 count 메서드를 사용하여 주소록에 있는 주소 카드의 개수를 반환한다.


빠른 열거

list 메서드에 있는 for 문에서는 지금까지 보지 못한 새로운 구조를 사용한다.

for ( AddressCard *theCard in book )
    NSLog (@"%-20s   %-32s", [theCard.name UTF8String],
        [theCard.email UTF8String]);


이 반복문은 '빠론 열거 (Fast Enumeration)' 를 사용해 book 배열의 각 원소를 순서대로 가져온다. 문법은 매우 간단하다. 배열의 각 원소를 담을 변수를 하나 정의한다(AddressCard *theCard). 그 뒤에 키워드 in 과 배열 이름을 적어 준다. for 문이 실행될 때, 지정한 변수에 배열의 첫 원소를 할당하고 반복문의 몸체를 실행한다. 그 후 배열의 두 번째 원소를 변수에 대입하고, 반복문의 몸체를 실행한다. 이 작업은 배열에 있는 모든 원소가 차례차례 변수에 대입되고 그때마다 배열의 몸체를 실행시킨다. 이는 마지막 원소까지 반복된다.

만일 theCard 가 앞에서 AddressCard 객체로 이미 정의 되었다면, for 문은 다음과 같이 더욱 단순해진다.

for ( theCard in book )
    ...


프로그램 15.10 로 새 AddressBook 클래스를 테스트해 보자.


프로그램 15.10 테스트 프로그램


#import "AddressBook.h"
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSString *aName = @"Julia Kochan";
    NSString *aEmail = @"jewls337@axlc.com";
    NSString *bName = @"Tony Iannino";
    NSString *bEmail = @"tony.iannino@techfitness.com";
    NSString *cName = @"Stephen Kochan";
    NSString *cEmail = @"steve@kochan-wood.com";
    NSString *dName = @"Jamie Baker";
    NSString *dEmail = @"jbaker@kochan-wood.com";

    AddressCard *card1 = [[AddressCard alloc] init];
    AddressCard *card2 = [[AddressCard alloc] init];
    AddressCard *card3 = [[AddressCard alloc] init];
    AddressCard *card4 = [[AddressCard alloc] init];

    AddressBook *myBook = [AddressBook alloc];

    // 주소 카드를 네 개 만든다.
    [card1 setName: aName andEmail: aEmail];
    [card2 setName: bName andEmail: bEmail];
    [card3 setName: cName andEmail: cEmail];
    [card4 setName: dName andEmail: dEmail];

    // 주소록을 초기화한다.
    myBook = [myBook initWithName: @"Linda's Address Book"];

    NSLog (@"Entries in address book after creation: %i",
            [myBook entries]);

    // 주소록에 카드를 추가한다.

    [myBook addCard: card1];
    [myBook addCard: card2];
    [myBook addCard: card3];
    [myBook addCard: card4];

    NSLog (@"Entries in address book after adding cards: %i",
            [myBook entries]);

    // 주소록의 모든 항목을 나열한다.
    [myBook list];

    [card1 release];
    [card2 release];
    [card3 release];
    [card4 release];
    [myBook release];
    [pool drain];
    return 0;
}

프로그램 15.10 의 출력결과


Entries in address book after creation: 0
Entries in address book after adding cards: 4

======== Contents Of: Linda's Address Book =========
Julia Kochan      jewls337@axlc.com
Tony Iannino      tony.iannino@techfitness.com
Stephen Kochan    steve@kochan-wood.com
Jamie Baker       jbaker@kochan-wood.com


프로그램은 주소 카드를 네 개 생성하고 Linda's Address Book 이라는 이름으로 주소록을 생성한다. 그 후 addCard: 메서드를 써서 카드 네 개를 주소록에 추가한다. 그리고 list 메서드로 주소록의 내용을 나열하고 확인한다.

주소록에서 사람 찾기

주소록이 방대하면, 누군가를 찾으려고 주소록 전체 목록을 나열하고 싶지는 않을 것이다. 이때는 검색 메서드를 추가하는 편이 낫다. 이 메서드를 lookup: 이라고 이름 붙이고, 검색할 이름을 인수로 받도록 만들자. 이 메서드는 주소록에서 맞는 이름(대소문자는무시한다)을 찾을 경우에 그것을 반환한다. 만일 주소록에 이름이 없다면 nil 을 반환한다.


여기 새로운 lookup: 메서드를 보자.

// 이름으로 주소 카드를 찾는다 - 정확히 맞는 짝을 검색.

-(AddressCard *) lookup: (NSString *) theName
{
    for ( AddressCard *nextCard in book )
        if ( [[nextCard name] caseInsensitiveCompare: theName] == NSOrderedSame )
            return nextCard;

    return nil;
}


인터페이스 파일에서는 선언부분에,구현 파일에서는 정의 부분에 이 메서드를 넣자. 그러면 테스트 프로그램을 작성하여 이 lookup: 메서드를 테스트해 볼 수 있다.

프로그램 15.11 과 프로그램 15.11 의 출력 결과로 새 메서드를 테스트한다.


프로그램 15.11 테스트 프로그램


#import "AddressBook.h"
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSString *aName = @"Julia Kochan";
    NSString *aEmail = @"jewls337@axlc.com";
    NSString *bName = @"Tony Iannino";
    NSString *bEmail = @"tony.iannino@techfitness.com";
    NSString *cName = @"Stephen Kochan";
    NSString *cEmail = @"steve@kochan-wood.com";
    NSString *dName = @"Jamie Baker";
    NSString *dEmail = @"jbaker@kochan-wood.com";
    AddressCard *card1 = [[AddressCard alloc] init];
    AddressCard *card2 = [[AddressCard alloc] init];
    AddressCard *card3 = [[AddressCard alloc] init];
    AddressCard *card4 = [[AddressCard alloc] init];

    AddressBook *myBook = [AddressBook alloc];
    AddressCard *myCard;

    // 주소 카드를 네 개 설정한다.

    [card1 setName: aName andEmail: aEmail];
    [card2 setName: bName andEmail: bEmail];
    [card3 setName: cName andEmail: cEmail];
    [card4 setName: dName andEmail: dEmail];

    myBook = [myBook initWithName: @"Linda's Address Book"];

    // 주소록에 카드를 추가한다.

    [myBook addCard: card1];
    [myBook addCard: card2];
    [myBook addCard: card3];
    [myBook addCard: card4];

    // 이름으로 사람을 찾는다.

    NSLog (@"Lookup: Stephen Kochan");
    myCard = [myBook lookup: @"stephen kochan"];

    if (myCard != nil)
        [myCard print];
    else
        NSLog (@"Not found!");

    // 다른 검색 시도

    NSLog (@"Lookup: Haibo Zhang");
    myCard = [myBook lookup: @"Haibo Zhang"];

    if (myCard != nil)
        [myCard print];
    else
        NSLog (@"Not found!");

    [card1 release];
    [card2 release];
    [card3 release];
    [card4 release];
    [myBook release];

    [pool drain];
    return 0;
}

프로그램 15.11 의 출력결과


Lookup: Stephen Kochan
====================================
|                                  |
| Stephen Kochan                   |
| steve@kochan-wood.com            |
|                                  |
|                                  |
|                                  |
|       0                 0        |
====================================
Lookup: Haibo Zhang
Not found!


lookup: 메서드가 주소록에서 Stephen Kochan 을 찾으면, 이 메서드는 결과로 나오는 주소 카드에 AddressCard 의 print 메서드를 써서 화면에 표시한다. 두 번째로 검색한 Haibo Zhang 은 발견되지 않았다.

이 검색 메시지는 이름 전체가 일치할 때만 찾아낸다는 면에서 매우 원시적이다. 이름의 일부라도 일치하는지 찾아서 다중 검색된 결과를 처리하는 방식이 더 나은 방법이다. 예를 들어, 메시지 표현식을 다음처럼 작성하면 'Steve Kochan' 과'Fried Stevens' 그리고 'steven levy' 가 검색 결과로 나올 것이다.

[myBook lookup: @"steve"]


다중 검색된 결과가 있을테니, 다음처럼 모든 검색 결과를 담는 배열을 만들어서 메서드 호출자에게 넘겨주는 방법이 좋을것이다(이 장의 연습문제 2번을보라).

matches = [myBook lookup: @"steve"];


주소록에서 사람 지우기

사람을 추가하는 기능만 있고 삭제하는 기능은 없는 주소록이 있을까. removeCard: 메서드를 만들어 주소록에서 특정 AddressCard 를 제거할 수 있다. 또다른 기능도 넣어보자. 넘겨진 이름을 삭제하는 remove: 메서드를 만들자(연습문제 6 을보라).


프로그램 15.12 AddressBook.h 인터페이스 파일


#import <Foundation/NSArray.h>
#import "AddressCard.h"

@interface AddressBook: NSObject
{
    NSString *bookName;
    NSMutableArray *book;
}

-(id) initWithName: (NSString *) name;

-(void) addCard: (AddressCard *) theCard;
-(void) removeCard: (AddressCard *) theCard;

-(AddressCard *) lookup: (NSString *) theName;
-(int) entries;
-(void) list;
@end


인터페이스 파일에 몇 가지 변화를 가했으니 프로그램 15.12 에서 새 메서드 removeCard: 가 추가된 인터페이스 파일을 다시 한번 보자.

다음은 구현 파일에 추가될 removeCard: 메서드다.

-(void) removeCard: (AddressCard *) theCard
{
    [book removeObjectIndenticalTo: theCard];
}


어떤 것을 동일한 객체로 볼지 판단할 때, 여기서는 메모리상 위치가 동일한가를 판단한다. 따라서 removeObjectIdenticalTo: 메서드는, 주소카드 두 개가 동일한 정보를 보유했지만 메모리상 위치가 다르다면 두 주소 카드(예를 들어 AddressCard 객체의 사본을 만드는 경우)를 같다고 여기지 않는다.

덧붙여서 removeObjectIdenticalTo: 메서드는 인수와 동일한 객체를 모두 삭제한다. 그러나 배열에 동일한 객체가 여러 번 나타날 경우에만 이것이 문제가 될 것이다.

또한 removeObject: 메서드를 사용하고, 두 객체가 동일한지 확인하는 isEqual: 메서드를 직접 작성해서 좀더 정교하게 객체의 동일 여부를 확인할 수 있다. 만일 removeObject: 를 사용하면 시스템은 배열의 원소마다 isEqual: 을 자동으로 호출하여 두 객체를 비교한다. 이 경우 주소록이 AddressCard 객체를 원소로 담고 있으므로 isEqual: 을 그 클래스에 추가해야 한다(NSObject에게서 상속받은 메서드를 재정의하게 된다). 그래야 이 메서드의 동일 여부를 결정할수 있다. 이때는 두 객체의 이름과 메일 주소를 비교할 것이다. 만일 두 가지가 모두 동일하다면 YES 를 반환하고,그렇지 않으면 NO 를 반환한다. 메서드의 형태는 다음과 같을 것이다.

-(BOOL) isEqual: (AddressCard *) theCard
{
    if ([name isEqualToString: theCard.name) == YES &&
            [email isEqualToString: theCard.email] == YES)
        return YES;
    else
        return NO;
}


NSArray 에 있는 다른 메서드, 예컨대 containsObject: 와 indexOfObject: 같은 메서드 역시 isEqual: 을 사용하여 두 객체가 동일한지 판단한다.


프로그램 15.12 테스트 프로그램


#import "AddressBook.h"
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSString *aName = @"Julia Kochan";
    NSString *aEmail = @"jewls337@axlc.com";
    NSString *bName = @"Tony Iannino";
    NSString *bEmail = @"tony.iannino@techfitness.com";
    NSString *cName = @"Stephen Kochan";
    NSString *cEmail = @"steve@kochan-wood.com";
    NSString *dName = @"Jamie Baker";
    NSString *dEmail = @"jbaker@kochan-wood.com";

    AddressCard *card1 = [[AddressCard alloc] init];
    AddressCard *card2 = [[AddressCard alloc] init];
    AddressCard *card3 = [[AddressCard alloc] init];
    AddressCard *card4 = [[AddressCard alloc] init];

    AddressBook *myBook = [AddressBook alloc];
    AddressCard *myCard;

    // 주소 카드를 네 개 설정한다.

    [card1 setName: aName andEmail: aEmail];
    [card2 setName: bName andEmail: bEmail];
    [card3 setName: cName andEmail: cEmail];
    [card4 setName: dName andEmail: dEmail];

    myBook = [myBook initWithName: @"Linda's Address Book"];

    // 주소록에 카드를 추가한다.

    [myBook addCard: card1];
    [myBook addCard: card2];
    [myBook addCard: card3];
    [myBook addCard: card4];

    // 이름으로 사람을 찾는다.

    NSLog (@"Stephen Kochan");
    myCard = [myBook lookup: @"Stephen Kochan"];

    if (myCard != nil)
        [myCard print];
    else
        NSLog (@"Not found!");

    // 주소록에서 항목을 제거한다.

    [myBook removeCard: myCard];
    [myBook list]; // 제거되었는지 확인한다.

    [card1 release];
    [card2 release];
    [card3 release];
    [card4 release];
    [myBook release];
    [pool drain];
    return 0;
}

프로그램 15.12 의 출력결과


Lookup: Stephen Kochan
==================================
|                                |
| Stephen Kochan                 |
| steve@kochan-wood.com          |
|                                |
|                                |
|                                |
|        0                0      |
==================================

======== Contents of: Linda's Address Book =========
Julia Kochan jewls337@axlc.com
Tony Iannino tony.iannino@techfitness.com
Jamie Baker  jbaker@kochan-wood.com
===================================================


프로그램 15.12는 새로운 메서드인 removeCard: 를 테스트한다.

주소록에 Stephen Kochan 이 있는지 검색해 찾아낸 다음에 결과 AddressCard 를 새로 만든 removeCard: 메서드에 넘겨 삭제한다. 주소록 명단을 검색한 결과, 삭제 되었음을 확인할 수 있다.


배열 정렬하기

주소록에 등록된 사람들이 많다면 알파벳 순서대로 정렬하면 편리할 것이다. AddressBook 클래스에 sort 메서드를 추가하고 NSMutableArray 의 메서드인 sortUsingSelector: 를 사용하면 쉽게 정렬할 수 있다. 이 메서드는 자신이 두 원소를 비교할 때 사용하는 셀렉터를 인수로 받는다. 배열에는 데이터 형에 관계없이 모든종류의 객체가 들어갈 수 있다. 그러니 일반 정렬 메서드를 구현하는 유일한 방법은 프로그래머가 배열 내 원소들을 차례대로 놓을지 말지를 정하는 것이다. 이를 위해서, 배열의 두 원소를 비교할 메서드를 추가해야 한다.[4] 이 메서드가 반환하는 결과는 NSComparisonResult 형이다. 만일, 배열에서 첫째 원소를 둘째 원소보다 앞에 위치하게 하려면 정렬하는 메서드는 NSOrderedAscending 을 반환하고, 두 원소가 동일하다고 여길 때는 NSOrderedSame 을, 그리고 첫째 원소가 둘째 원소보다 뒤에 나와야 하는 경우에는 NSOrderedDecending 을 반환한다.

먼저, AddressBook 클래스의 새 정렬 메서드를 보자.

-(void) sort
{
    [book sortUsingSelector: @selector(compareNames:)];
}


9장 「다형성,동적 타이핑,동적 바인딩」에서 배웠듯이 다음표현식은 SEL 형인 셀렉터를 주어진 메서드 이름으로 생성한다.

@selector (compareNames:)


sortUsingSelector: 는 이 메서드를 사용하여 배열 안의 두 원소를 비교한다. 비교해야 할 때 지정한 메서드를 호출하는데, 이때 배열의 첫째 원소(메시지 수신자)에 메시지를 보내 그 원소와 인수를 비교하는 것이다. 반환값은 앞에서 설명한 대로 NSComparisonResult 형이어야 한다.

주소록의 원소가 AddressCard 객체이기 때문에 AddressCard 클래스에 비교를 위한 메서드를 추가해야 한다. AddressCard 클래스로 돌아가 다음 compareNames: 메서드를 추가하자.

// 지정한 주소 카드의 이름을 비교한다.
-(NSComparisonResult) compareNames: (id) element
{
    return [name compare: [element name]];
}


주소록에 있는 두 이름의 스트링을 비교하고 있으므로 NSString 의 compare: 메서드를 사용해 비교할 수 있다. sort 메서드를 AddressBook 클래스에 추가하고 compareNames: 메서드를 AddressCard 클래스에 추가했다면, 이제 테스트 프로그램을 작성하여 테스트할 수 있다(프로그램 15.13 을 보라)


프로그램 15.13 테스트 프로그램


#import "AddressBook.h"
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSString *aName = @"Julia Kochan";
    NSString *aEmail = @"jewls337@axlc.com";
    NSString *bName = @"Tony Iannino";
    NSString *bEmail = @"tony.iannino@techfitness.com";
    NSString *cName = @"Stephen Kochan";
    NSString *cEmail = @"steve@kochan-wood.com";
    NSString *dName = @"Jamie Baker";
    NSString *dEmail = @"jbaker@kochan-wood.com";

    AddressCard *card1 = [[AddressCard alloc] init];
    AddressCard *card2 = [[AddressCard alloc] init];
    AddressCard *card3 = [[AddressCard alloc] init];
    AddressCard *card4 = [[AddressCard alloc] init];

    AddressBook *myBook = [AddressBook alloc];

    // 주소 카드를 네 개 설정한다.

    [card1 setName: aName andEmail: aEmail];
    [card2 setName: bName andEmail: bEmail];
    [card3 setName: cName andEmail: cEmail];
    [card4 setName: dName andEmail: dEmail];

    myBook = [myBook initWithName: @"Linda's Address Book"];

    // 주소록에 카드를 추가한다.

    [myBook addCard: card1];
    [myBook addCard: card2];
    [myBook addCard: card3];
    [myBook addCard: card4];

    // 정렬되지 않은 주소록 나열한다.
    [myBook list];

    // 정렬하고 다시 나열한다.
    [myBook sort];
    [myBook list];

    [card1 release];
    [card2 release];
    [card3 release];
    [card4 release];
    [myBook release];
    [pool drain];
    return 0;
}

프로그램 15.13 의 출력결과


======== Contents of: Linda's Address Book =========
Julia Kochan jewls337@axlc.com
Tony Iannino tony.iannino@techfitness.com
Stephen Kochan steve@kochan-wood.com
Jamie Baker jbaker@kochan-wood.com
===================================================

======== Contents of: Linda's Address Book =========
Jamie Baker jbaker@kochan-wood.com
Julia Kochan jewls337@axlc.com
Stephen Kochan steve@kochan-wood.com
Tony Iannino tony.iannino@techfitness.com
===================================================


오름차순 정렬인 것에 주목하자. 내림차순 정렬을하고 싶은 경우에는 AddressCard 클래스의 comparesNames: 메서드를 수정해서 반환되는 값이 반대로 만들면 된다.

Objective-C 에서는 배열 객체를 다루는 메서드를 50 개 이상 제공한다. 표 15.4 와 표 15.5 는 각각 수정 불가능한 배열, 수정 가능한 배열에서 자주 사용되는 메서드를 나열한다. NSMutableArray 는 NSArray 의 서브클래스이므로, 전자는 후자의 메서드를 모두 상속받는다.

표 15.4 와 표 15.5 에서 obj, obj1, obj2 는 객체를 지칭한다. i 는 NSUInteger 정수로, 배열에서 유효한 인덱스 번호다. selector는 SEL 형의 셀렉터 객체다. size 는 NSUInteger 정수다.

메서드 설명
+(id) arrayWithObjects: obj1, obj2, ... nil obj1, obj2, ...가 원소인 새 배열을 생성한다.
-(BOOL) containsObject: obj 배열에 obj가 있는지 확인한다(isEqual: 메서드를 사용한다).
-(NSUInteger) count 배열에 담긴 원소의 개수를 반환한다.
-(NSUInteger) indexOfObject: obj obj를 담은 첫째 원소의 인덱스 번호를 반환한다(isEqual: 메서드를 사용한다).
-(id) objectAtIndex: i 원소 i에 저장된 객체를 반환한다.
-(void) makeObjectsPerform Selector:
(SEL)selector
selector가 가리키는 메시지를 배열에 있는 모든 객체에게 보낸다.
-(NSArray *) sortedArrayUsing Selector:
(SEL) selector
selector로 지정한 비교 메서드를 이용해 배열을 정렬한다.
-(BOOL) writeToFile: path atomically:
(BOOL) flag
지정한 파일로 배열을 쓴다. flag가 YES이면 임시 파일을 먼저 생성한다.
표 15.4 자주 사용되는 NSArray 메서드


메서드 설명
+(id) array 빈 배열을 생성한다.
+(id) arrayWithCapacity: size 초기 크기가 size인 배열을 생성한다.
-(id) initWithCapacity: size 새로 생성된 배열을 초기 크기가 size인 배열로 초기화한다.
-(void) addObject: obj 배열의 끝에 obj를 추가한다.
-(void) insertObject: obj atIndex: i 배열의 원소 iobj를 추가한다.
-(void) replaceObjectAtIndex: i withObject: obj 배열의 원소 iobj로 교체한다.
-(void) removeObject: obj 배열에서 obj를 모두 제거한다.
-(void) removeObjectAtIndex: i 배열에서 i 원소를 제거하고 i+1부터 배열 끝까지 앞으로 한 칸씩 옮긴다.
-(void) sortUsingSelector: (SEL) selector selector가 가리키는 비교 메서드로 배열을 정렬한다.
표 15.5 자주 사용되는 NSMutableArray 메서드


딕셔너리 객체

'딕셔너리'는 키-객체 짝으로 구성된 데이터의 모음이다. 사전에서 단어의 정의를 찾듯이 Objective-C 사전(딕셔너리)에서도 키로 값(객체)을 얻는다. 딕셔너리의 키는 유일해야 하며, 어느 객체 형이어도 좋지만 보통은 스트링이다. 키와 연계된 값도 객체 형이 어떻든 상관없지만 nil 이어서는 안 된다.

딕셔너리도 수정할 수도 있고, 수정 불가능할 수도 있다. 수정 가능한 딕셔너리들은 동적으로 엔트리를 추가하거나 삭제할 수 있다. 특정 키를 사용하여 딕셔너리를 찾는 일도, 내용물을 열거하는 일도가능하다. 프로그램 15.4 에서는 수정 가능한 딕셔너리를 생성하여 Objective-C 의 용어에 대한 소사전으로 사용한다. 이때 딕셔너리 는 세 엔트리를 보유한다.

프로그램에서 딕셔너리를 사용하려면 다음 코드를 추가한다.

#import <Foundation/NSDictionary.h>



프로그램 15.14


#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSMutableDictionary *glossary = [NSMutableDictionary dictionary];

    // 소사전에 항목 세 개를 저장한다.

    [glossary setObject: @"A class defined so other classes can inherit from it"
            forKey: @"abstract class"];
    [glossary setObject: @"To implement all the methods defined in a protocol"
            forKey: @"adopt"];
    [glossary setObject : @"Storing an object for later use"
            forKey: @"archiving"];

    // 값을 받아 표시한다.

    NSLog (@"abstract class: %@", [glossary objectForKey: @":abstract class"]);
    NSLog (@"adopt: %@", [glossary objectForKey: @"adopt"]);
    NSLog (@"archiving: %@", [glossary objectForKey: @"archiving"]);

    [pool drain];
    return 0;
}

프로그램 15.14 의 출력결과


abstract class: A class defined so other classes can inherit from it
adopt: To implement all the methods defined in a protocol
archiving: Storing an object for later use


다음 표현식은 비어 있는 딕셔너리를 생성하는데, 이 딕셔너리는 수정할 수 있다.

[NSMutableDictionary dictionary]


setObject:forKey: 메서드를 사용하여 이 딕셔너리에 키-값 짝을 추가한다. 딕셔너리가 구성되고 나면, objectForKey: 메서드를사용해 주어진 키 값을 받을수 있다. 프로그램 15.14 는 용어 소사전에 담긴 세 엔트리의 값을 어떻게 받아오고 표시하는지를 보여 준다. 좀더 실용적인 프로그램을 생각해 보면, 사용자가 단어를 입력하고 프로그램이 용어집에서 단어 정의를 검색하는 프로그램을 만들어도 좋을 것이다.


딕셔너리 열거하기

프로그램 15.15 에는 dictionaryWithObjectsAndKeys: 메서드를 써서 딕셔너리를 초기 키-값 짝으로 정의하는 방법이 나온다. 여기서는 수정 불가능한 객체를 만들고, 빠른 열거 반복분을 써서 한 번에 한 키씩 딕셔너리에서 각 원소를 받아 왔다. 배열 객체와 달리, 딕셔너리 객체는 순서가 없다. 따라서, 딕셔너리의 내용을 나열할 때, 맨 앞에 위치한 키-객체 짝이 가장 먼저 추출되지 않을수도 있다.


프로그램 15.15


#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSDictionary *glossary =
        [NSDictionary dictionaryWithObjectsAndKeys:
            @"A class defined so other classes can inherit from it",
            @"abstract class",
            @"To implement all the methods defined in a protocol",
            @"adopt",
            @"Storing an object for later use",
            @"archiving",
            nil
    ];

    // 딕셔너리의 모든 키-값 짝을 표시한다.

    for ( NSString *key in glossary )
        NSLog (@"%@%@", key, [glossary objectForKey: key]);

    [pool drain];
    return 0;
}

프로그램 15.15 의 출력결과


abstract class: A class defined so other classes can inherit from it
adopt: To implement all the methods defined in a protocol
archiving: Storing an object for later use


dictionaryWithObjectsAndKeys: 메서드로 넘어기는 인수는 각각 쉼표로 구분되는 객체-키 짝(이 순서대로)의 목록이다. 이 목록은 특별한 객체 nil 로 마쳐야만 한다.

프로그램이 딕셔너리를 생성하고 나면, 반복문에서 딕셔너리의 내용물을 열거한다. 이미 언급했듯이 딕셔너리에서 매번 받는 키들은 따로 순서가 없다. 딕셔너리 내용이 알파벳 순서대로 표시되게 하려면 딕셔너리의 모든 키를 받은 뒤, 정렬하고, 순서대로 그 정렬된 키에 대한 값을 받아 오면 된다. 이때 keySortedByValueUsingSelector: 를 이용하면 이 작업이 반 정도 처리된다. 이 메서드는 지정한 정렬 범주에 따라 정렬된 키를 배열로 반환해준다.

지금까지 딕셔너리를 다루는 기본적인 방법을 설명했다. 표 15.6 과 표 15.7 은 각각 수정 불가능한 딕셔너리와 수정 가능한 딕셔너리에서 자주 사용되는 메서드를보여 준다. NSMutableDictionaty 는 NSDictionary 의 서브클래스이므로 메서드를 상속받는다.

표 15.6 과 표 15.7 에서 key, key1, key2, obj, obj1, obj2 는 모두 객체이며 size 는 NSUInteger unsigned 정수다.

메서드 설명
+(id) dictionaryWithObjectsAndKeys:
obj1, key1, obj2, key2, ..., nil
키-객체 짝으로 딕셔너리를 생성한다. {key1, obj1}, {key2, obj2}, ...
-(id) initWithObjectsAndKeys: obj1,
key1, obj2, key2, ..., nil
새로 생성된 딕셔너리를 키-객체 짝으로 초기화한다. {key1, obj1}, {key2, obj2}, ...
-(unsigned int) count 딕셔너리에 들어 있는 엔트리 개수를 반환한다.
-(NSEnumerator *) keyEnumerator 딕셔너리의 모든 키에 해당하는 NSEnumerator객체를 반환한다.
-(NSArray *)
keysSortedByValueUsingSelector:
(SEL) selector
selector가 지정하는 비교 메서드에 따라 정렬된, 딕셔너리의 키 배열을 반환한다.
-(NSEnumerator *) objectEnumerator 딕셔너리의 모든 값에 해당하는 NSEnumerator객체를 반환한다.
-(id) objectForKey: key 지정한 key에 해당하는 객체를 반환한다.
표 15.6 자주 사용되는 NSDictionary 메서드


메서드 설명
+(id) dictionaryWithCapacity: size 초기 크기가 size인 수정 가능한 딕셔너리를 생성한다.
-(id) initWithCapacity: size 생성된 딕셔너리의 초기 크기를 size로 초기화한다.
-(void) removeAllObjects 딕셔너리에 든 모든 엔터리를 삭제한다.
-(void) removeObjectForKey: key 지정한 key에 해당하는 엔트리를 딕셔너리에서 삭제한다.
-(void) setObject: obj forKey: key 딕셔너리에 key 키로 obj를 추가한다. 만일 key가 존재하면 값을 대체한다.
표 15.7 자주 사용되는 NSMutableDictionary 메서드


세트 객체

'세트'는 유일(unique)한 객체들의 모음이며, 이 세트는 수정 가능하거나 불가능할 수 있다. 세트로는 멤버를 검색하고, (수정 가능한 세트의 경우) 추가 또는 삭제하는 일, 두 세트를 비교 혹은 교차, 결합할 수 있다.

세트를 프로그램에서 사용하려면 다음 코드를 추가한다.

#import <Foundation/NSSet.h>


프로그램 15.16 에 세트를 이용하는 기본적인 작업들이 나온다. 프로그램이 실행되는 동안 세트 내용을 여러 번 표시하고 싶다고 해보자. 그래서 printPtr 는 새 메서드를 만들기로 결정한다, Printing 이라는 카테고리를 새로 만들어 NSSet 클래스에 print 메서드를 추가한다" NSMutableSet 는 NSSet 의 서브클래스이므로, 수정 가능한 세트 역시 새 print 메서드를 사용할 수 있다.



프로그램 15.16


#import <Foudnation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foudnation/NSValue.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSString.h>

// 정수 객체를 생성한다.
#define INTOBJ(v) [NSNumber numberWithInteger: v]

// Printing 카테고리로 NSSet에 print 메서드를 추가한다.

@interface NSSet (Printing)
-(void) print;
@end

@implementation NSSet (Printing)
-(void) print {
    printf ("{");

    for (NSNumber *element in self)
        printf ("%li ", (long) [element integerValue]);

    printf ("}\n");
}
@end

int main (int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSMutableSet *set1 = [NSMutableSet setWithObjects:
        INTOBJ(1), INTOBJ(3), INTOBJ(5), INTOBJ(10), nil];
    NSSet *set2 = [NSSet setWithObjects:
        INTOBJ(-5), INTOBJ(100), INTOBJ(3), INTOBJ(5), nil];
    NSSet *set3 = [NSSet setWithObjects:
        INTOBJ(12), INTOBJ(200), INTOBJ(3), nil];

    NSLog (@"set1: ");
    [set1 print];
    NSLog (@"set2: ");
    [set2 print];

    // 일치 여부 확인
    if ([set1 isEqualToSet: set2] == YES)
        NSLog (@"set 1 equals set2");
    else
        NSLog (@"set1 is not equal to set2");

    // 멤버쉽 테스트

    if ([set1 containsObject: INTOBJ(10)] == YES)
        NSLog (@"set1 contains 10");
    else
        NSLog (@"set1 does not contain 10");

    if ([set2 containsObject: INTOBJ(10)] == YES)
        NSLog (@"set2 contains 10");
    else
        NSLog (@"set2 does not contain 10");

    // 수정 가능한 세트 set1에서 객체 추가하고 제거한다.
    [set1 addObject: INTOBJ(4)];
    [set1 removeObject: INTOBJ(10)];
    NSLog (@"set1 after adding 4 and removing 10: ");
    [set1 print];

    // 두 개의 세트의 교차 구하기

    [set1 intersectSet: set02];
    NSLog (@"set1 intersect set2: ");
    [set1 print];

    // 두 개의 세트의 합 구하기

    [set1 unionSet:set3];
    NSLog (@"set1 union set3: ");
    [set1 print];

    [pool drain];
    return 0;
}

프로그램 15.16 의 출력결과


set1:
{ 3 10 1 5 }
set2:
{ 100 3 -5 5 }
set 1 is not equal to set2
set1 contains 10
set2 does not contain 10
set1 after adding 4 and removing 10:
{ 3 1 5 4 }
set 1 intersect set2:
{ 3 5 }
set 1 union set3:
{ 12 3 5 200 }


print 메서드는 앞에서 설명했던 빠른 열거 기법으로 세트에서 각 원소를 받아온다. 또한 INTOBJ 매크로를 정의하여 정수에서 객체를 생성해 내도록 한다. 이를 통해 프로그램이 좀더 간결해지고 불필요한 타이핑을 줄일 수 있다. 물론 print 메서드는 정수 멤버만 보유한 세트에만 동작하므로, 일반적이지는 않다. 그렇지만 이 메서드로 카테고리[5]를 통해 클래스에 메서드를 추가하는 방법을 다시 살피게 되니, 매우 좋은 예가 된다(print 메서드에서 C 라이브러리의 printf 를 사용하여 세트의 원소를 한줄에 표시했다).

setWithObjects: 는 nil 로 끝나는 객체 목록에서 새 세트를 생성한다. 세트를 세 개 만든 후, 프로그램은 print 메서드를 사용하여 처음 두 세트를 표시한다. 그 후 setEqualToSet: 를 사용하여 set1 과 set2 가 같은지를 비교한다. 당연히 서로 다르다.

containsObjects: 메서드를 사용하여 먼저 정수 10 이 set1 에 들었는지 확인하고, 그 다음 set2 에 들어 있는지도 확인한다. 메서드가 반환하는 불리언 값을 보니 첫번째 세트에는 정수 10이 있고 두 번째 세트에는 없다.

그 다음으로 프로그램은 addObjects: 와 removeObjects: 메서드를 사용하여 set1 에서 4 를 추가하고 10 을 제거한다. 세트 내용을 표시하여 이 작업들이 성공했음을 확인한다.

intersect: 와 union: 메서드를 쓰면 두 세트의 교차와 합을 계산할 수 있다. 둘 다 연산결과가 메시지의 수신자를 대치한다.

Foundation 프레임워크에는 NSCounterdSet 이라는 클래스도 있다. 이 세트들은 동일한 객체가 한 번 이상 나타나는 것을 표현할 수 있다. 그러나 세트에서 여러 번 나타나는 객체를 관리하는 게 아니라, 객체가 나타나는 횟수를 관리한다. 따라서, 객체가 세트에 처음 추가될 때, 횟수는 1 이다. 이후에 이 세트에 객체를 추가하면 횟수가 증가되고, 객체를 제거하면 횟수가 감소된다. 만일 횟수가 0 이면, 세트에서 실제 객체 자체가 제거된다. countForObject: 는 세트에서 지정된 객체의 횟수를 반환한다.

NSCounterdSet 를 사용하는 예로 단어를 세는 프로그램이 있다. 텍스트에서 특정단어가 검색될 때마다 카운트 세트에 단어 횟수가 추가된다. 텍스트 스캔이 완료되면 세트에서 각 단어와 횟수를 받아 텍스트에서 해당 단어가 몇 번 나타났는지 알아낼 수 있다.

이제까지 세트로 할 수 있는 기본적인 작업의 일부만 살펴보았다. 표 15.8 과 표15.9 에 수정 불가능한 세트와 가능한 세트에서 자주 사용되는 메서드를 정리했다. NSMutableSet 는 NSSet 의 서브클래스이므로 메서드를 상속받는다.

표 15.8 과 표 15.9 에서 obj, obj1, obj2 는 객체이며, nsset 는 NSSet 객체이거나 NSMutableSet 객체다. 그리고 size 는 NSUInteger 정수다.

메서드 설명
+(id) setWithObjects: obj1, obj2, ..., nil 객체 목록에서 새 세트를 생성한다.
-(id) initWithObjects: obj1, obj2, ..., nil 새로 생성된 세트를 객체 목록으로 초기화한다.
-(NSUInteger) count 세트에 담긴 멤버 개수를 반환한다.
-(BOOL) containsObject: obj 세트에 obj가 들어 있는지 아닌지를 반환한다.
-(BOOL) member: obj 세트에 obj가 들어 있는지 아닌지를 반환한다(isEqual: 메서드를 사용한다).
-(NSEnumerator *) objectEnumerator 세트의 모든 객체에 대한 NSEnumerator를 반환한다.
-(BOOL) isSubsetOfSet: nsset nsset에 수신자의 모든 멤버가 있는지 아닌지를 반환한다.
-(BOOL) intersectsSet: nsset nsset에 수신자의 멤버 가운데 단 한 개라고 들어 있는지 아닌지를 반환한다.
-(BOOL) isEqualToSet: nsset 두 세트의 동일성 여부를 반환한다.
표 15.8 자주 사용되는 NSSet 메서드


메서드 설명
-(id) setWithCapacity: size 멤버를 size만큼 저장할 수 있는 초기 용량으로 세트를 새로 생성한다.
-(id) initWithCapacity: size 새로 생성된 세트의 초기 크기를 size만큼으로 초기화한다.
-(void) addObject: obj 세트에 obj를 추가한다.
-(void) removeObject: obj 세트에서 obj를 삭제한다.
-(void) removeAllObjects 세트에 들어있는 모든 멤버를 제거한다.
-(void) unionSet: nsset nsset에 있는 각 멤버를 수신자에 추가한다.
-(void) minusSet: nsset nsset의 모든 멤버를 수신자에서 제거한다.
-(void) intersectSet: nsset nsset에 없는 수신자 멤버를 모두 제거한다.
표 15.9 자주 사용되는 NSMutableSet 메서드


연습문제

1. 레퍼런스 문서에서 NSCalendarDate 클래스를 살펴본 다음, NSCalendarData 의 새 카테고리 ElapsedDays 를 추가하라. 새 카테고리에서 다음 메서드 선언에 따라 메서드를 추가하자.

-(unsigned long) numberOfElapsedDays: (NSCalendarDate *) theDate;

새 메서드는 수신자와 메서드의 인수 사이에 며칠이 지났는지 반환한다. 테스트 프로그램을 작성하여 새 메서드를 테스트해 보자.

힌트! years:months:days:hours:minutes:seconds:sinceDate: 메서드를 보라.


2. 이 장의 AddressBook 클래스에서 만든 lookup: 메서드를 수정하여 이름을 부분 검색할 수 있도록 해보자. 다시 말해서 {myBook lookup: @"steve"] 이라는 표현식은 이름의 어느 부분에 steve 라는 스트링이 있든 이 스트링을 포함한 카드를 검색해야 한다.


3. 이 장의 AddressBook 클래스에서 만든 lookup: 메서드를 수정해 주소록에서 일치하는 모든 검색 결과를 찾도록 만들자. 메서드를 고쳐서, 일치하는 주소 카드를 모두 배 열로 반환하도록 만든다. 일치하는 카드가 없을 때는 nil 을 반환한다.


4. AddressCard 클래스에 원하는 필드를 추가하자. 예를 들어, name을 성과 이름 필드로 분리하거나, 주소(국가, 시, 군, 구, 우편번호 퉁으로 분리), 전화번호 필드등을 추가할 수 있다. 적절한 세터와 게터 메서드를 작성하고 print 와 list 메서드에서 필드를 표시하자.


5. 연습문제 4를 완성한 다음에, 연습문제 2의 lookup: 메서드를 사용하여 주소록의 모든 필드를 검색할 수 있도록 만들어 보자 AddressBook 클래스가 AddressCard 에 저장된 필드를 모두 알지 않아도 되도록 디자인하려면 어떤 방법이 있을까?


6. 다음 메서드 선언에 맞춰 removeName: 메서드를 AddressBook 클래스에 추가한다. 그리하여 주소록에서 사람을 삭제할수 있도록 만들자.

-(BOOL) removeName : (NSString *) theName;


연습문제 2 에서 만든 lookup: 메서드를 사용하자. 만일 이름이 발견되지 않거나, 검색된 결과가 많을 경우 메서드는 NO 를 반환한다. 만일 무사히 삭제하면 YES 를 반환한다.


7. 1 부에서 정의한 Fraction 클래스를 사용하여 아무 값이나 갖는 분수의 배열을 만들자. 그 후 배열에 저장된 모든 분수의 합을 구하는 코드를 작성하자.


8. 1 부에서 정의한 Fraction 클래스를 사용해 임의의 값을 가지는 분수의 수정 가능한 배열을 만들자. 그 후 NSMutableArray 클래스의 sortUsingSelector: 메서드를 사용하여 배열을 정렬하라. Fraction 클래스에 Comparison 카테고리를 추가하고, 여기에 비교 메서드를 구현하라.


9. Song, Playlist, MusicCollection 이라는 세 클래스를 새로 정의하자. Song 객체는 제목, 아티스트, 앨범, 재생시간 같은 특정 노래의 정보를 담는다. Playlist 객체에는 재생목록의 이름과 곡 모음을 담을 것이다. MusicCollection 객체에는 재생목록 모음과 컬렉션에 있는 모든 곡을 전부 담는 특별한 마스터 재생목록 library 가 담긴다. 이 세 클래스를 정의하고 다음 작업을 수행하는 메서드들을 작성하라 .

  • Song 객체를 생성하고 정보를 설정한다 .
  • Playlist 객체를 생성하고 재생목록에 곡을 추가하거나 제거한다. 마스터 재생목록에 새 곡이 포함되지 않았다면 이를 추가한다. 만일 곡이 마스터 재생목록에서 제거되면, 해당 음악 컬렉션의 다른 재생목록들 에서도 모두 제거되어야 한다.
  • MusicCollection 객체를 새로 생성하고 컬렉션에 재생목록을 추가하거나 제거한다.
  • 곡, 재생 목록, 전체 음악 컬렉션에 대한 정보를 찾고 표시한다.


모든 클래스에서 메모리 누수가 발생하지 않도록 하자!


10. 정수 값을 갖는 NSNumber 객체들을 NSArray 배열에 넣고 각 정수와, 그 정수가 배열에 나타나는 횟수를 적은 빈도수 차트를 작성하라. NSCounterSet 객체를 사용하여 빈도수를 얻어 내라.


Notes

  1. 현재 unichar 문자는 16비트를 차지하지만, 유니코드 표준은 이보다 큰 문자도 지원한다. 따라서 , 앞으로는 unichar 문자가 16비트보다 커질 가능성도 었다. 중요한 것은, 유니코드 문자의 크기를 가정해서는 안된다는 점이다.
  2. Mac OS X 에서 제공하는 Address Book 프레임워크에는 주소록을 다루는 데 필요한 강력한 기능들이 있다.
  3. 초기화 메서드 initWithName:andEmail: 도 추가하면 편리하겠지만, 여기서는 다루지 않는다.
  4. sortUsingFunction:context: 메서드를 사용하면 메서드 대신 함수로 비교할 수 있다.
  5. 더 일반적인 메서드에서는 각 객체의 description 을 호출하여 세트의 각 멤버를 표시할 수 있다. 이렇게 하면, 세트에 포함된 객체 형이 무엇인지에 상관없이 읽을수 있는 형식으로 표시된다. 또한 '객체 출력' 포맷 문자인 "%@" 를 사용하여 NSLog 를 한번만 호출하는 것으로 모든 컬렉션의 내용을 표시해줄 수 있다.