

アーティスト名とそのアーティストの曲とそのキーを入力し，Userdefault.standardに保存することでリストを作成するアプリを作成しています．

しかし，ビルドは成功するもののデータを入力する際に，決定ボタンを押すと Thread 1: signal SIGABRT
と表示されアプリが止まってしまいます．エラーコードから推測するとUserdefault.standardに空のartistクラスを保存しようとしていることがいけないのかも知れません．

また，入力するデータを["","",""]
にすると決定ボタンを押しても止まることはありませんでした．なのでif分以下の，データを格納する部分の分岐でエラーが発生していると考えています．

解決方法がわからないため質問させていただきたいです．

**現状のソースコード:**

[code]

    override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            let defaults = UserDefaults.standard
            //クラスの中にアーティスト情報を格納する
    //        for i in 0..<sectionTitle.count {
    //            let band = artist(artist: sectionTitle[i], song: "", key: "")
    //            band.songList = (tableData[i])
    //            allData.append(band)
    //        }
    //        defaults.set(allData, forKey: "allData")
        }
    
        override func viewWillAppear(_ animated: Bool) {
    
            let defaults = UserDefaults.standard
    
            var names:[String] = []
            var songs:[String] = []
            var artistIndex = 0
            var songIndex = 0
            for i in 0..<allData.count {
                names.append(allData[i].artistName)
            }
            if names.firstIndex(of: newSong[0]) != nil{
                artistIndex = names.firstIndex(of: newSong[0])!
                for i in 0..<allData[artistIndex].songList.count {
                    songs.append(allData[artistIndex].songList[i][0])
                }
                if songs.firstIndex(of: newSong[1]) != nil {
                    songIndex = songs.firstIndex(of: newSong[1])!
                }
            }
    
            if newSong == ["","",""] {
                //何もしない
            } else if !names.contains(newSong[0]) {
                //アーティストのクラスを作成し追加
                var tmpData = defaults.array(forKey: "allData") as! [artist]
                let band = artist(artist: newSong[0], song: newSong[1], key: newSong[2])
                tmpData.append(band)
                defaults.set(tmpData, forKey: "allData")
            } else if !songs.contains(newSong[1]) {
                //既存のアーティストに曲を追加
                let index = names.firstIndex(of: newSong[0])!
                var tmpData = defaults.array(forKey: "allData") as! [artist]
                tmpData[index].songList.append(Array(newSong[1...2]))
                defaults.set(tmpData, forKey: "allData")
            } else {
                //既存のアーティストのきょくのキーを変更
                var tmpData = defaults.array(forKey: "allData") as! [artist]
                tmpData[artistIndex].songList[songIndex][1] = newSong[2]
                defaults.set(tmpData, forKey: "allData")
            }
    
    
    //        sectionTitle = defaults.stringArray(forKey: "sectionTitle")!
    //        tableData = defaults.array(forKey: "tableData") as! [[[String]]]
    
            //並べ替え
            allData.sort(by: {String($0.artistName) < String($1.artistName)})
            for i in 0..<allData.count {
                allData[i].songList.sort(by: {String($0[0]) < String($1[0])})
                print(allData[i].artistName, allData[i].songList)
            }
    
    
            let MyTableView: UITableView!
            MyTableView = UITableView(frame: view.frame, style: .grouped)
            MyTableView.delegate = self
            MyTableView.dataSource = self
            view.addSubview(MyTableView)
        }
    
    }
    
    
[/code]

**エラーメッセージ:**

[code]

    2019-04-06 16:31:35.482362+0900 Karaoke[31929:2461031] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
    2019-04-06 16:31:38.481424+0900 Karaoke[31929:2461031] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/hoge/Library/Developer/CoreSimulator/Devices/BF931511-B48E-4CF8-A87C-6CC0D2BEA563/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
    2019-04-06 16:31:38.482555+0900 Karaoke[31929:2461031] [MC] Reading from private effective user settings.
    2019-04-06 16:31:43.654878+0900 Karaoke[31929:2461031] [User Defaults] Attempt to set a non-property-list object (
        "Karaoke.artist"
    ) as an NSUserDefaults/CFPreferences value for key allData
    2019-04-06 16:31:43.665879+0900 Karaoke[31929:2461031] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object (
        "Karaoke.artist"
    ) for key allData'
    *** First throw call stack:
    (
        0   CoreFoundation                      0x000000010eaea6fb __exceptionPreprocess + 331
        1   libobjc.A.dylib                     0x000000010e08eac5 objc_exception_throw + 48
        2   CoreFoundation                      0x000000010eaea555 +[NSException raise:format:] + 197
        3   CoreFoundation                      0x000000010ea0582b _CFPrefsValidateValueForKey + 283
        4   CoreFoundation                      0x000000010ea05c95 -[CFPrefsSource setValues:forKeys:count:copyValues:removeValuesForKeys:count:from:] + 373
        5   CoreFoundation                      0x000000010ea05fbc -[CFPrefsSource setValues:forKeys:count:copyValues:from:] + 28
        6   CoreFoundation                      0x000000010ea06013 -[CFPrefsSource setValue:forKey:from:] + 67
        7   CoreFoundation                      0x000000010eae159e __108-[_CFXPreferences(SearchListAdditions) withSearchListForIdentifier:container:cloudConfigurationURL:perform:]_block_invoke + 318
        8   CoreFoundation                      0x000000010eae0e0a normalizeQuintuplet + 314
        9   CoreFoundation                      0x000000010eae1454 -[_CFXPreferences(SearchListAdditions) withSearchListForIdentifier:container:cloudConfigurationURL:perform:] + 100
        10  CoreFoundation                      0x000000010eabe3fb -[_CFXPreferences setValue:forKey:appIdentifier:container:configurationURL:] + 91
        11  CoreFoundation                      0x000000010eac2065 _CFPreferencesSetAppValueWithContainer + 117
        12  Foundation                          0x000000010db78563 -[NSUserDefaults(NSUserDefaults) setObject:forKey:] + 55
        13  Karaoke                             0x000000010d796de2 $s7Karaoke14ViewControllerC14viewWillAppearyySbF + 6098
        14  Karaoke                             0x000000010d7992a3 $s7Karaoke14ViewControllerC14viewWillAppearyySbFTo + 51
        15  UIKitCore                           0x0000000111d6e437 -[UIViewController _setViewAppearState:isAnimating:] + 687
        16  UIKitCore                           0x0000000111d6eba2 -[UIViewController __viewWillAppear:] + 131
        17  UIKitCore                           0x0000000111cb7491 -[UINavigationController _startCustomTransition:] + 1122
        18  UIKitCore                           0x0000000111ccd31a -[UINavigationController _startDeferredTransitionIfNeeded:] + 741
        19  UIKitCore                           0x0000000111cce6a7 -[UINavigationController __viewWillLayoutSubviews] + 150
        20  UIKitCore                           0x0000000111caf38d -[UILayoutContainerView layoutSubviews] + 217
        21  UIKitCore                           0x00000001128389c1 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1417
        22  QuartzCore                          0x0000000113da9eae -[CALayer layoutSublayers] + 173
        23  QuartzCore                          0x0000000113daeb88 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 396
        24  QuartzCore                          0x0000000113dbaee4 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 72
        25  QuartzCore                          0x0000000113d2a3aa _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 328
        26  QuartzCore                          0x0000000113d61584 _ZN2CA11Transaction6commitEv + 608
        27  QuartzCore                          0x0000000113d61ede _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 76
        28  CoreFoundation                      0x000000010ea510f7 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
        29  CoreFoundation                      0x000000010ea4b5be __CFRunLoopDoObservers + 430
        30  CoreFoundation                      0x000000010ea4bc31 __CFRunLoopRun + 1505
        31  CoreFoundation                      0x000000010ea4b302 CFRunLoopRunSpecific + 626
        32  GraphicsServices                    0x0000000117aec2fe GSEventRunModal + 65
        33  UIKitCore                           0x000000011236aba2 UIApplicationMain + 140
        34  Karaoke                             0x000000010d79e28b main + 75
        35  libdyld.dylib                       0x0000000110edb541 start + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    
[/code]


CC BY-SA 4.0
2019-04-06T12:25:00.310
53979
2021-04-15T04:07:23.910
2021-04-15T04:07:23.910
3060
32808
question
swift ios swift4
Userdefault.standardを用いた際のエラー


まず、ご質問を書かれる際には、必ず関係あるコードは全て含めるようにしてください。

[code]

            var tmpData = defaults.array(forKey: "allData") as! [artist]
    
[/code]

という行にある`artist`とは何のことでしょうか?コンパイルエラーが出ていないので、何かの型名だと思われますが、どんな型でしょう?

あなたが経験された **エラーで一番肝心な部分** は、ここ:

[code]

    Attempt to set a non-property-list object (
        "Karaoke.artist"
    ) as an NSUserDefaults/CFPreferences value for key allData
    
[/code]

「
**`"allData"`というキー名で、plist形式じゃないオブジェクト`Karaoke.artist`型の配列を保存しようとしている!!!**」と言って怒られているわけです。

エラーメッセージに現れている一番肝心なデータ型の説明が全くないのでは、回答の書きようがありません。以下いくつもの推定を含んでいますので、もしかしたら、あなたのコードにはうまく適用できない部分があるかもしれませんが、その場合には、ご質問を編集して必要な情報を追記してください。

なお、Swiftでは型名のみに大文字始まりの識別子を使います。`artist`型は`Artist`とすべき(`MyTableView`は`myTableView`とすべき)です。大文字小文字の使い分けが変なせいで、あなたのコードはSwiftコードとして、とても読みにくいです。

ここでは、イニシャライザから推定して、こんなクラスだと仮定しておきます。

[code]

    class Artist {
        var artistName: String
        var songList: [[String]]
    
        init(artist: String, song: String, key: String) {
            self.artistName = artist
            self.songList = [[song, key]]
        }
    }
    
[/code]

(先の理由で型名は大文字に変更させてもらってます。)

他にも`allData`や、`newSong`など、どう宣言されているのかよくわからないものが出てきています。ご質問を書かれる際には、掲示したコード中で使われている変数は、全てその宣言を示すようにしてください。同じく以下のようなインスタンスプロパティであるものと仮定しています。

[code]

    var allData: [Artist] = []
    var newSong: [String] = ["", "", ""] //<- 呼び出し元のVCがセットする?
    
[/code]

(`newSong`のように[0]番目はアーティスト名、[1]番目は曲名、[2]番目はキー、なんて決まったデータ構造を表すのに、配列を使うのはやめた方が良いでしょう。どう書き換えるかは後の方に。)

* * *

さて、上に書いた「plist形式」のオブジェクトとは以下のようなものを指します。

[code]

    NSNumber     (Swiftのほとんどの数値型、Bool型)
    NSString     (SwiftのString型)
    NSData       (SwiftのData型)
    NSDate       (SwiftのDate型)
    NSArray      (SwiftのArray型、要素型もplist形式でないといけない)
    NSDictionary (SwiftのDictionary型、キーは文字列、値型はplist形式でないといけない)
    
[/code]

あなたの`artist`型(あるいは上の`Artist`型)は、このどれにも入りませんから、`UserDefaults`には保存できない、と言うことになります。

`UserDefaults`に保存したいのであれば、上のどれかの形式に変換してやらないといけません。

これから作られるアプリであれば、`Codable`と`JSONEncoder`を使って、まるごと`Data`型に変換してしまうことになるでしょう。

まずは、`Artist`を`Codable`に適合させておきます。上のままでもいいのですが、せっかくtype-
safeなSwiftを使うのですから、「何でも配列や辞書型」と言うのはできるだけ避けましょう。

[code]

    class Artist: Codable {
        var artistName: String
        var songList: [Song]
    
        init(artist: String, song: String, key: String) {
            self.artistName = artist
            self.songList = [Song(title: song, key: key)]
        }
    }
    
    class Song: Codable {
        var title: String
        var key: String
    
        init(title: String, key: String) {
            self.title = title
            self.key = key
        }
    }
    
[/code]

`Data`への変換はErrorが投げられたりするので、ちょっと面倒ですから、別メソッドにしておきます。

[code]

    func saveArtists(_ tmpData: [Artist]) {
        do {
            let data = try JSONEncoder().encode(tmpData)
            UserDefaults.standard.set(data, forKey: "allData")
            allData = tmpData
    
            sortAllData()
        } catch {
            print(error)
            //...
        }
    }
    
[/code]

データの更新があった場合には、`allData`の方も更新しなければいけないはずで、それはソートされている必要があるようなので、そのコードも追加してあります。`sortAllData()`はあなたのコードにあったものを概ねそのまま使っています。

[code]

    func sortAllData() {
        //並べ替え
        allData.sort(by: {$0.artistName < $1.artistName})
        for i in 0..<allData.count {
            allData[i].songList.sort(by: {$0.title < $1.title})
            print(allData[i].artistName, allData[i].songList)
        }
    }
    
[/code]

ついでに`newSong`のデータ型も「何でも配列」はやめてこんな感じにしてみます。

[code]

    typealias NewSongType = (artist: String, song: String, key: String)
    var newSong: NewSongType = ("", "", "")
    
[/code]

これらを使うと、`viewWillAppear(_ animated: Bool)`はこんな風に書けるでしょう。

[code]

    override func viewWillAppear(_ animated: Bool) {
    
        if newSong == ("","","") {
            //何もしない
        } else if let index = allData.firstIndex(where: {$0.artistName == newSong.artist}) {
            //既存のアーティストの場合
            var tmpData = allData
            if let songIndex = tmpData[index].songList.firstIndex(where: {$0.title == newSong.song}) {
                //既存のアーティストのきょくのキーを変更
                tmpData[index].songList[songIndex].key = newSong.key
            } else {
                //既存のアーティストに曲を追加
                tmpData[index].songList.append(Song(title: newSong.song, key: newSong.key))
            }
            saveArtists(tmpData)
        } else {
            //アーティストのクラスを作成し追加
            var tmpData = allData
            let band = Artist(artist: newSong.artist, song: newSong.song, key: newSong.key)
            tmpData.append(band)
            saveArtists(tmpData)
        }
    }
    
[/code]

あなたのコードでは、`UITableView`のview階層への追加を`viewWillAppear`の中で呼んでいましたが、それだと`viewWillAppear`が呼ばれるたびに新しい`UITableView`が増えていくことになるので、`viewDidLoad()`に移動させる想定です。こんな感じ。

[code]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        let defaults = UserDefaults.standard
        if let data = defaults.data(forKey: "allData") {
            do {
                let tmpData = try JSONDecoder().decode([Artist].self, from: data)
                allData = tmpData
                sortAllData()
            } catch {
                print(error)
                //...
            }
        } else {
            //UserDefaultsが空の時には初期値を設定する
            let tmpData: [Artist] = [/*...*/]
            saveArtists(tmpData)
        }
    
        let myTableView: UITableView!
        myTableView = UITableView(frame: view.frame, style: .grouped)
        myTableView.delegate = self
        myTableView.dataSource = self
        view.addSubview(myTableView)
    }
    
[/code]

あちこち推定で補っている上に、割と大きな書き換えになるので大変でしょうが、試してみてください。

* * *

とここまで長々と書いてきてなんですが、`UserDefaults`と言うのは、その名の通り、各アプリの設定画面から設定できる程度の情報(せいぜい2〜30項目を想定している)を保存することを主眼に設計されたクラスです。

あなたのが`allData`に保存したいデータ量は何百、もしかしたら何千何万にもなるのではないですか?
`UserDefaults`は、そのような大量のデータを保存するのには向いていません。

そのお手軽さゆえに、ちょっとしたブログやチュートリアルのサンプルコードでは、このようなデータの保存に`UserDefaults`を使っている例が多くみられますが、あなたのアプリを「ちょっとしたサンプルコード」以上のものにしたいのであれば、`UserDefaults`は使わず、Core
Dataあるいはその他大量データの保存に適したデータフレームワークを使用した方が良いでしょう。


CC BY-SA 4.0
2019-04-06T15:37:45.410
53984
2019-04-06T15:37:45.410
13972
53979
answer