Using Predicates

OSXDEV

Jump to: navigation, 찾기

Predicates Programming Guide로 이동


이 문서는 일반적으로 프리디케이트를 사용하는 법을 보여주고, 프리디케이트를 사용하는 것이 어플리케이션 데이터의 구조에 어떤 영향을 미치는지를 설명한다.

목차

[편집] Evaluating Predicates

프리디케이트를 평가하기 위해, NSPredicate의 메소드인 evaluteWithObject:를 이용해서 프리디케이트를 이용해 평가될 오브젝트에 전한다. 이 메소드는 Boolean값을 리턴한다 - 다음 예에서 결과값은 YES이다.

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF IN %@", [NSArray arrayWithObjects:@"Stig", @"Shaffiq", @"Chris", nil]];
BOOL result = [predicate evaluateWithObject:@"Shaffiq"];

어떤 클래스의 오브젝트에 대해서도 프리디케이트를 사용할 수 있지만, 프리디케이트에서 사용하길 원하는 그 클래스가 키-값 코딩을 지원해야 한다.

[편집] Using Predicates with Arrays

NSArray와 NSMutableArray는 어레이 컨텐트를 필터할 수 있는 메소드를 제공한다. NSArray는 리시버에의 오브젝트들 중 지정한 프리디케이트에 일치하는 오브젝트를 포함하는 새로운 어레이를 리턴하도록 하는 filteredArrayUsingPredicate:를 제공한다. NSMutableArray는 리시버의 컨텐트를 상대로 지정한 프리디케이트를 평가하고일치하는 오브젝트만 남기는 filterUsingPredicate:를 제공한다.

NSMutableArray *array = [NSMutableArray arrayWithObjects:@"Bill", @"Ben", @"Chris", @"Melissa", nil];
 
NSPredicate *bPredicate = [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
NSArray *beginWithB = [array filteredArrayUsingPredicate:bPredicate];
// beginWithB contains { @"Bill", @"Ben" }.
 
NSPredicate *sPredicate = [NSPredicate predicateWithFormat:@"SELF contains[c] 's'"];
[array filterUsingPredicate:sPredicate];
// array now contains { @"Chris", @"Melissa" }

코어 데이터 프레임워크를 사용한다면, 어레이 메소드는 - 패치가 하는 것처럼 - 퍼시스턴트 persistent 데이터 스토어에 대한 왕복 없이현존하는 어레이의 오브젝트를 필터링하는 효과적인 수단을 제공한다.


[편집] Using Predicates with Relationships

키패스를 이용해 프리디케이트에서 릴레이션십을 따라갈 수 있음을 상기하자. 다음의 예는 주어진 이름의 부서에 속하는 종업원을 찾아내는 프리디케이트의 생성을 보여준다 (“Performance”도 참조한다).

NSString *departmentName = ... ;
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"department.name like %@", departmentName];

일 대 다수의 릴레이션십을 사용한다면, 프리디케이트를 만드는 것이 약간 달라진다. "Matthew"라는 이름을 가진 종업원이 최소한 한 명이라도 있는 부서를 패치하려 한다면, 다음 예에서 처럼 ANY 연산자를 사용한다:

NSPredicate *predicate = [NSPredicate predicateWithFormat: @"ANY employees.firstName like 'Matthew'"];

모든 종업원들이 일정 금액 이상을 받는 부서를 찾는다면, 다음 예제처럼 ALL연산자를 사용한다

float salary = ... ;
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"ALL employees.salary > %f", salary];

[편집] Null Values

비교comparison 프리디케이트는 NSNull 널 값이나 null(nil)을 제외한 어떠한 null 값과도 일치하지 않는다( 즉, ($value == nil)은 $value가 nil이면 YES를 리턴한다). 다음의 예를 생각해 보자.

NSString *firstName = @"Ben";
 
NSMutableArray *array = [NSMutableArray array];
[array addObject: [NSDictionary dictionaryWithObject:@"Turner" forKey:@"lastName"]];
[array addObject: [NSDictionary dictionaryWithObjectsAndKeys:@"Ben", @"firstName", @"Ballard", @"lastName",
[NSDate dateWithString:@"1972-03-24 10:45:32 +0600"], @"birthday", nil]];
 
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"firstName like %@", firstName];
NSArray *filteredArray = [array filteredArrayUsingPredicate:predicate];
 
NSLog(@"filteredArray: %@", filteredArray);
// Output:
// filteredArray ({birthday = 1972-03-24 10:45:32 +0600; \\
     firstName = Ben; lastName = Ballard; })

프리디케이트는 firstName키로 Ben이라는 값을 가지고 있는 딕셔너리와 일치하지만 firstName키에 아무 값이 없는 딕셔너리와는 일치하지 않는다. 다음의 코드조각은 날짜와 ~보다 큰 비교자의 유사한 활용법을 보여준다.

NSDate *referenceDate = [NSDate dateWithTimeIntervalSince1970:0];
 
predicate = [NSPredicate predicateWithFormat:@"birthday > %@", referenceDate];
filteredArray = [array filteredArrayUsingPredicate:predicate];
 
NSLog(@"filteredArray: %@", filteredArray);
// Output:
// filteredArray: ({birthday = 1972-03-24 10:45:32 +0600; \\
                       firstName = Ben; lastName = Ballard; }) 
[편집] Testing for Null

null값에 매칭시키기를 원한다면, 다음의 코드 조각들 처럼 일반적인 비교구문에 특정한 테스트를 더해줘야 한다.

predicate = [NSPredicate predicateWithFormat:@"(firstName like %@) || (firstName = nil)", firstName];
filteredArray = [array filteredArrayUsingPredicate:predicate];
NSLog(@"filteredArray: %@", filteredArray);
 
// Output:
// filteredArray: ( { lastName = Turner; }, { birthday = 1972-03-23 20:45:32 -0800; firstName = Ben; lastName = Ballard; }

함축적으로, null이면 참 값을 리턴하도록 null에 대해 테스트하는 것이다. 다음의 코드조각에서, ok는 두개의 프리디케이트 평가 모두에 대해 YES로 설정된다.

BOOL ok;
predicate = [NSPredicate predicateWithFormat:@"firstName = nil"];
ok = [predicate evaluateWithObject:[NSDictionary dictionary]];
 
ok = [predicate evaluateWithObject: [NSDictionary dictionaryWithObject:[NSNull null] forKey:@"firstName"]];


[편집] Using Predicates with Cocoa Bindings

컨텐트 어레이를 필터하기 위해 어레이 컨트롤러를 위한 프리디케이트를 설정할 수 있다. (setFilterPredicate:를 이용해) 프리디케이트를 코드에서 설정할 수 있다. 어레이 컨트롤러의 filterPredicate 바인딩을 NSPredicate를 리턴하는 메소드에 바인드할 수도 있다. 메소드를 수행하는 오브젝트는 파일 소유권자 내지는 어떤 다른 컨트롤러 오브젝트일 것이다. 프리디케이트를 바꾸려 한다면, 어레이 컨트롤러가 정상적으로 스스로 업데이트 할 수 있도록 키-값 observing 호환방식으로 해야 한다는 것을 기억하라( Key-Value Observing Programming Guide를 보라.)

NSSearchField의 프리디케이트 바인딩을 어레이 컨트롤러의 filterPredicate로 바인드 할 수 있다. NSSearchField의 predicate바인딩은 Binding Types에 설명한 대로 다중-값 바인딩이기 때문이다.

[편집] Using Predicates with Core Data

Core Data 프레임워크를 사용한다면, 코어 데이터를 사용하지 않았을 때 하던 것과 동일한 방법으로 프리디케이트를 사용할 수 있다(예를 들어, 어레이를 필터하거나 어레이 컨트롤러를 사용하는). 게다가, 프리디케이트를 패치 요청서에 묶어서 사용할 수 있고 패치 요청서 탬플리트를 매니지드 오브젝트 모델에 저장할 수도 있다(Managed Object Models를 보라.)

"임의의"SQL 쿼리를 프리디케이트나 패치 요청서로 바꿔야할 필요는 없다는 점을 주의하라. SELECT name, sum(value) FROM ... GROUP BY name 과 같은 SQL 구뭄ㄴ을 패치 요청서로 바꿀 수 있는 방법도 없다. 관심있는 오브젝트를 패치한 후, 그 결과를 바로 이용해 계산을 수행하거나 어레이 연산자를 사용한다.

[편집] Fetch Requests

타겟 엔티티의 프라퍼티에 맞추기 위해 프리디케이트를 만들 수 있으며 그 프리디케이트를 패치 요청서와 연관지을 수 있다. 요청서가 실행되면, 프리디케이트에서 지정된 범주에 맞는 오브젝트를(있다면) 포함하고 있는 어레이가 리턴된다. 다음 예는 지정된 금액보다 많이 버는 종업원을 찾는 프리디케이트의 사용법을 보여준다.

NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Employee"
        inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// assume salaryLimit defined as an NSNumber variable
NSPredicate *predicate = [NSPredicate predicateWithFormat:
        @"salary > %@", salaryLimit];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
[편집] Object Controllers

코코아 바인딩을 사용하고 있다면, 오브젝트 컨트롤러(NSObjectController나 NSArrayController의 인스턴스 같은)를 위한 패치 프리디케이트를 지정할 수 있다. 인터페이스 빌더의 인스펙터 패널의 프리디케이트 편집기 텍스트 필드에서 setFetchPredicate:를 이용해 프리디케이트의 타입을 직접 지정할 수 있다. 프리디케이트는 컨트롤러가 패치를 실행했을 때 리턴되는 결과를 묶기 위해 사용된다. NSObjectController 오브젝트를 사용하고 있다면, 컨트롤러의 컨텐트로 사용하고 싶은 오브젝트를 구별해 내는 패치를 지정한다 -예를 들어, 컨트롤러의 엔티티가 부서Department 라면, 프리디케이트는 name like "Engineering" .

[편집] Regular Expressions

MATCHES 연산자는 다음 예에서 처럼, ICU's Regular Expressions package를 사용한다.

NSArray *array = [NSArray arrayWithObjects:
    @"TATACCATGGGCCATCATCATCATCATCATCATCATCATCATCACAG",
    @"CGGGATCCCTATCAAGGCACCTCTTCG", @"CATGCCATGGATACCAACGAGTCCGAAC",
    @"CAT", @"CATCATCATGTCT", @"DOG", nil];
 
// find strings that contain a repetition of at least 3 'CAT' sequences,
// but not followed by a further 'CA'
NSPredicate *catPredicate =
    [NSPredicate predicateWithFormat:@"SELF MATCHES '.*(CAT){3,}(?!CA).*'"];
 
NSArray *filteredArray = [array filteredArrayUsingPredicate:catPredicate];
// filteredArray contains just 'CATCATCATGTCT'

ICU 명세에 따르면, 일반적은 공식 메타캐릭터들은 패턴 세트 내에서는 사용할 수 없다. 예를 들어, 일반적인 공식 \d{9}[\dxX]는 사용가능한 ISBN 숫자(10자리 이진수 또는 9자리 이진수와 문자 'X')와 패턴 세트 ([\dxX])가 메타캐릭터 (\.d)를 포함하기 때문에 일치하지 않는다. 아니면 다음 코드 예제에서 처럼 OR 공식을 사용할 수도 있다.

NSArray *isbnTestArray =
    [NSArray arrayWithObjects:@"123456789X", @"987654321x",
        @"1234567890", @"12345X", @"1234567890X", nil];
NSPredicate *isbnPredicate =
    [NSPredicate predicateWithFormat:@"SELF MATCHES '\\\\d{10}|\\\\d{9}[Xx]'"];
 
NSArray *isbnArray = [isbnTestArray filteredArrayUsingPredicate:isbnPredicate];
// isbnArray contains (123456789X, 987654321x, 1234567890)

[편집] Performance

작업의 량을 최소화 하기 위해 프리디케이트를 컴파운드compound한다. 일반적인 공식을 특별하게 매칭시키는 것은 낭비적인 연산이다. 컴파운드 프리디케이트에서, 일반적인 공식이전에 간단한 테스트를 수행해야 한다; 그러므로 다음 예와 같은프리디케이트를 사용하는 대신

NSPredicate *predicate = [NSPredicate predicateWithFormat: @"( title matches .*mar[1-10] ) OR ( type = 1 )"];

다음과 같이 해야 한다

NSPredicate *predicate = [NSPredicate predicateWithFormat: @"( type = 1 ) OR ( title matches .*mar[1-10] )"];

두번째 예에서, 일반적인 공식은 첫번째 구문이 false일 때에만 평가가 진행된다.

[편집] Joins

일반적으로, joins (릴레이션십들간의 쿼리)는 비싼 연산이며, 가능한한 피해야 한다. 일 대 일 릴레이션십을 테스트 할 때, 이미 릴레이션십 소스 오브젝트(또는 그 오브젝트 ID)를 가지고 있다면 - 또는 쉽게 얻어올 수 있다면, 오브젝트의 동일성을 테스트하는 것이 소스 오브젝트의 프라퍼티를 테스트 하는 것보다 효과적이다.

다음과 같이 하지 말고

NSPredicate *predicate = [NSPredicate predicateWithFormat: @"department.name like %@", [department name]];

이것이 더 효과적인 방법이다:

NSPredicate *predicate = [NSPredicate predicateWithFormat: @"department == %@", department];

프리디케이트가 하나 이상의 공식을 가지고 있다면, 이것은 전형적으로 join하지 않는 구조로 가는 게 효과적이다. 예를 들어, firstName startswith[cd[ 'Matt' AND (ANY directreports.paygrade <= 7)"@"(ANY directreports.patgrade <=7) AND (firstNAme startswith[cd] 'Matt')"보다 첫번째 테스트가 성공적이지 않는 이상 join을 만들지 않을 거이므로 더 효과적일 것이다.

[편집] Structuring Your Data

어떤 상황에서, 데이타의 표현과 프리디케이트의 사용사이에는 긴장이 있을 수 있다. 당신의 어플리케이션에서 프리디케이트를 사용할 생각이 있다면, 전형적인 쿼리 오퍼레이션의 패턴이 당신의 데이터를 구조화하는데 영향을 줄 수 있다. 코어데이타에서, 엔티티와 엔티티-클래스 매핑을 지정했다 하더라도, 퍼시스턴트 스토어에서 밑에 깔리는 구조를 만드는 단계는 불투명하다. 그럼에도 불구하고, 여전히 엔티티와 그들이 가지는 프라퍼티들에 대한 컨트롤은 가지고 있다.

낭비적인 경향에 있어서, join 또한 유연성을 제한할 수도 있다. 그러므로, 당신의 데이타를 de-normalize하는 것이 적당하다. 일반적으로-쿼리가 자주 발행된다고 가정할 때- 올바른 것을 찾는게 쉬워진다는 장점이 없다면, 큰 오브젝트를 가지는 게 좋은 거래일 수 있다 (메모리에서도 적게 차지한다)