2017-02-05 3 views
0

私はいくつかのオブジェクトをディスクに書き込んだ後、後でそれらを再び取得する必要があるプロジェクトに取り組んできました。私はNSKeyedArchiverNSKeyedUnarchiverを使って書き込みと読み取りを行い、私のカスタムクラスがNSCodingに適合していることを確認しました。それは動作していなかったので、それをデバッグしてWebを検索しようとした後、私は問題を切り分けることに決めました。NSKeyedArchiverとNSKeyedUnarchiverがここで期待どおりに動作しないのはなぜですか?

誰かがこのコードが私が期待することをしない理由を教えてください。それはおそらく明らかですが、何時間もそれを見つめた後、私はまだそれを解決することはできません。

私は、UILabelUITextFieldおよびUIButtonMain.storyboardで定義されたビューコントローラを持っています。ユーザーは何かを入力して「投稿」ボタンを押すと、入力した内容がラベルに表示されます。ユーザーが何かを入力するたびに、ディスクに書き込むようにして、アプリケーションを終了してビューを再度読み込むと、メッセージが戻ってラベルに表示されるようにします。

現在のところ、ラベルにはユーザーが入力した内容が表示されますが、アプリを終了してもう一度開くと、ラベルにはストーリーボードに割り当てられた文字列の「Your message here」と表示されます。

私は、私はスウィフト3以降、値の型は、独自の方法(例えばunarchiveBool(withFile:))を使用して、アーカイブされていないされなければならないことをどこかで読んので、StringunarchiveObject(withFile:)コールおよび/または型キャストとは何かかもしれないと感じていますStringのような方法はないようですが。

import UIKit 

class ViewController: UIViewController { 

    @IBOutlet weak var outputLabel: UILabel! 
    @IBOutlet weak var textBox: UITextField! 

    var filePath: URL! 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     filePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("message") 

     // Load message 
     if let loadedMessage = NSKeyedUnarchiver.unarchiveObject(withFile: filePath.absoluteString) as? String { 
      outputLabel.text = loadedMessage 
     } 
    } 

    @IBAction func textFieldDoneEditing(_ sender: UITextField) { 
     sender.resignFirstResponder() 
    } 

    @IBAction func postButtonTapped(_ sender: Any) { 
     outputLabel.text = textBox.text 
     textBox.text = "" 

     let str = outputLabel.text! 

     // Save message  
     let data = NSKeyedArchiver.archivedData(withRootObject: str) 
     do { 
      try data.write(to: filePath) 
     } catch { 
      print("Couldn't write file.") 
     } 
    } 
} 

さて、@gkchristopherはNSCoderはので、私はNSCodingに準拠したクラスで私のデータをラップする必要がありStringを直接操作することができないことを指摘しています。そこでここではそれを行うにしようと小さなクラスだ:

class DataThing: NSObject, NSCoding { 
    var message: String! 

    init(withMessage message: String) { 
     self.message = message 
    } 

    required convenience init?(coder aDecoder: NSCoder) { 
     self.init(withMessage: aDecoder.decodeObject(forKey: "message") as! String) 
    } 

    func encode(with aCoder: NSCoder) { 
     aCoder.encode(message, forKey: "message") 
    } 
} 

私はこのクラスを使用してデータの罰金を書き込むことができます。

let dataThing = DataThing(withMessage: str) 

     // Save message 
     let data = NSKeyedArchiver.archivedData(withRootObject: dataThing) 
     do { 
      try data.write(to: filePath) 
     } catch { 
      print("couldn't write file.") 
     } 

しかし、読書はまだ動作しません:

if let loadedData = NSKeyedUnarchiver.unarchiveObject(withFile: filePath.absoluteString) as? DataThing { 
      outputLabel.text = loadedData.message 
} 

私は「クラスでデータをラップする」と誤解しましたか? filePath.path

変更filePath.absoluteString

+0

デバッガを使用しましたか? – Paulw11

+0

はい。データを正常に出力しているようですが(例外はスローされません)、アプリケーションを再構築して実行すると、ローカル変数ビューアの隣に 'loadedMessage'文字列に 'データを読み取れません'という文字列があります。それに基づいて、私は、データが書き込まれる方法(後で読むことができないような)に問題があるか、または単にそれを読むことに問題があると思います。 – xander

+0

ファイルパスを 'URL(fileURLWithPath:"/Users/Xander/Desktop ").appendingPathComponent(" message ")'に設定しました。それは動作し、ファイルにはデータが含まれているように見えます。しかしまだそれを解凍することに失敗しました... – xander

答えて

1

は答えを更新しました。

オリジナルの答え:

NSCoderは、Stringとして、スウィフト構造体を直接操作しない場合があります。 NSKeyedArchiverを使用するには、クラスにデータをラップし、NSCodingに準拠する必要があります。

例:あなたがあなた自身の構造体を作成する必要はありません

let file = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("message") 
let dataThing = DataThing(withMessage: "Boo") 
NSKeyedArchiver.archiveRootObject(dataThing, toFile: file.path) 

let retrieved = NSKeyedUnarchiver.unarchiveObject(withFile: file.path) 

dump(retrieved) 
+0

そのクラスはコメントでは非常に読みにくいですが、私はこの追加をどこに投稿するか分かりません。 – xander

+0

@xander質問を編集して更新情報を追加することができます。 – Abizern

+0

@Abizern私は知っている、私はちょうどそれを行うに理にかなっていないと思った...私は今でもなります。 – xander

0

、あなたも、単純な文字列のためのアーカイバ/ Unarchiverを必要としません。 StringはDataから直接読み書きできます。

このようにコードを書き直してください。

class ViewController: UIViewController { 

    @IBOutlet var label: UILabel! 
    @IBOutlet var textField: UITextField! 

    lazy var fileURL: URL = { 
     do { 
      var url = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) 
      url.appendPathComponent("message") 
      return url 
     } catch { 
      fatalError("Cannot create storage file") 
     } 
    }() 


    override func viewDidLoad() { 
     super.viewDidLoad() 
     do { 
      let message = try String(contentsOf: fileURL) 
      label.text = message 

     } catch { 
      label.text = "No message available" 
     } 
    } 

    @IBAction func post(_ sender: UIButton) { 
     label.text = textField.text 

     guard 
      let text = textField.text, 
      let data = text.data(using: .utf8), 
      text != "" 
      else { return } 

     do { 
      try data.write(to: fileURL) 
     } catch { 
      print(error) 
     } 

     textField.text = "" 
    } 

} 

私は少し速く、エラーで緩んできました。アプリケーション固有のものとして扱うことができます。

+0

私はそう思った。すべての子オブジェクトがルートオブジェクトだけでなく、 'NSCoding'に準拠することは意味があります。そして、その場合、準拠型( 'DataThing')で不適合型(' String')をラップすることは助けにはならないでしょう。 – xander

+0

単純な構造体の場合は、辞書表現を作成してplistを読み書きすることができます。もっと複雑なものには、より適切な他の形態の永続性(Core Data、Realm、Mantleなど)があります。 – Abizern

関連する問題