SwiftでNSPredicateを使うための10選ガイド

SwiftでのNSPredicateの使い方とサンプルコードSwift
この記事は約24分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

皆さん、Swiftでのデータフィルタリングにお困りではないでしょうか?

データの取り扱いは、アプリケーション開発の中心的な役割を果たしています。

そして、NSPredicateはSwiftでデータを効果的にフィルタリングする際の強力なツールです。

この記事を読めば、SwiftのNSPredicateを活用することができるようになります。

●NSPredicateの基本とは

○NSPredicateの定義と役割

NSPredicateは、コレクションやデータベースのデータを検索またはフィルタリングするための条件を表現するクラスです。

具体的には、配列やCore Dataのようなオブジェクトグラフをフィルタリングするために使います。

例えば、特定の条件に一致するオブジェクトだけを取得したい、といった場合にNSPredicateを使用します。

これはデータベースのクエリのような役割を果たしますが、Swiftの文法で直感的に書くことができるのが特徴です。

○NSPredicateの基本的な構文

NSPredicateを使用する際の基本的な構文は次のようになります。

// 文字列の場合
let predicate1 = NSPredicate(format: "name == %@", "John")

// 数値の場合
let predicate2 = NSPredicate(format: "age == %d", 30)

// 配列の中から条件に一致するものを取得
let filteredArray = (array as NSArray).filtered(using: predicate1) as! [YourObjectType]

このコードでは、NSPredicateを使って「nameがJohn」という条件に一致するデータや「ageが30」という条件に一致するデータをフィルタリングしています。

filtered(using:)メソッドを使用して、指定した条件に一致するオブジェクトだけを取り出しています。

NSPredicateの条件部分は、SQLのWHERE句のように考えることができます。

つまり、条件に合致するデータを選択するための指定方法として使います。

そして、%記号を使って、動的な値を条件に組み込むことができます。上の例では、%@は文字列、%dは整数を表しています。

●SwiftでのNSPredicateの使い方

Swiftの強力なツールであるNSPredicateを使用すると、データを効果的にフィルタリングすることができます。

ここでは、SwiftでのNSPredicateの基本的な使い方を、具体的なサンプルコードとともに詳しく解説していきます。

○サンプルコード1:文字列に一致するデータをフィルタリングする

最初の例として、特定の文字列に一致するデータをフィルタリングする方法を見ていきましょう。

下記のコードは、name属性が”John”と一致するオブジェクトだけをフィルタリングします。

let predicate = NSPredicate(format: "name == %@", "John")
let filteredData = (data as NSArray).filtered(using: predicate) as! [YourObjectType]

このコードでは、NSPredicate(format:)メソッドを使用して、条件式を作成しています。

そして、NSArrayのfiltered(using:)メソッドを使用して、条件に一致するオブジェクトだけを取り出しています。

この例を使うと、name属性が”John”であるオブジェクトのみが、filteredData配列に格納されます。

このように、NSPredicateを使って簡単にデータのフィルタリングができるのが特徴です。

○サンプルコード2:数値に基づいてデータをフィルタリングする

次に、数値を条件にデータをフィルタリングする方法を見ていきます。

下記のコードは、age属性が30以上のオブジェクトだけをフィルタリングします。

let agePredicate = NSPredicate(format: "age >= %d", 30)
let filteredByAge = (data as NSArray).filtered(using: agePredicate) as! [YourObjectType]

このコードでは、NSPredicate(format:)メソッドを使用して、条件式を作成しています。

そして、NSArrayのfiltered(using:)メソッドを使用して、条件に一致するオブジェクトだけを取り出しています。

この例では、age属性が30以上のオブジェクトのみが、filteredByAge配列に格納されます。

数値を使った条件式も、文字列と同じように簡単に書くことができます。

○サンプルコード3:日付範囲でのデータフィルタリング

日付はアプリケーション内のデータでよく使用される型の一つです。特定の日付範囲内にあるデータをフィルタリングする必要が生じることも多くあります。

ここでは、日付範囲に基づいてデータをフィルタリングする方法を紹介します。

下記のサンプルコードは、特定の日付範囲内のオブジェクトだけをフィルタリングする方法を表しています。

// 日付範囲の設定
let startDate = Date() // 今日の日付
let endDate = Calendar.current.date(byAdding: .day, value: 7, to: startDate) // 1週間後の日付

let datePredicate = NSPredicate(format: "(date >= %@) AND (date <= %@)", startDate as NSDate, endDate as NSDate)
let filteredByDate = (data as NSArray).filtered(using: datePredicate) as! [YourObjectType]

このコードではNSPredicate(format:)メソッドを使用して、日付範囲の条件式を作成しています。

startDateendDateの間の日付を持つオブジェクトだけを取得します。

そして、NSArrayのfiltered(using:)メソッドを使用して、条件に一致するオブジェクトだけを取り出しています。

実際にこのコードを使うと、startDateからendDateまでの範囲内の日付を持つオブジェクトのみが、filteredByDate配列に格納されます。

日付の範囲指定は、特定の期間のデータを抽出する際や、期間限定のイベント情報などを取得する場面で役立ちます。

○サンプルコード4:複数条件でのデータフィルタリング

データをフィルタリングする際、複数の条件を組み合わせて検索を行うことも多いです。

例として、名前と年齢の2つの条件を同時に満たすデータをフィルタリングする方法を解説します。

以下のサンプルコードでは、name属性が”John”であり、かつ、age属性が25以上のオブジェクトだけをフィルタリングしています。

let multiPredicate = NSPredicate(format: "name == %@ AND age >= %d", "John", 25)
let filteredByMultiConditions = (data as NSArray).filtered(using: multiPredicate) as! [YourObjectType]

このコードでは、NSPredicate(format:)メソッドを用いて、複数の条件を持つ条件式を作成しています。

そして、NSArrayのfiltered(using:)メソッドを使用して、条件に一致するオブジェクトだけを取り出しています。

この例では、name属性が”John”で、かつ、age属性が25以上のオブジェクトのみが、filteredByMultiConditions配列に格納されます。

複数の条件を組み合わせることで、より詳細なデータフィルタリングが可能になります。

○サンプルコード5:関連オブジェクトの属性を基にしたフィルタリング

データベースやモデル内には、関連するオブジェクトやサブオブジェクトが存在することもあります。

例えば、ユーザーオブジェクトが住所オブジェクトを持つ場合、その住所の都市属性に基づいてユーザーをフィルタリングすることが考えられます。

下記のサンプルコードでは、住所オブジェクトの都市属性が”Tokyo”のユーザーオブジェクトだけをフィルタリングしています。

let relationPredicate = NSPredicate(format: "address.city == %@", "Tokyo")
let filteredByRelation = (data as NSArray).filtered(using: relationPredicate) as! [YourObjectType]

このコードでは、NSPredicate(format:)メソッドを用いて、関連オブジェクトの属性に基づく条件式を作成しています。

そして、NSArrayのfiltered(using:)メソッドを使用して、条件に一致するオブジェクトだけを取り出しています。

この例を用いると、住所の都市属性が”Tokyo”であるユーザーオブジェクトのみが、filteredByRelation配列に格納されます。

関連オブジェクトの属性を基にしたフィルタリングは、データの関連性を考慮して詳細な検索を行う際に非常に役立ちます。

●NSPredicateの応用例

SwiftのNSPredicateは基本的な使い方だけでなく、さまざまな高度な機能を提供しており、データフィルタリングのニーズに合わせて柔軟に対応できます。

ここでは、より高度なフィルタリング技術をいくつか紹介します。

○サンプルコード6:正規表現を使った詳細な文字列マッチング

文字列データのフィルタリングに正規表現を利用することで、より詳細な条件を指定して検索することができます。

下記のサンプルコードは、メールアドレスの形式に一致する文字列をフィルタリングする方法を表しています。

let regexPredicate = NSPredicate(format: "SELF MATCHES %@", "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}")
let filteredByEmailFormat = (data as NSArray).filtered(using: regexPredicate) as! [String]

このコードではNSPredicate(format:)メソッドを利用して、正規表現を使った条件式を作成しています。

そして、NSArrayのfiltered(using:)メソッドを用いて、メールアドレスの形式に一致する文字列のみを取り出しています。

このようにして抽出されたデータは、filteredByEmailFormat配列に格納され、正しいメールアドレスの形式に一致する文字列のみが含まれます。

○サンプルコード7:サブクエリを用いた複雑なデータのフィルタリング

サブクエリは、コレクションの中のコレクションに対してフィルタリングを行う強力なツールです。

下記のサンプルコードは、各ユーザーが持つフレンドリストの中から特定の条件を満たすフレンドを持つユーザーをフィルタリングする方法を表しています。

let subqueryPredicate = NSPredicate(format: "SUBQUERY(friends, $friend, $friend.age > 25).@count > 0")
let usersWithFriendsOver25 = (data as NSArray).filtered(using: subqueryPredicate) as! [User]

このコードでは、NSPredicate(format:)を使用してサブクエリを含む条件式を作成しています。

そして、filtered(using:)メソッドで条件に一致するユーザーを抽出しています。

この例を使用すると、25歳以上のフレンドを1人以上持つユーザーだけが、usersWithFriendsOver25配列に格納されます。

これにより、関連データを持つオブジェクトの集合の中から、特定の条件を満たすサブセットのデータを効果的にフィルタリングすることができます。

○サンプルコード8:NSCompoundPredicateを使用した複合条件のフィルタリング

複数のNSPredicateを組み合わせて一つの条件式を作成する場面もあります。

このような場合には、NSCompoundPredicateを使用します。

下記のサンプルコードは、名前が”John”で、かつ、25歳以上、または名前が”Alice”で、かつ、20歳未満のユーザーをフィルタリングする方法を表しています。

let predicate1 = NSPredicate(format: "name == %@ AND age >= %d", "John", 25)
let predicate2 = NSPredicate(format: "name == %@ AND age < %d", "Alice", 20)
let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [predicate1, predicate2])
let filteredUsers = (data as NSArray).filtered(using: compoundPredicate) as! [User]

このコードでは、まず2つのNSPredicateオブジェクトを作成しています。

次に、これらのプレディケートを組み合わせてNSCompoundPredicateを作成しています。

そして、この複合条件を使用して、条件に一致するユーザーを抽出しています。

○サンプルコード8:NSCompoundPredicateを使用した複合条件のフィルタリング

データのフィルタリングにおいて、複数の条件を一度に組み合わせて適用することがしばしば必要となります。

SwiftのNSPredicateでは、NSCompoundPredicateクラスを使用して、2つ以上のプレディケートを論理的に組み合わせることが可能です。

下記の例では、名前が”John”で25歳以上、または名前が”Alice”で20歳未満のユーザーをフィルタリングする方法を表しています。

import Foundation

class User: NSObject {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let users = [
    User(name: "John", age: 26),
    User(name: "Alice", age: 19),
    User(name: "Bob", age: 23)
]

let predicate1 = NSPredicate(format: "name == %@ AND age >= %d", "John", 25)
let predicate2 = NSPredicate(format: "name == %@ AND age < %d", "Alice", 20)

let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [predicate1, predicate2])
let filteredUsers = (users as NSArray).filtered(using: compoundPredicate) as! [User]

このコードでは、まず2つのNSPredicateオブジェクトを作成しています。

そして、NSCompoundPredicateを用いてこれらのプレディケートをOR条件で組み合わせています。

最後に、filtered(using:)メソッドを利用して、複合条件に一致するユーザーを取得します。

上記のコードを実行すると、名前が”John”で25歳以上のユーザーと、名前が”Alice”で20歳未満のユーザーがfilteredUsers配列に格納されます。

このようにNSCompoundPredicateは、複数の条件を組み合わせて複雑なフィルタリングを行う際に非常に役立ちます。

○サンプルコード9:動的なフィルタリングの実装

データベースや大量のデータセットを扱う際、ユーザーからの入力に基づいて動的にフィルタリング条件を変更することが必要となる場面があります。

NSPredicateでは、動的なフィルタリングもシンプルに実装することができます。

下記の例では、ユーザーが入力した名前を条件としてデータをフィルタリングする方法を表しています。

let searchName = "Alice"  // 例えば、ユーザーが入力した値
let dynamicPredicate = NSPredicate(format: "name == %@", searchName)
let dynamicFilteredUsers = (users as NSArray).filtered(using: dynamicPredicate) as! [User]

このコードでは、変数searchNameに格納された名前を条件として、NSPredicateを動的に生成しています。

そして、そのプレディケートを利用して、条件に一致するユーザーを取得しています。

上記のコードを実行すると、名前が”Alice”のユーザーがdynamicFilteredUsers配列に格納されます。

このように、動的な条件を用いたフィルタリングは、ユーザーインターフェースを持つアプリケーションの開発において非常に有用です。

○サンプルコード10:NSFetchRequestとの組み合わせ

SwiftでCore Dataフレームワークを使用してデータベースを操作する際、NSPredicateはNSFetchRequestと組み合わせて利用されることが多いです。

これにより、データベース内の大量のデータから、特定の条件に一致するデータを効率的に取得することができます。

以下の例は、Core Dataを使用して、特定の条件に一致するデータを取得する方法を表しています。

import CoreData

let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "age >= %d", 25)
do {
    let results = try context.fetch(fetchRequest)
    for user in results {
        print(user.name, user.age)
    }
} catch {
    print("Fetch error: \(error)")
}

このコードでは、まずNSFetchRequestを作成して、25歳以上のユーザーを取得する条件を指定しています。

次に、context.fetch(fetchRequest)メソッドを利用して、条件に一致するデータをデータベースから取得しています。

上記のコードを実行すると、データベースに保存されている25歳以上のユーザーの名前と年齢が表示されます。

●注意点と対処法

NSPredicateは非常に強力で柔軟なツールであり、データフィルタリングのための幅広い条件を定義することができます。

しかし、その強力さゆえに、いくつかの注意点と対処法が存在します。

ここでは、SwiftでNSPredicateを使用する際の主な注意点と、それに対する適切な対処法について詳しく解説していきます。

○NSPredicateの潜在的な問題点

  1. パフォーマンスの低下:複雑な条件や大量のデータをフィルタリングする場合、パフォーマンスの低下が発生する可能性があります。特に、正規表現やサブクエリを頻繁に使用すると、処理速度が遅くなることが考えられます。
  2. セキュリティリスク:外部からの入力を直接プレディケートの条件として使用する場合、SQLインジェクションのようなセキュリティリスクが生じる可能性があります。
  3. 誤ったフィルタリング:NSPredicateの条件が不正確か、または不完全であると、期待した結果が得られないことがあります。特に、文字列の比較や日付の範囲指定においては、微妙な違いが結果に大きな影響を与える可能性があります。

下記のサンプルコードは、大量のデータをフィルタリングする際のパフォーマンスの低下を表しています。

import Foundation

class Product: NSObject {
    var name: String
    var price: Int

    init(name: String, price: Int) {
        self.name = name
        self.price = price
    }
}

let products = (0...100000).map { Product(name: "Product \($0)", price: $0) }

let startTime = CFAbsoluteTimeGetCurrent()
let expensivePredicate = NSPredicate(format: "price > %d", 50000)
let expensiveProducts = (products as NSArray).filtered(using: expensivePredicate) as! [Product]
let endTime = CFAbsoluteTimeGetCurrent()

print("フィルタリングにかかった時間: \(endTime - startTime)秒")

このコードでは、100,000個のProductオブジェクトを生成し、価格が50,000以上の商品をフィルタリングしています。

フィルタリングにかかった時間を計測して表示しています。

この例では、大量のデータを扱う際のパフォーマンスの低下を体感することができます。

○エラーハンドリングの重要性

NSPredicateを使用する際、条件の設定や構文の誤りによってはエラーが発生する可能性があります。

そのため、エラーハンドリングを適切に行うことが非常に重要です。

例えば、不正な形式の日付文字列をNSPredicateの条件として使用すると、エラーが発生します。

このようなケースを考慮し、適切なエラーハンドリングを行うことで、アプリケーションのクラッシュを防ぐことができます。

下記のサンプルコードは、不正な形式の日付文字列を条件として使用した際のエラーハンドリングを表しています。

class Event: NSObject {
    var name: String
    var date: Date

    init(name: String, date: Date) {
        self.name = name
        self.date = date
    }
}

let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd"
let startDate = formatter.date(from: "2023/01/01")!
let endDate = formatter.date(from: "2023/12/31")!

let events = [
    Event(name: "New Year", date: startDate),
    Event(name: "End of Year", date: endDate)
]

do {
    let predicate = NSPredicate(format: "date BETWEEN %@", ["2023-01-01", "2023-31-12"])
    let filteredEvents = (events as NSArray).filtered(using: predicate) as! [Event]
} catch {
    print("不正な形式の日付文字列が使用されました。")
}

上記のコードでは、”BETWEEN”キーワードを使用して日付の範囲を指定していますが、不正な形式の日付文字列を使用しているためエラーが発生します。

このエラーを捉えて、適切なメッセージを表示しています。

●カスタマイズ方法

SwiftのNSPredicateはそのままの使用だけでなく、カスタマイズを行うことで、さらに柔軟なデータフィルタリングが可能となります。

ここでは、NSPredicateのカスタマイズ方法と、それを効果的に利用するための高度なテクニックについて詳しく解説します。

○NSPredicateの高度なカスタマイズテクニック

□プレースホルダを使用した動的な条件の設定

データフィルタリングの条件が固定ではなく、ユーザーの入力や外部からのデータに応じて変更される場合があります。

このような動的な条件を設定する際には、プレースホルダを使用することで、柔軟に条件を設定することができます。

このコードでは、NSPredicateのプレースホルダを使って動的な条件を設定する方法を紹介しています。

この例では、指定した価格以下の商品をフィルタリングしています。

class Item: NSObject {
    var itemName: String
    var itemPrice: Int

    init(name: String, price: Int) {
        self.itemName = name
        self.itemPrice = price
    }
}

let items = [
    Item(name: "アイテムA", price: 1000),
    Item(name: "アイテムB", price: 5000),
    Item(name: "アイテムC", price: 10000)
]

let maxPrice = 6000
let predicateWithPlaceholder = NSPredicate(format: "itemPrice <= $MAX_PRICE")
let filteredItems = (items as NSArray).filtered(using: predicateWithPlaceholder.withSubstitutionVariables(["MAX_PRICE": maxPrice])) as! [Item]

for item in filteredItems {
    print(item.itemName)
}

このコードを実行すると、価格が6000以下のアイテムの名前が表示されます。

つまり、「アイテムA」と「アイテムB」が結果として出力されます。

□カスタム関数を使用したフィルタリング

NSPredicateの内部では、独自の関数を定義して、その関数を条件の中で利用することができます。

これにより、通常の条件指定では実現困難な複雑なフィルタリングを行うことが可能となります。

下記のサンプルコードでは、カスタム関数を使用して、指定した文字列の長さ以上の商品名を持つ商品をフィルタリングする方法を表しています。

extension Item {
    @objc func itemNameLengthIsGreaterThan(length: Int) -> Bool {
        return itemName.count > length
    }
}

let minLength = 7
let predicateWithCustomFunction = NSPredicate(format: "FUNCTION(SELF, 'itemNameLengthIsGreaterThan:', %d) == YES", minLength)
let filteredItemsWithFunction = (items as NSArray).filtered(using: predicateWithCustomFunction) as! [Item]

for item in filteredItemsWithFunction {
    print(item.itemName)
}

このコードを実行すると、商品名の長さが7文字より多いアイテムの名前が表示されます。

この例では、「アイテムB」と「アイテムC」が結果として出力されます。

まとめ

SwiftのNSPredicateは、データフィルタリングのための強力なツールとして、多くの開発者に利用されています。

このガイドを通じて、NSPredicateの基本的な使い方から高度なカスタマイズ方法までを詳細に学ぶことができたかと思います。

初めに、NSPredicateの基本的な構文と役割を理解し、その後Swiftでの具体的な使い方や様々な応用例を学びました。

さらに、注意点やエラーハンドリングの方法、そしてカスタマイズのテクニックなどを通じて、より高度なフィルタリングを行う方法を習得することができました。

特に、動的なフィルタリングやカスタム関数を使用したフィルタリングなど、高度なテクニックを駆使することで、さまざまなシチュエーションに対応可能なコードを書くことができるようになります。

これからSwiftでのアプリ開発を行う際、データのフィルタリングが必要になったときは、このガイドを参考にして、効率的かつ効果的なフィルタリングを実現してください。

データベースの操作やフィルタリングはアプリのパフォーマンスやユーザー体験に大きな影響を与えるため、常に最適な方法を選択し、適切に実装することが重要です。

このガイドが、SwiftでのNSPredicateの使用に関するあなたの理解を深める手助けとなり、より高品質なアプリ開発に貢献することを願っています。