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