NSKeyedUnarchiveFromDataに代わるカスタム値変換の実装について

主題
macOS 10.14 MojaveからDeprecateされた、NSKeyedUnarchiveFromDataに代わるカスタム値変換の実装について述べる。

背景
筆者が開発しているアプリの1つでは、テーブルのソート状態の保存にCocoa-Bindingsを用いている。ソート状態の保存の際、NSKeyedUnarchiveFromDataという値変換を用いてきたが、これはmacOS 10.14 MojaveからDeprecateされている。
本稿では、カスタム値変換の実装を通じて、この問題に対処する手順を述べる。

手順

  1. カスタム値変換の実装
    NSSecureUnarchiveFromDataTransformerを継承するカスタム値変換を実装する。ファイル名はMyTransformer.swiftとする。
    NSSecureUnarchiveFromDataTransformerは、そのままではテーブルのソート状態(NSSortDescriptor)を扱えない。よってカスタム値変換は、テーブルのソートに応用するケースでは必須である。
    各コードの説明は、コメントを参照されたい。

                    //
                    //  MyTransformer.swift
                    //  SecureTransformer
                    //
                    //  Created by 桃源老師 on 2020/12/03.
                    //
    
                    import Cocoa
    
                    class MyTransformer: NSSecureUnarchiveFromDataTransformer {
    
                        /*
                         値の逆変換ができるかどうかを規定するメソッド。(必須)
                         できないのでfalse
                        */
                        override class func allowsReverseTransformation() -> Bool {
                            return false
                        }
    
                        /*
                         変換されたオブジェクトのクラスを規定するメソッド。(必須)
                         NSArrayを返すので、NSArray.self
                         (ObjC的には[NSArray class])
                        */
                        override class func transformedValueClass() -> AnyClass {
                            return NSArray.self
                        }
    
                        /*
                         NSSecureUnarchiveFromDataTransformerが扱える型を規定するクラス変数。
                         NSSortDescriptorを扱えるように拡張する。
                        */
                        override class var allowedTopLevelClasses: [AnyClass] {
                            return [NSArray.self, NSSortDescriptor.self]
                        }
    
                        // 値の変換メソッド。(必須)
                        override func transformedValue(_ value: Any?) -> Any? {
    
                            // valueがnilであること=ソート状態未設定を考慮。
                            if let nonNilValue = value {
    
                                // Data型かどうかをチェック
                                guard let data = nonNilValue as? Data else {
                                    fatalError("Wrong data type: value must be a Data object; received \(type(of: nonNilValue))")
                                }
    
                                /*
                                 以前アーカイブされた値(=UserDefaultsに保存された
                                 ソート状態)のデコードを行う。例外を投げるのでtryを
                                 つける。
                                */
                                let object = try?
                                    NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, NSSortDescriptor.self], from: data)
    
                                /*
                                 デコードされたデータが、[NSSortDescriptor]型かを
                                 チェック
                                */
                                guard let sortDescriptors = object as? [NSSortDescriptor] else {
                                    fatalError("Failed to unarchive a [NSSortDescriptor] object!")
                                }
    
                                /*
                                 デコードされたデータに`allowEvaluation()`を設定。
                                 デコード時点では、Evaluationは許可されていない。
                                 Evaluation不許可だと、それが原因でクラッシュする。
                                */
                                for descriptor in sortDescriptors {
                                    descriptor.allowEvaluation()
                                }
    
                                return sortDescriptors
                            }
                            return value
                        }
                    }
    
                    // NSValueTransformerNameへの"MyTransformer"の登録
                    extension NSValueTransformerName {
                        static let myTransformerName = NSValueTransformerName(rawValue: "MyTransformer")
                    }
                
  2. ValueTransformerへの登録
    カスタム値変換のValueTransformerへの登録は、アプリのごく初期状態で行う必要がある。よって、本稿ではAppDelegateのイニシャライザで行う。

                    //
                    //  AppDelegate.swift
                    //  SecureTransformer
                    //
                    //  Created by 桃源老師 on 2020/12/03.
                    //
    
                    import Cocoa
    
                    @main
                    class AppDelegate: NSObject, NSApplicationDelegate {
    
                        /*
                         カスタム値変換のValueTransformerへの登録をAppDelegateのイニシャライザで行う。
                        */
                        override
                        init() {
                            super.init()
                            ValueTransformer.setValueTransformer( MyTransformer(), forName: .myTransformerName )
                        }
                        
                        func applicationDidFinishLaunching(_ aNotification: Notification) {
                            // Insert code here to initialize your application
                        }
    
                        func applicationWillTerminate(_ aNotification: Notification) {
                            // Insert code here to tear down your application
                        }
    
                    }
                
  3. ArrayControllerの設定
    Main.storyboard上で、ArrayControllerのSort Descriptorsに、カスタム値変換のクラス名を入力する。

    ArrayControllerの設定

以上。

この投稿へのコメント

コメントはありません。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

この投稿へのトラックバック

トラックバックはありません。

トラックバック URL