본문 바로가기

학습 노트/iOS (2021)

191. Predicate Syntax

Predicate Syntax


NSPredicate(format: String, arguments: CVaListPointer)

NSPredicate(format: "target LIKE %@", "value")

여러 생성자 중 위의 생성자의 사용 빈도가 가장 높다.

pram1 : Format 문자열 : 해당 문자열을 사용해 검색 조건을 지정
"targer LIKE %@"은 target 속성에 저장된 문자열을 검색한다.
LIKE는 문자열 비교에 사용되는 연산자이고, Format 문자열 자체는 대소문자를 가리지 않지만
LIKE와 같이 연산자 등에 해당하는 키워드들은 대문자로 작성하는 것이 관행이다.
두 개 이상의 공백은 하나로 취급한다.
%@는 포맷 지정자로 Swift의 것과 동일하다.

param2 : 포맷 지정자의 수만큼 전달 가능한 가변 파라미터

NSPredicate(block: (Any?, [String : Any]?) -> Bool)

위의 생성자는 inmemory 저장소와 Atomic 저장소에 제한적으로 사용되며 CoreData에서는 사용할 수 없다.

 

Literals

Boolean

YES, TRUE, 1 NO, FALSE, 0
NSPredicate(format: "YES == TRUE")

NSPredicate(format: "NO == FALSE")

YES, TRUE, NO, FALSE를 모두 사용할 수 있고,
0과 1고 Boolean으로 사용할 수 있지만 권장되지는 않는다.

nil Check

NSPredicate(format: "NULL == NIL")

NULL과 NIL을 모두 사용할 수 있다.
NULL은 NIL로 치환돼 사용된다.

Number

NSPredicate(format: "age > 30")

숫자는 별도의 인용 부호 없이 그대로 표기해 사용한다.

String

NSPredicate(format: "name == 'Walmart'")

NSPredicate(format: "name == \"Walmart\"")

문자열은 작은 따옴표나 큰 따옴표로 감싸 사용한다.
이 중 큰 따옴표는 '\'와 함께 사용해야 한다.

Array

NSPredicate(format: "value IN {1, 2, 3, 4}")

배열은 '{ }'로 감싸 표기한다.
Collection 연산자와 함께 사용되고, 배열 내의 인자들은 '.'로 구분한다.

 

Format Specifiers

%K, %@

NSPredicate(format: "%K CONTAINS %@", "name", "name")
결과

name CONTAINS "name"

%K는 속성의 이름을 동적으로 전달할 때 사용한다.
%@은 검색할 값을 전달할 때 사용한다.

따라서 %K는 인용 구호 없이, %@은 인용 구호를 포함해 사용됐다.

NSPredicate(format: "age >= %@", 30)

%@은 문자열과 참조 형식을 대체하고, 숫자 등의 값 형식을 대체하지는 못한다.
따라서 바로 위의 예시는 잘못된 예시이다.

NSPredicate(format: "age >= %@", NSNumber(value: 30))

만약 %@을 사용해 값형식을 대체해야 할 때는 위와 같이 NSNumber 등의 클래스를 사용해 전달해야 한다.

%d, %i

NSPredicate(format: "age >= %d", 30)

%d와 %i는 숫자 그대로를 전달할 수 있다.
클래스를 사용해 전달하는 것이 번거롭기 때문에 위와 같이 %d나 %i로 전달하는 경우가 많다.

 

Evaluating Predicates

Predicate는 주로 Collection이 제공하는 filtering 메서드에 전달되거나 Core Data의 fetch Request에 전달된다.
하지만 Predicate 자체를 직접 평가하는 것도 가능하다.

let list = ["Apple", "Google", "MS"]
var predicate = NSPredicate(format: "SELF IN %@", list)
var ok = predicate.evaluate(with: "Apple")
결과

true

생성된 Predicate를 조건으로 사용해 판단했다.

SELF 키워드는 evaluate 메서드에 전달된 파라미터로 대체되며
이에 따라 list 배열에 대체된 문자열이 존재하는 경우 참으로 평가한다.

predicate = NSPredicate(format: "SELF.revenue > 500000")
ok = predicate.evaluate(with: companies.firstObject)
결과

true

지금과 같이 객체를 직접 전달하는 경우 SELF 키워드를 생략하기도 한다.

predicate = NSPredicate(format: "revenue > 500000")

둘 다 문제없이 작동한다.

 

Comparisons

predicate에서 사용되는 비교 연산자는 Swift의 것과 동일하다.

Equal To (=, ==)

'='와 '=='는 동일한 의미를 갖는다.
동일성을 비교하며, 혼란을 막기 위해 '=='를 주로 사용한다.

var predicate = NSPredicate(format: "SELF == %d", 2)
var result = list.filtered(using: predicate)
결과

[2]

NSArray의 filtered 메서드를 사용한다. 파라미터로 predicate를 전달하면 해당 조건을 사용해 데이터를 추출한다.

Not Equal To (!=, <>)

'!='와 '<>'는 동일한 의미를 갖지만 혼란을 막기 위해 '!='를 주로 사용한다.

predicate = NSPredicate(format: "SELF != %d", 2)
result = list.filtered(using: predicate)
결과

[1, 3, 4, 5, 6, 7, 8, 9]

Greater Than (>)

predicate = NSPredicate(format: "SELF > %d", 5)
result = list.filtered(using: predicate)
결과

[6, 7, 8, 9]

Greater Than Or Equal To (>=, =>)

'>='와 '=>'는 동일한 의미를 갖지만 혼란을 막기 위해 '>='를 주로 사용한다.

predicate = NSPredicate(format: "SELF >= %d", 5)
result = list.filtered(using: predicate)
결과

[5, 6, 7, 8, 9]

Less Than (<)

predicate = NSPredicate(format: "SELF < %d", 5)
result = list.filtered(using: predicate)
결과

[1, 2, 3, 4]

Less Than Or Equal To (<=, =<)

'<='와 '=<'는 동일한 의미를 갖지만 혼란을 막기 위해 '<='를 주로 사용한다.

predicate = NSPredicate(format: "SELF <= %d", 5)
result = list.filtered(using: predicate)
결과

[1, 2, 3, 4, 5]

BETWEEN

비교 대상이 지정된 범위에 속하는지를 판단한다.
범위는 배열로 전달한다.

predicate = NSPredicate(format: "SELF BETWEEN {3, 7}")
result = list.filtered(using: predicate)
결과

[3, 4, 5, 6, 7]

 

String Comparisions

predicate 자체는 대소문자를 구분하지 않지만 문자열 비교 시에는 대소문자를 포함해 악센트까지 구별한다.

BEGINSWITH

비교 대상으로 시작하는 문자열을 검색한다.

var predicate = NSPredicate(format: "%K BEGINSWITH %@", #keyPath(Company.name), "America")
companies.filtered(using: predicate)
결과

[{NSObject, rank 71, name "American Airlines Group", revenue 42207},
{
NSObject, rank 86, name "American Express", revenue 35583}]

접두어를 비교해 'America'로 시작하는 사명을 반환한다.

predicate = NSPredicate(format: "%K BEGINSWITH %@", #keyPath(Company.name), "america")
companies.filtered(using: predicate)
결과

[]

문자열 비교에는 대소문자를 구분하기 때문에 'america'로 시작하는 사명을 찾을 수 없다.

ENDSWITH

비교 대상으로 끝나는 문자열을 검색한다.

predicate = NSPredicate(format: "%K ENDSWITH %@", #keyPath(Company.name), "Group")
companies.filtered(using: predicate)
결과

[{NSObject, rank 5, name "UnitedHealth Group", revenue 201159},
{NSObject, rank 68, name "Liberty Mutual Insurance Group", revenue 42687},
{NSObject, rank 70, name "Goldman Sachs Group", revenue 42254},
{NSObject, rank 71, name "American Airlines Group", revenue 42207}]

이름이 'Group'으로 끝나는 사명을 반환한다.

COTNAINS

문자열의 포함 관계를 비교한다.

predicate = NSPredicate(format: "%K CONTAINS %@", #keyPath(Company.name), "America")
companies.filtered(using: predicate)
결과

[{NSObject, rank 24, name "Bank of America Corp.", revenue 100264},
{NSObject, rank 71, name "American Airlines Group", revenue 42207},
{NSObject, rank 86, name "American Express", revenue 35583}]

전달된 문자열이 포함되어 있으면 참으로 판단한다.
따라서 중간에 'America'가 들어간 데이터도 함께 검색된다.

LIKE

문자열 패턴 비교에 사용된다.

? : 하나의 문자를 표현한다.
* : 하나 이상의 문자를 표현하거나 없음을 의미한다.

predicate = NSPredicate(format: "%K LIKE %@", #keyPath(Company.name), "*Air*")
companies.filtered(using: predicate)
결과

[{NSObject, rank 71, name "American Airlines Group", revenue 42207},
{NSObject, rank 75, name "Delta Air Lines", revenue 41244}]

'Air'의 앞 뒤로 '*'이 추가돼 'Air'가 존재하기만 하면 검색된다.

predicate = NSPredicate(format: "%K LIKE %@", #keyPath(Company.name), "De*Air*")
companies.filtered(using: predicate)
결과

[{NSObject, rank 75, name "Delta Air Lines", revenue 41244}]

마찬가지로 'DE'로 시작하고 'Air'가 포함되는 사명이면 검색된다.

MATCHES

LIKE보다 복잡한 패턴 비교에 사용된다.
MATCHES는 inline memory에서만 지원한다.
따라서 Core Data에서는 사용할 수 없다.

let list = NSArray(array: ["010-1234-5678", "010-123-4567", "01012345678", "02-123-4567"])
predicate = NSPredicate(format: "SELF MATCHES '\\\\d{3}-\\\\d{3,4}-\\\\d{4}'")
list.filtered(using: predicate)
결과

["010-1234-5678", "010-123-4567"]

정규식으로 표현된 전화번호의 양식과 비교한다.

let regex = "\\d{3}-\\d{3,4}-\\d{4}"
predicate = NSPredicate(format: "SELF MATCHES %@", regex)
list.filtered(using: predicate)
결과

["010-1234-5678", "010-123-4567"]

predicate에서 정규식을 사용하는 경우 '\'의 수에 주의해야 한다.
predicate 내에서 직접 사용하는 경우 정규식 전체를 작은따옴표로 감싼 후 '\' 하나를 '\\\\'로 표현한다.
'%@'으로 정규식을 전달할 때는 문자열로 생성하며 '\' 하나를 '\\'로 표현한다.

Case Insensitive Search

문자열 비교 시 대소문자의 구분을 무시한다.

predicate = NSPredicate(format: "%K BEGINSWITH [c] %@", #keyPath(Company.name), "America")
companies.filtered(using: predicate)
결과

[{NSObject, rank 71, name "American Airlines Group", revenue 42207},
{NSObject, rank 86, name "American Express", revenue 35583}]
predicate = NSPredicate(format: "%K BEGINSWITH[c] %@", #keyPath(Company.name), "america")
companies.filtered(using: predicate)
결과

[{NSObject, rank 71, name "American Airlines Group", revenue 42207},
{NSObject, rank 86, name "American Express", revenue 35583}]

키워드의 뒤에 '[ ]'를 붙이고 안에 c를 추가해 '[ c ]'의 형태로 사용한다.
'[ ]' 내의 공백은 무의미하다.

Diacritic Insensitice Search

predicate = NSPredicate(format: "'cafe' == 'cafè'")
predicate.evaluate(with: nil)
결과

false

문자열 비교는 대소문자를 포함해 악센트까지 구별한다.
따라서 'cafe'와 'cafè'는 구분된다.

predicate = NSPredicate(format: "'cafe' ==[d] 'cafè'")
predicate.evaluate(with: nil)
결과

true

키워드의 뒤에 '[ ]'를 붙이고 안에 c를 추가해 '[ d ]'의 형태로 사용한다.
악센트를 무시하고 비교하기 때문에 'cafe'와 'cafè'는 구분되지 않는다.

predicate = NSPredicate(format: "%K BEGINSWITH [cd] %@", #keyPath(Company.name), "America")
companies.filtered(using: predicate)
결과

[{NSObject, rank 71, name "American Airlines Group", revenue 42207},
{NSObject, rank 86, name "American Express", revenue 35583}]

두 옵션은 함께 사용하는 것도 가능하다.

 

Compound Predicates

기존과 같이 하나의 조건으로 작동하는 predicate는 simple predicate라고 부른다.
논리 연산자를 사용해 두 개 이상의 조건으로 동작하는 경우 compound predicate라고 부르며,
사용하는 논리 연산자는 Swift와 동일하다.

AND, &&

모든 조건이 참인 경우 참으로 판단한다.
'AND'와 '&&' 모두 사용 가능하다.

var predicate = NSPredicate(format: "%K CONTAINS %@ AND %K > %d", #keyPath(Company.name), "Group", #keyPath(Company.revenue), 200000)
companies.filtered(using: predicate)
결과

[{NSObject, rank 5, name "UnitedHealth Group", revenue 201159}]

사명에 'Group'이 존재하고 revenue가 '200000' 이상인 회사만 검색된다.

OR, ||

조건 중 하나만 참이면 참으로 판단한다.
'OR'과 '||' 모두 사용 가능하다.

predicate = NSPredicate(format: "%K CONTAINS %@ OR %K > %d", #keyPath(Company.name), "Group", #keyPath(Company.revenue), 200000)
companies.filtered(using: predicate)
결과

[{NSObject, rank 1, name "Walmart", revenue 500343},
{NSObject, rank 2, name "Exxon Mobil", revenue 244363},
{NSObject, rank 3, name "Berkshire Hathaway", revenue 242137},
{NSObject, rank 4, name "Apple", revenue 229234},
{NSObject, rank 5, name "UnitedHealth Group", revenue 201159},
{NSObject, rank 68, name "Liberty Mutual Insurance Group", revenue 42687},
{NSObject, rank 70, name "Goldman Sachs Group", revenue 42254},
{NSObject, rank 71, name "American Airlines Group", revenue 42207}]

사명에 'Group'이 존재하거나 revenue가 '200000' 이상인 회사만 검색된다.

NOT, !

조건을 반전시킨다.
'NOT'과 '!' 모두 사용 가능하다.

predicate = NSPredicate(format: "NOT (%K CONTAINS %@ OR %K > %d)", #keyPath(Company.name), "Group", #keyPath(Company.revenue), 200000)
companies.filtered(using: predicate)
결과

[{NSObject, rank 6, name "McKesson", revenue 198533},
{NSObject, rank 7, name "CVS Health", revenue 184765},
{NSObject, rank 8, name "Amazon.com", revenue 177866},
{NSObject, rank 9, name "AT&T", revenue 160546},
{NSObject, rank 10, name "General Motors", revenue 157311},
{NSObject, rank 11, name "Ford Motor", revenue 156776},
{NSObject, rank 12, name "AmerisourceBergen", revenue 153144},
{NSObject, rank 13, name "Chevron", revenue 134533},
{NSObject, rank 14, name "Cardinal Health", revenue 129976},
{NSObject, rank 15, name "Costco", revenue 129025},
{NSObject, rank 16, name "Verizon", revenue 126034},
{NSObject, rank 17, name "Kroger", revenue 122662},
{NSObject, rank 18, name "General Electric", revenue 122274},
{NSObject, rank 19, name "Walgreens Boots Alliance", revenue 118214},
{NSObject, rank 20, name "JPMorgan Chase", revenue 113899},
{NSObject, rank 21, name "Fannie Mae", revenue 112394},
{NSObject, rank 22, name "Alphabet", revenue 110855},
{NSObject, rank 23, name "Home Depot", revenue 100904},
{NSObject, rank 24, name "Bank of America Corp.", revenue 100264},
{NSObject, rank 25, name "Express Scripts Holding", revenue 100064.6},
{NSObject, rank 26, name "Wells Fargo", revenue 97741},
{NSObject, rank 27, name "Boeing", revenue 93392},
{NSObject, rank 28, name "Phillips 66", revenue 91568},
{NSObject, rank 29, name "Anthem", revenue 90039.39999999999},
{NSObject, rank 30, name "Microsoft", revenue 89950},
{NSObject, rank 31, name "Valero Energy", revenue 88407},
{NSObject, rank 32, name "Citigroup", revenue 87966},
{NSObject, rank 33, name "Comcast", revenue 84526},
{NSObject, rank 34, name "IBM", revenue 79139},
{NSObject, rank 35, name "Dell Technologies", revenue 78660},
{NSObject, rank 36, name "State Farm Insurance Cos.", revenue 78330.8},
{NSObject, rank 37, name "Johnson & Johnson", revenue 76450},
{NSObject, rank 38, name "Freddie Mac", revenue 74676},
{NSObject, rank 39, name "Target", revenue 71879},
{NSObject, rank 40, name "Lowe’s", revenue 68619},
{NSObject, rank 41, name "Marathon Petroleum", revenue 67610},
{NSObject, rank 42, name "Procter & Gamble", revenue 66217},
{NSObject, rank 43, name "MetLife", revenue 66153},
{NSObject, rank 44, name "UPS", revenue 65872},
{NSObject, rank 45, name "PepsiCo", revenue 63525},
{NSObject, rank 46, name "Intel", revenue 62761},
{NSObject, rank 47, name "DowDuPont", revenue 62683},
{NSObject, rank 48, name "Archer Daniels Midland", revenue 60828},
{NSObject, rank 49, name "Aetna", revenue 60535},
{NSObject, rank 50, name "FedEx", revenue 60319},
{NSObject, rank 51, name "United Technologies", revenue 59837},
{NSObject, rank 52, name "Prudential Financial", revenue 59689},
{NSObject, rank 53, name "Albertsons Cos.", revenue 59678.2},
{NSObject, rank 54, name "Sysco", revenue 55371.1},
{NSObject, rank 55, name "Disney", revenue 55137},
{NSObject, rank 56, name "Humana", revenue 53767},
{NSObject, rank 57, name "Pfizer", revenue 52546},
{NSObject, rank 58, name "HP", revenue 52056},
{NSObject, rank 59, name "Lockheed Martin", revenue 51048},
{NSObject, rank 60, name "AIG", revenue 49520},
{NSObject, rank 61, name "Centene", revenue 48572},
{NSObject, rank 62, name "Cisco Systems", revenue 48005},
{NSObject, rank 63, name "HCA Healthcare", revenue 47653},
{NSObject, rank 64, name "Energy Transfer Equity", revenue 47487},
{NSObject, rank 65, name "Caterpillar", revenue 45462},
{NSObject, rank 66, name "Nationwide", revenue 43939.9},
{NSObject, rank 67, name "Morgan Stanley", revenue 43642},
{NSObject, rank 69, name "New York Life Insurance", revenue 42296},
{NSObject, rank 72, name "Best Buy", revenue 42151},
{NSObject, rank 73, name "Cigna", revenue 41616},
{NSObject, rank 74, name "Charter Communications", revenue 41581},
{NSObject, rank 75, name "Delta Air Lines", revenue 41244},
{NSObject, rank 76, name "Facebook", revenue 40653},
{NSObject, rank 77, name "Honeywell International", revenue 40534},
{NSObject, rank 78, name "Merck", revenue 40122},
{NSObject, rank 79, name "Allstate", revenue 38524},
{NSObject, rank 80, name "Tyson Foods", revenue 38260},
{NSObject, rank 81, name "United Continental Holdings", revenue 37736},
{NSObject, rank 82, name "Oracle", revenue 37728},
{NSObject, rank 83, name "Tech Data", revenue 36775},
{NSObject, rank 84, name "TIAA", revenue 36025.3},
{NSObject, rank 85, name "TJX", revenue 35864.7},
{NSObject, rank 86, name "American Express", revenue 35583},
{NSObject, rank 87, name "Coca-Cola", revenue 35410},
{NSObject, rank 88, name "Publix Super Markets", revenue 34836.8},
{NSObject, rank 89, name "Nike", revenue 34350},
{NSObject, rank 90, name "Andeavor", revenue 34204},
{NSObject, rank 91, name "World Fuel Services", revenue 33695.5},
{NSObject, rank 92, name "Exelon", revenue 33531},
{NSObject, rank 93, name "Massachusetts Mutual Life Insurance", revenue 33495.4},
{NSObject, rank 94, name "Rite Aid", revenue 32845.1},
{NSObject, rank 95, name "ConocoPhillips", revenue 32584},
{NSObject, rank 96, name "CHS", revenue 31934.8},
{NSObject, rank 97, name "3M", revenue 31657},
{NSObject, rank 98, name "Time Warner", revenue 31271},
{NSObject, rank 99, name "General Dynamics", revenue 30973},
{NSObject, rank 10, name "0USAA", revenue 30015.8}]

사명에 'Group'이 포함되지 않고, avenue가 '200000' 미만인 회사만 검색된다.

predicate는 바로 위의 예시와 같이 괄호를 사용해 조건의 우선순위를 지정할 수 있다.
하지만 가독성이 크게 떨어진다는 단점이 있다.

 

Aggregate Qualifiers

집계 연산자에 해당한다.
Core Data 내에서 to-many Relation과 함께 사용한다.
ALL, ANY, IN 키워드는 predicate 내에서 한 번만 사용할 수 있다.

ANY, SOME

조건에 만족하는 데이터가 Collection에 존재하는 경우 참으로 판단한다.

var predicate = NSPredicate(format: "ANY employees.age >= 50")
departments.filtered(using: predicate)

predicate = NSPredicate(format: "SOME employees.age >= 50")
departments.filtered(using: predicate)
결과

[{NSObject, id 6, name "Finance",
[{NSObject, name "Rosemary Gibbs", age 20, salary 30000, address "9561 S. St Margarets Ave. Hackettstown, NJ 07840"},
{NSObject, name "Aidan Bray", age 26, salary 74000, address "211 Arch Court Oak Ridge, TN 37830"},
{NSObject, name "Myles Sheppard", age 22, salary 83000, address "88 Lakeshore Drive Champlin, MN 55316"},
{NSObject, name "Alivia Roach", age 50, salary 53000, address "468 Country Avenue Lewiston, ME 04240"}]}]

Finance 부서 내에 50세 이상의 직원이 존재하기 때문에
해당 부서가 반환된다.

ALL

Collection에 포함된 모든 데이터가 조건을 만족시켜야 참으로 판단한다.

predicate = NSPredicate(format: "ALL employees.age BETWEEN {31, 42}")
departments.filtered(using: predicate)
결과

[{NSObject, id 7, name "Sales",
[{NSObject, name "Madalynn Camacho", age 35, salary 33000, address "12 South Center Ave. Everett, MA 02149"},
{NSObject, name "Campbell Evans", age 33, salary 50000, address "538 King Drive Saint Petersburg, FL 33702"},
{NSObject, name "Laci Flores", age 42, salary 48000, address "7667 Birchpond Street Prior Lake, MN 55372"},
{NSObject, name "Zain Hinton", age 31, salary 54000, address "9517 Jennings Rd. Pearl, MS 39208"}]}]

Sales 부서의 모든 직원이 31세 이상 42세 미만이기 때문에
해당 부서가 반환된다.

NONE

predicate = NSPredicate(format: "NONE employees.age < 30")
departments.filtered(using: predicate)
결과

[{NSObject, id 7, name "Sales",
[{
NSObject, name "Madalynn Camacho", age 35, salary 33000, address "12 South Center Ave. Everett, MA 02149"},
{
NSObject, name "Campbell Evans", age 33, salary 50000, address "538 King Drive Saint Petersburg, FL 33702"},
{
NSObject, name "Laci Flores", age 42, salary 48000, address "7667 Birchpond Street Prior Lake, MN 55372"},
{
NSObject, name "Zain Hinton", age 31, salary 54000, address "9517 Jennings Rd. Pearl, MS 39208"}]}]

Sales 부서에는 20대 직원이 존재하지 않기 때문에
해당 부서가 반환된다.

IN

Collection의 속성에 저장된 값이 지정된 범위 안에 존재하는 경우 참으로 판단한다.

predicate = NSPredicate(format: "revenue IN {229234.0, 89950.0}")
companies.filtered(using: predicate)
결과

[{NSObject, rank 4, name "Apple", revenue 229234}, {NSObject, rank 30, name "Microsoft", revenue 89950}]

전달되는 범위는 배열의 형태를 가지며, 반드시 두 개의 수를 가져야 한다.

 

Aggregation Operator

Collection 내에서 간단한 산술 연산을 실행한다.
해당 연산자들은 Collection에서만 사용할 수 있고, 모든 연산자가 '@'으로 시작한다.

@count

Collection에 포함된 Data의 수를 반환한다.

predicate = NSPredicate(format: "employees.@count < 4")
departments.filtered(using: predicate)
결과

[{NSObject, id 1, name "Management",
[{NSObject, name "Mariana Sanders", age 27, salary 34000, address "8148 Pilgrim Circle Phoenixville, PA 19460"},
{NSObject, name "Aniyah Pace", age 31, salary 80000, address "646 Nut Swamp Ave. Hampton, VA 23666"}]},
{NSObject, id 5, name "Planning",
[{NSObject, name "Jovanni Braun", age 21, salary 55000, address "50 Sugar St. Port Saint Lucie, FL 34952"},
{NSObject, name "Londyn Williams", age 28, salary 88000, address "29 Oakwood Rd. Cranston, RI 02920"},
{NSObject, name "Cassius Bishop", age 36, salary 30000, address "9716 Tallwood Street Germantown, MD 20874"}]}]

employee의 데이터가 4개 미만일 때를 참으로 판단한다.
따라서 소속된 인원이 3명 이하인 부서가 반환된다.

@avg

평균값을 반환한다.
따라서 속성에 저장된 값이 숫자인 경우에만 정상적으로 동작한다.

predicate = NSPredicate(format: "employees.@avg.age < 30")
departments.filtered(using: predicate)
결과

[{NSObject, id 1, name "Management",
[{NSObject, name "Mariana Sanders", age 27, salary 34000, address "8148 Pilgrim Circle Phoenixville, PA 19460"},
{NSObject, name "Aniyah Pace", age 31, salary 80000, address "646 Nut Swamp Ave. Hampton, VA 23666"}]},
{NSObject, id 5, name "Planning",
[{NSObject, name "Jovanni Braun", age 21, salary 55000, address "50 Sugar St. Port Saint Lucie, FL 34952"},
{NSObject, name "Londyn Williams", age 28, salary 88000, address "29 Oakwood Rd. Cranston, RI 02920"},
{NSObject, name "Cassius Bishop", age 36, salary 30000, address "9716 Tallwood Street Germantown, MD 20874"}]},
{NSObject, id 6, name "Finance",
[{NSObject, name "Rosemary Gibbs", age 20, salary 30000, address "9561 S. St Margarets Ave. Hackettstown, NJ 07840"},
{NSObject, name "Aidan Bray", age 26, salary 74000, address "211 Arch Court Oak Ridge, TN 37830"},
{NSObject, name "Myles Sheppard", age 22, salary 83000, address "88 Lakeshore Drive Champlin, MN 55316"},
{NSObject, name "Alivia Roach",age 50, salary 53000, address "468 Country Avenue Lewiston, ME 04240"}]}]

소속된 직원의 평균 나이가 20대인 부서가 반환된다.

@min

최솟값을 반환한다.

predicate = NSPredicate(format: "employees.@min.age >= 30")
departments.filtered(using: predicate)
결과

[{NSObject, id 7, name "Sales",
[{NSObject, name "Madalynn Camacho", age 35, salary 33000, address "12 South Center Ave. Everett, MA 02149"},
{NSObject, name "Campbell Evans", age 33, salary 50000, address "538 King Drive Saint Petersburg, FL 33702"},
{NSObject, name "Laci Flores", age 42, salary 48000, address "7667 Birchpond Street Prior Lake, MN 55372"},
{NSObject, name "Zain Hinton", age 31, salary 54000, address "9517 Jennings Rd. Pearl, MS 39208"}]}]

소속된 최연소 직원의 나이가 30세 이상인 부서가 반환된다.

@max

최댓값을 반환한다.

predicate = NSPredicate(format: "employees.@max.age >= 50")
departments.filtered(using: predicate)
결과

[{NSObject, id 6, name "Finance",
[{NSObject, name "Rosemary Gibbs", age 20, salary 30000, address "9561 S. St Margarets Ave. Hackettstown, NJ 07840"},
{NSObject, name "Aidan Bray", age 26, salary 74000, address "211 Arch Court Oak Ridge, TN 37830"},
{NSObject, name "Myles Sheppard", age 22, salary 83000, address "88 Lakeshore Drive Champlin, MN 55316"},
{NSObject, name "Alivia Roach", age 50, salary 53000, address "468 Country Avenue Lewiston, ME 04240"}]}]

소속된 최고령 직원의 나이가 50세 이상인 부서가 반환된다.

@sum

속성의 총합을 반환한다.

predicate = NSPredicate(format: "employees.@sum.salary >= 300000")
departments.filtered(using: predicate)
결과

[{NSObject, id 2, name "Design",
[{NSObject, name "Zaire May", age 23, salary 39000, address "59 Goldfield Court Saint Louis, MO 63109"},
{NSObject, name "Draven Sellers", age 46, salary 76000, address "51 N. Summer St. West Orange, NJ 07052"},
{NSObject, name "Paxton Sellers", age 25, salary 27000, address "9560 Van Dyke Street La Porte, IN 46350"},
{NSObject, name "Elian Strong", age 46, salary 56000, address "43 E. Augusta Lane San Carlos, CA 94070"},
{NSObject, name "Bronson Villegas", age 21, salary 85000, address "271 Lookout Road Harrisburg, PA 17109"},
{NSObject, name "Zachariah Beltran", age 44, salary 71000, address "323 Railroad Drive Homestead, FL 33030"},
{NSObject, name "Aydan Hinton", age 29, salary 78000, address "360 E. Williams Dr. Muncie, IN 47302"}]},
{NSObject, id 4, name "HR",
[{NSObject, name "Jaidyn Ayala", age 20, salary 56000, address "505 Rockaway St. Edison, NJ 08817"},
{NSObject, name "Kamron Deleon", age 47, salary 63000, address "620 N. Jennings Lane Rowlett, TX 75088"},
{NSObject, name "Luciana Shea", age 47, salary 32000, address "81 Amherst Court Manchester Township, NJ 08759"},
{NSObject, name "Boston Carney", age 39, salary 36000, address "733 Wild Rose Rd. Massapequa, NY 11758"},
{NSObject, name "Aarav Duran", age 35, salary 77000, address "299 Roosevelt Rd. Gettysburg, PA 17325"},
{NSObject, name "Ross Joseph", age 20, salary 45000, address "8699 South College Lane West Haven, CT 06516"}]}]

소속된 직원의 연봉의 합이 300000 이상인 부서가 반환된다.

Array Operations

format 문자열에서 선언된 문자열에 접근할 때는 서브 스크립트 문법이 사용된다.

predicate = NSPredicate(format: "employees[0].age >= 35")
departments.filtered(using: predicate)
결과

[{NSObject, id 7, name "Sales",
[{NSObject, name "Madalynn Camacho", age 35, salary 33000, address "12 South Center Ave. Everett, MA 02149"},
{NSObject, name "Campbell Evans", age 33, salary 50000, address "538 King Drive Saint Petersburg, FL 33702"},
{NSObject, name "Laci Flores", age 42, salary 48000, address "7667 Birchpond Street Prior Lake, MN 55372"},
{NSObject, name "Zain Hinton", age 31, salary 54000, address "9517 Jennings Rd. Pearl, MS 39208"}]}]

위와 같이 원하는 index를 직접 지정하는 것이 가능하다.
다만 잘못된 index를 지정하는 경우 문제가 발생할 수 있다.

predicate = NSPredicate(format: "employees[FIRST].age >= 35")
departments.filtered(using: predicate)
결과

[{NSObject, id 7, name "Sales",
[{NSObject, name "Madalynn Camacho", age 35, salary 33000, address "12 South Center Ave. Everett, MA 02149"},
{NSObject, name "Campbell Evans", age 33, salary 50000, address "538 King Drive Saint Petersburg, FL 33702"},
{NSObject, name "Laci Flores", age 42, salary 48000, address "7667 Birchpond Street Prior Lake, MN 55372"},
{NSObject, name "Zain Hinton", age 31, salary 54000, address "9517 Jennings Rd. Pearl, MS 39208"}]}]

따라서 위와 같이 index의 자리에 FIRST 키워드를 전달하면 배열의 첫 번째 요소에 접근한다.

predicate = NSPredicate(format: "employees[LAST].age >= 35")
departments.filtered(using: predicate)
결과

[{NSObject, id 5, name "Planning",
[{NSObject, name "Jovanni Braun", age 21, salary 55000, address "50 Sugar St. Port Saint Lucie, FL 34952"},
{NSObject, name "Londyn Williams", age 28, salary 88000, address "29 Oakwood Rd. Cranston, RI 02920"},
{NSObject, name "Cassius Bishop", age 36, salary 30000, address "9716 Tallwood Street Germantown, MD 20874"}]},
{NSObject, id 6, name "Finance",
[{NSObject, name "Rosemary Gibbs", age 20, salary 30000, address "9561 S. St Margarets Ave. Hackettstown, NJ 07840"},
{NSObject, name "Aidan Bray", age 26, salary 74000, address "211 Arch Court Oak Ridge, TN 37830"},
{NSObject, name "Myles Sheppard", age 22, salary 83000, address "88 Lakeshore Drive Champlin, MN 55316"},
{NSObject, name "Alivia Roach", age 50, salary 53000, address "468 Country Avenue Lewiston, ME 04240"}]}]

index의 자리에 LAST 키워드를 전달해 마지막 요소에 접근할 수도 있다.

predicate = NSPredicate(format: "employees[SIZE] == 6")
departments.filtered(using: predicate)
결과

[{NSObject, id 4, name "HR",
[{NSObject, name "Jaidyn Ayala", age 20, salary 56000, address "505 Rockaway St. Edison, NJ 08817"},
{NSObject, name "Kamron Deleon", age 47, salary 63000, address "620 N. Jennings Lane Rowlett, TX 75088"},
{NSObject, name "Luciana Shea", age 47, salary 32000, address "81 Amherst Court Manchester Township, NJ 08759"},
{NSObject, name "Boston Carney", age 39, salary 36000, address "733 Wild Rose Rd. Massapequa, NY 11758"},
{NSObject, name "Aarav Duran", age 35, salary 77000, address "299 Roosevelt Rd. Gettysburg, PA 17325"},
{NSObject, name "Ross Joseph", age 20, salary 45000, address "8699 South College Lane West Haven, CT 06516"}]}]

index의 자리에 SIZE 키워드를 전달하면 배열의 크기를 반환한다.

 

Variables

format 문자열에서 검색에 사용될 조건은 지정자를 사용해서 표현한다.

var predicate = NSPredicate(format: "%K BEGINSWITH %@", #keyPath(Company.name), "America")

위와 같이 문자열과 값을 함께 전달하는 경우에는 문제가 없다.

predicate = NSPredicate(format: "%K BEGINSWITH $COMPANY_NAME", #keyPath(Company.name)).withSubstitutionVariables(["COMPANY_NAME": "America"])
결과

[{NSObject, rank 71, name "American Airlines Group", revenue 42207},
{NSObject, rank 86, name "American Express", revenue 35583}]

하지만 문자열을 지금과 같이 동적으로 구현해 전달하게 되거나 여러 지정자가 존재하면 가독성이 나빠지기 시작한다.

따라서 '$'를 사용해 변수의 이름을 설정한다.
이 경우 직접 지정자를 전달하는 대신 withSubstitutionVariables 메서드를 Dictionary와 함께 전달한다.
Key에는 지정한 변수 명을, Value에는 대체할 값을 전달한다.

 

Subquery

Department에서 40대 이상의 직원이 3면 이상 소속된 부서

위의 조건을 일반적인 predicate로 표현하는 것은 어렵다.

var p = NSPredicate(format: "(employees.age >= 40).@count >= 3")
departments.filtered(using: p)
결과

//error

'employees.age >= 40'의 결과가 collection이 아니기 때문에 @count는 사용할 수 없다.

var p2 = NSPredicate(format: "ANY employees.age >= 40 AND employees.@count >= 3")
departments.filtered(using: p2)
결과

[{NSObject, id 2, name "Design", [{NSObject, name "Zaire May", age 23, salary 39000, address "59 Goldfield Court Saint Louis, MO 63109"}, {NSObject, name "Draven Sellers", age 46, salary 76000, address "51 N. Summer St. West Orange, NJ 07052"}, {NSObject, name "Paxton Sellers", age 25, salary 27000, address "9560 Van Dyke Street La Porte, IN 46350"}, {NSObject, name "Elian Strong", age 46, salary 56000, address "43 E. Augusta Lane San Carlos, CA 94070"}, {NSObject, name "Bronson Villegas", age 21, salary 85000, address "271 Lookout Road Harrisburg, PA 17109"}, {NSObject, name "Zachariah Beltran", age 44, salary 71000, address "323 Railroad Drive Homestead, FL 33030"}, {NSObject, name "Aydan Hinton", age 29, salary 78000, address "360 E. Williams Dr. Muncie, IN 47302"}]},
{NSObject, id 3, name "Development", [{NSObject, name "Samantha Heath", age 29, salary 80000, address "7397 Lees Creek Ave. Ponte Vedra Beach, FL 32082"}, {NSObject, name "Jerimiah Nunez", age 40, salary 30000, address "34 6th Street Elk Grove Village, IL 60007"}, {NSObject, name "Alivia Carey", age 32, salary 80000, address "336 Grove Ave. Lynn, MA 01902"}, {NSObject, name "Wyatt Charles", age 34, salary 88000, address "463 Helen Avenue Greenville, NC 27834"}]},
{NSObject, id 4, name "HR", [{NSObject, name "Jaidyn Ayala", age 20, salary 56000, address "505 Rockaway St. Edison, NJ 08817"}, {NSObject, name "Kamron Deleon", age 47, salary 63000, address "620 N. Jennings Lane Rowlett, TX 75088"}, {NSObject, name "Luciana Shea", age 47, salary 32000, address "81 Amherst Court Manchester Township, NJ 08759"}, {NSObject, name "Boston Carney", age 39, salary 36000, address "733 Wild Rose Rd. Massapequa, NY 11758"}, {NSObject, name "Aarav Duran", age 35, salary 77000, address "299 Roosevelt Rd. Gettysburg, PA 17325"}, {NSObject, name "Ross Joseph", age 20, salary 45000, address "8699 South College Lane West Haven, CT 06516"}]},
{NSObject, id 6, name "Finance", [{NSObject, name "Rosemary Gibbs", age 20, salary 30000, address "9561 S. St Margarets Ave. Hackettstown, NJ 07840"}, {NSObject, name "Aidan Bray", age 26, salary 74000, address "211 Arch Court Oak Ridge, TN 37830"}, {NSObject, name "Myles Sheppard", age 22, salary 83000, address "88 Lakeshore Drive Champlin, MN 55316"}, {NSObject, name "Alivia Roach", age 50, salary 53000, address "468 Country Avenue Lewiston, ME 04240"}]},
{NSObject, id 7, name "Sales", [{NSObject, name "Madalynn Camacho", age 35, salary 33000, address "12 South Center Ave. Everett, MA 02149"}, {NSObject, name "Campbell Evans", age 33, salary 50000, address "538 King Drive Saint Petersburg, FL 33702"}, {NSObject, name "Laci Flores", age 42, salary 48000, address "7667 Birchpond Street Prior Lake, MN 55372"}, {NSObject, name "Zain Hinton", age 31, salary 54000, address "9517 Jennings Rd. Pearl, MS 39208"}]}]

결과는 나오지만 의도한 조건과는 다른 결과가 반환된다.
반환된 값들 중 'Development' 부서는 40대 이상인 직원이 한 명 밖에 되지 않는다.

이러한 경우 Subquery를 사용하면
format 문자열 내에 중첩된 query를 추가하고 결과를 기반으로 새로운 query를 구성할 수 있다.

param1 : collection : 실행할 collection
Core Data에서 사용되는 경우 to-many relation이 자리하는 경우가 많다.

param2 : varName : 변수 이름
해당 이름은 Collection의 데이터와 바인딩된다.

param3 : predicate : predicate format 문자열

let predicate = NSPredicate(format: "SUBQUERY(employees, $emp, $emp.age >= 40).@count >= 3")
departments.filtered(using: predicate)
결과

[{NSObject, id 2, name "Design",
[{NSObject, name "Zaire May", age 23, salary 39000, address "59 Goldfield Court Saint Louis, MO 63109"},
{NSObject, name "Draven Sellers", age 46, salary 76000, address "51 N. Summer St. West Orange, NJ 07052"},
{NSObject, name "Paxton Sellers", age 25, salary 27000, address "9560 Van Dyke Street La Porte, IN 46350"},
{NSObject, name "Elian Strong", age 46, salary 56000, address "43 E. Augusta Lane San Carlos, CA 94070"},
{NSObject, name "Bronson Villegas", age 21, salary 85000, address "271 Lookout Road Harrisburg, PA 17109"},
{NSObject, name "Zachariah Beltran", age 44, salary 71000, address "323 Railroad Drive Homestead, FL 33030"},
{NSObject, name "Aydan Hinton", age 29, salary 78000, address "360 E. Williams Dr. Muncie, IN 47302"}]}]

위의 format 문자열은 employees 배열의 데이터 중 나이가 40대 이상인 직원을 필터링하고,
결과의 수가 3 이상인지를 판단해 반환한다.

'학습 노트 > iOS (2021)' 카테고리의 다른 글

193. Fetched Result Controller  (0) 2022.04.09
192. Expression  (0) 2022.03.30
190. Predicate  (0) 2022.03.24
187 ~ 189. Fetch Request  (0) 2022.02.25
187. Entity Hierarchy, Relationships  (0) 2022.02.18