The only thing we have is what is available to use, which is stored in vocabData
. So if you want to restart it, you just need to fill in the vocabData
with all items, then shuffle again. Sounds familiar? Because that's exactly what loadWord
does! So you can just call the function.
Let's look at loadWords
a little bit:
func loadWords() {
// Get URL
guard let jsonURL = Bundle.main.url(forResource: "vocabList", withExtension: "json") else {
return
}
do {
// Get JSON string
let jsonString = try String(contentsOf: jsonURL, encoding: String.Encoding.utf8) else {
// Decode JSON String, and put it into `vocabData`
vocabData = try JSONDecoder().decode([VocabData].self, from: Data(jsonString.utf8))
// Shuffle data in-place
vocabData.shuffle()
} catch {
print(error)
}
}
- We read the file into
jsonString
,
- Decode it into
vocabData
, then
- Shuffle
vocabData
.
When we put the file content into jsonString
, we copy the content into jsonString
. Mutating jsonString
won't change the file content. Likewise, changing the file won't mutate the jsonString
. Similarly, when you decode the string and put it in vocabData
, the content of vocabData
and jsonString
are independent of each other. That's why when you shuffle vocabData
, jsonString
doesn't get jumbled.
So removeLast
does mutate the memory of vocabData
, but doesn't touch jsonString
(not that it still exists), and won't be altering the original file.
It depends on how you update it. As mentioned earlier, the content of the file are separated from the content of the vocabData
. So if you update the file, vocabData
won't change.
You can change the vocabData
as you see fit, it only represent the items that can be selected, in a random order. So long as you maintain that notion, it should be fine.
So, to meet the new requirement to restart the when vocabData
is empty, the randomWord
becomes:
func randomWord() {
if vocabData.isEmpty {
loadWords()
}
assert(!vocabData.isEmpty)
let randomVocab = vocabData.removeLast()
definitionLabel.text = randomVocab.Definition
chineseLabel.text = randomVocab.Chinese
pinyinLabel.text = randomVocab.Pinyin
}
Note that I added a little function call assert(...)
. This function will crash the program if the condition inside the call is false (when you're in debug mode). I out it there because of what the code before just did:
if vocabData.isEmpty {
loadWords()
}
What did we just do? "if vocabData
is empty, load the words (into vocabData
)." So vocabData
can't be empty right after.
- If it was't empty at the beginning of
randomWord
, it surely isn't right now,
- If it was empty at the beginning of
randomWord
, we just reload it.
Note that it can still be empty if, it was empty at the beginning and the file contains no item. If you want to handle it, you can replace assert
with appropriate checking.
Now after we assert that vocabData
isn't empty, we can just assume it to be so afterward (we haven't mutate vocabData
, there's no reason to think otherwise). So we can remove the unnecessary if
.