2017-02-05 2 views
0

Я работаю над проектом, где мне нужно записать некоторые объекты на диск, а затем вернуть их позже. Я использовал NSKeyedArchiver и NSKeyedUnarchiver, чтобы сделать запись и чтение, убедившись, что мои пользовательские классы соответствуют NSCoding. Он не работал, и поэтому, после долгого времени отладки и поиска в Интернете, я решил изолировать проблему.Почему NSKeyedArchiver и NSKeyedUnarchiver работают так, как ожидалось?

Может кто-нибудь рассказать мне, почему этот код не делает то, что я ожидаю от него? Это, вероятно, очевидно, но, глядя на него в течение многих часов, я все еще не могу разобраться.

У меня есть контроллер представления, определенный в Main.storyboard с UILabel, в UITextField и UIButton. Идея состоит в том, что пользователь может ввести что-то, нажмите кнопку «Опубликовать», и на этикетке будет отображаться то, что они ввели. Каждый раз, когда пользователь вводит что-то, я хочу, чтобы оно было записано на диск, чтобы, если приложение завершено и представление снова загружено, сообщение возвращается и отображается меткой.

В настоящий момент на этикетке отображается то, что пользователь ввел, но если я уйду из приложения и снова открою его, на этикетке просто будет указано «Ваше сообщение здесь», которое является строкой, которую я назначил ей в раскадровке.

У меня есть ощущение, что это может быть что-то делать с unarchiveObject(withFile:) вызова и/или приведения типов в String, потому что я прочитал где-то, что так как Swift 3, типы значений должны быть Разархивирован, используя свой собственный метод (например, unarchiveBool(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 не может работать непосредственно с String, так что я должен обернуть свои данные в классе, который соответствует NSCoding. Так вот небольшой класс, который пытается сделать это:

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 
} 

Я неверно истолковал «Обернуть ваши данные в классе»?

+0

Вы прошли через отладчик? – Paulw11

+0

Да. Кажется, что запись данных успешно (без исключения), но когда я перестраиваю и запускаю приложение, строка 'loadedMessage' имеет« неспособную прочитать данные », написанную рядом с ней в средстве просмотра локальных переменных. Исходя из этого, я думаю, что существует либо проблема с тем, как данные записываются (чтобы их нельзя было прочитать позже), либо просто возникает проблема с ее чтением. – xander

+0

Теперь я установил 'filePath' в' URL (fileURLWithPath: «/ Users/Xander/Desktop») .appendingPathComponent («message») ', чтобы узнать, что произойдет, если я попытаюсь написать прямо на мой рабочий стол. Он работает, и файл, как представляется, содержит данные. Все еще не удалось его разблокировать, хотя ... – xander

ответ

1

Обновленный ответ:

Изменение filePath.absoluteString в filePath.path.

Оригинальный ответ:

NSCoder не может работать непосредственно с Swift структуры, такие как String. Вам нужно будет обернуть ваши данные в класс и соответствовать NSCoding, чтобы использовать NSKeyedArchiver.

Пример:

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

Вам не нужно, чтобы создать свою собственную-структуру, Вам даже не нужен архиватор/распаковщик для простой строки. Строку можно читать и записывать непосредственно в 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», а не только корневому объекту. И в этом случае обработка неконкурентного типа ('String') в соответствующем типе (' DataThing'), похоже, не помогла бы. – xander

+0

Для простых структур вы можете просто создать представление словаря, прочитать и записать в plists. Для чего-то более сложного существуют другие формы упорства, которые могут быть более подходящими (основные данные, царство, мантия и т. Д.), – Abizern

Смежные вопросы