Save and persist data with UserDefaults | Todo List #4

Swiftful Thinking
17 Mar 202114:41

Summary

TLDR在这个视频中,Nick 介绍了如何在他们正在开发的应用程序中实现数据持久化。他们指出,尽管应用程序可以正常工作,但之前并未保存任何数据,导致关闭并重新打开应用程序后,所有新增的待办事项都会丢失。为了解决这个问题,Nick 决定使用 UserDefaults 来保存待办事项列表。他解释说,UserDefaults 适用于存储较小的数据片段,例如用户 ID 或当前用户的名称。在本例中,由于待办事项模型只包含标题和布尔值,因此使用 UserDefaults 是合适的。Nick 展示了如何将待办事项模型转换为 JSON 数据,然后存储到 UserDefaults 中,并在需要时再将其转换回模型。他还展示了如何通过在待办事项数组上使用计算属性和 didSet 来确保每次更改数组时都会保存到 UserDefaults。最后,他演示了如何从 UserDefaults 中检索数据,并更新应用程序中的待办事项列表。这个视频不仅提供了关于如何使用 UserDefaults 的实用指导,还练习了使用 guard 语句和 if let 语句进行可选绑定。

Takeaways

  • 📝 课程中构建的应用程序可以正常工作,但之前没有实现数据保存功能,关闭再打开应用后,新添加的待办事项会丢失。
  • 🔄 为了解决数据保存问题,视频介绍了如何使用用户默认设置(UserDefaults)来保存待办事项列表,使其在会话之间持久化。
  • 📚 用户默认设置(UserDefaults)主要用于存储较小的数据片段,例如用户ID或用户名,而不适用于大型数据库。
  • 🛠️ 通过将待办事项列表(Item Models)转换为JSON数据,可以存储在UserDefaults中,之后可以再次转换回待办事项列表。
  • 🔑 为了能够将待办事项列表编码为数据,Item Model需要符合Codable协议,这样可以实现数据的编码和解码。
  • 🔩 在ListView Model中创建了一个新的函数`saveItems`,用于将待办事项列表编码并存储到UserDefaults中。
  • 🔄 通过计算属性和`didSet`属性观察器,确保每次待办事项列表(items array)发生变化时,都会调用`saveItems`函数进行保存。
  • 🔑 引入了一个常量`itemsKey`作为UserDefaults中的键,避免直接在代码中硬编码键名,提高代码的可维护性。
  • 📈 为了从UserDefaults中恢复待办事项列表,需要使用JSON Decoder将存储的数据解码回Item Models数组。
  • 🛡️ 使用`guard`语句来安全地解包可选类型,确保数据存在且可以成功转换为待办事项列表。
  • 🔄 通过在应用启动时从UserDefaults获取数据,并更新待办事项列表,实现了数据的持久化。
  • 🎉 关闭应用后再打开,可以看到之前添加和修改的待办事项都被保存了下来,演示了数据持久化的成功实现。

Q & A

  • 为什么在应用程序中添加了新的任务后,关闭再打开应用,任务会消失?

    -这是因为应用尚未实现数据持久化功能,关闭应用后,之前添加的任务没有被保存,所以重新打开应用时会恢复到初始状态。

  • 为了使应用中的数据在关闭后重新打开依然存在,应该使用什么技术?

    -在视频中,Nick提到如果是为了上架App Store的应用,可能会使用Core Data。但在这个例子中,为了简单,选择使用了UserDefaults。

  • 为什么在Swift UI Bootcamp课程中没有覆盖Core Data的内容?

    -Nick没有在Swift UI Bootcamp中覆盖Core Data的内容,可能是因为课程的重点在于其他方面,或者是时间限制,或者是为了让课程更加集中和易于消化。

  • UserDefaults适合存储哪些类型的数据?

    -UserDefaults主要适合存储较小的数据,例如用户ID或当前用户的姓名。对于较大的数据集,应该使用其他存储解决方案,如Core Data。

  • 为什么在ListView Model中使用UserDefaults而不是App Storage?

    -因为App Storage更适合在视图中直接使用,而ListView Model是一个类,所以在这个上下文中使用UserDefaults更为合适。

  • 如何将Item Model转换为可以存储在UserDefaults中的数据?

    -通过将Item Model转换为JSON数据。首先,需要让Item Model符合Codable协议,然后使用JSON编码器将模型数组编码为数据。

  • 为什么需要将Item Model符合Codable协议?

    -Codable协议允许数据模型被编码和解码,这意味着可以将模型转换为数据(如JSON格式),并从数据重建模型。

  • 在ListView Model中,如何确保每次更新items数组时都保存数据?

    -通过在items属性上使用计算属性,并在didSet属性观察器中调用saveItems函数来实现。

  • 如何从UserDefaults中检索并恢复保存的任务列表?

    -首先从UserDefaults中获取保存的数据,然后使用JSON解码器将数据解码回Item Model的数组,并用这个数组更新items属性。

  • 为什么在获取UserDefaults中的数据时要使用guard语句?

    -使用guard语句是为了安全地处理可选值。如果从UserDefaults中获取的数据不存在,或者无法将其解码为Item Model数组,guard语句可以防止程序崩溃,并提供一个明确的退出点。

  • 在视频中提到的“saveItems”函数的作用是什么?

    -“saveItems”函数的作用是将当前的items数组(包含待办事项)编码为JSON数据,并将其存储到UserDefaults中,以实现数据的持久化。

  • 为什么在视频中不直接在每个修改items数组的函数末尾添加saveItems调用?

    -为了代码的效率和清晰,通过在items属性的didSet属性观察器中调用saveItems,可以确保无论何时items数组被修改,都会自动保存,而不是在每个修改函数中重复添加保存逻辑。

Outlines

00:00

🔄 应用数据持久化问题

Nick在视频中介绍了他们的应用虽然已经可以正常运行,但存在一个问题:应用没有保存任何数据。这意味着,如果关闭应用再重新打开,新添加的待办事项并不会被保存。为了解决这个问题,他计划通过使用用户默认设置(UserDefaults)来保存待办事项列表,确保数据在会话之间持久化。他还提到,如果应用要上架App Store,可能会使用Core Data,但由于在Swift UI训练营中尚未涉及Core Data,因此选择使用UserDefaults。他强调UserDefaults主要用于存储较小的数据片段,如用户ID或用户名,对于待办事项列表这种数据量不大的情况,使用UserDefaults是合适的。

05:00

📝 UserDefaults的使用和数据编码

为了使用UserDefaults保存待办事项,Nick创建了一个名为saveItems的新函数。他解释说,UserDefaults无法直接存储ItemModel对象,因此需要将这些对象转换为JSON数据。他通过使ItemModel符合Codable协议,从而能够将ItemModel编码和解码为数据。在saveItems函数中,他使用JSON编码器将待办事项数组编码为数据,并将其存储在UserDefaults中。为了确保每次待办事项数组发生变化时都能保存,他在items数组上添加了一个计算属性,并在该属性的didSet属性中调用saveItems。此外,他还修改了获取待办事项的函数,以便从UserDefaults中检索数据,并将其转换回ItemModel数组。

10:01

📱 应用数据的恢复和测试

最后,Nick展示了如何从UserDefaults中恢复数据,并在模拟器中测试应用。他首先移除了用于初始化的三个假待办事项,然后通过添加新事项、保存、标记完成和编辑等操作来测试数据是否能够正确保存和恢复。他强调了使用guard语句和if let语句的重要性,这些语句在Swift中用于安全的可选值解包。通过这些步骤,他证明了应用现在能够保存待办事项,并且在重新启动后能够恢复这些数据。视频以鼓励观众继续关注和学习结尾,并邀请观众订阅频道。

Mindmap

Keywords

💡用户默认值(UserDefaults)

用户默认值是iOS开发中用于存储小型数据的一种持久化存储方式,如用户偏好设置或用户ID。在视频中,它被用来存储待办事项列表的数据,以确保用户在关闭并重新打开应用后,之前添加的待办事项能够被保留。

💡数据持久化

数据持久化是指将数据存储在非易失性存储中,以保证在程序关闭后数据依然存在。视频主要讲述了如何通过用户默认值来实现待办事项应用中数据的持久化存储。

💡SwiftUI

SwiftUI是苹果公司推出的一种用于构建iOS、macOS等平台用户界面的框架。视频中提到的应用开发就是基于SwiftUI框架进行的。

💡Codable协议

Codable协议在Swift中用于将数据模型转换为外部表示(如JSON),或将外部数据转换为模型。在视频中,待办事项的数据模型通过遵循Codable协议,实现了与JSON数据的相互转换,以便于存储和读取。

💡JSON数据

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。视频中使用JSON数据格式来存储和读取待办事项列表。

💡待办事项列表(To-Do List)

待办事项列表是视频中开发的应用的核心功能,允许用户添加、删除、标记完成等操作。通过使用用户默认值,待办事项列表的数据能够在应用会话之间保持持久化。

💡视图模型(ViewModel)

视图模型是MVVM(Model-View-ViewModel)设计模式中的一个组件,负责处理数据逻辑和UI的交互。在视频中,待办事项列表的数据持久化逻辑就是在视图模型中实现的。

💡计算属性(Computed Property)

计算属性是Swift中的一种属性,它根据其他属性的值动态计算其结果。视频中通过计算属性的didSet属性,每次待办事项数组发生变化时自动调用保存方法,确保数据的及时更新。

💡守卫语句(Guard Statement)

守卫语句是Swift中的一个控制流语句,用于确保某个条件为真时才执行代码块。在视频中,守卫语句用于安全地解包可选类型,并在条件不满足时提前退出函数。

💡数据模型(Data Model)

数据模型是应用程序中用于存储和管理数据的结构。在视频中,待办事项列表的数据模型被用来表示待办事项的标题和完成状态,并且这个模型遵循了Codable协议以实现数据的编码和解码。

💡模拟器(Simulator)

模拟器是一种软件,可以模拟特定设备的硬件和操作系统环境,允许开发者在没有物理设备的情况下测试应用程序。视频中提到了在模拟器上测试待办事项应用的数据持久化功能。

Highlights

在课程中构建的应用程序可以正常工作,但尚未实现数据保存功能。

关闭应用程序后,新添加的待办事项不会保存。

通过使用用户默认设置(UserDefaults)来保存待办事项列表。

用户默认设置主要适用于存储较小的数据片段,例如用户ID或用户名。

如果待办事项列表很大,不应使用用户默认设置来存储。

将创建一个新的函数来保存待办事项数组。

使用JSON数据格式来存储Item模型,因为UserDefaults无法直接存储Item模型。

Item模型需要符合Codable协议,以便能够编码和解码成JSON数据。

通过编码将待办事项数组转换为JSON数据,并存储到UserDefaults中。

使用guard语句来安全地解包从UserDefaults中检索的数据。

如果从UserDefaults中成功获取数据,将使用JSON解码器将其解码回Item模型数组。

通过计算属性和didSet方法,在每次待办事项数组更改时自动保存数据。

不再使用固定的三个初始待办事项,而是从UserDefaults中获取并追加保存的待办事项。

演示了应用程序关闭后重新打开,待办事项数据仍然被保存和持久化。

提供了对guard语句和if let语句的实践,展示了它们在不同情况下的用法。

代码实现高效且简洁,展示了如何将待办事项列表保存在用户默认设置中。

感谢观看,并鼓励订阅频道,以便继续学习。

Transcripts

play00:00

[Music]

play00:06

what's up everyone i'm nick

play00:07

and so far in this course we've built

play00:10

the majority of our app our app is

play00:12

totally working

play00:13

but you might have noticed that it

play00:14

doesn't actually save any of our data

play00:17

so if we close the app and reopen the

play00:19

app it doesn't save any of those

play00:21

new to do items that we added so in this

play00:23

video really quickly we're just going to

play00:25

incorporate

play00:25

user defaults so that we can save our

play00:28

to-do list items so that they persist

play00:31

and save between sessions

play00:35

all right so i'm back in our project

play00:39

and so far in this course we have done

play00:40

the bulk of the code the bulk of the app

play00:44

we have our app we have our screens we

play00:46

can add items

play00:47

to our app we can delete items

play00:50

we can toggle their completion

play00:54

but one thing you might have noticed is

play00:56

that if we edit this list

play00:58

and we close the app

play01:01

and then reopen the app it defaults back

play01:04

to the starting state

play01:06

and right now it has that those three

play01:08

item models that we create on the init

play01:11

when we call get items

play01:12

and all the work that we just did adding

play01:15

updating items

play01:16

is not saving and that's because we

play01:18

haven't saved it anywhere and we're not

play01:20

persisting the data

play01:22

so we need a place to save our items

play01:24

array

play01:25

when we change it when we update it

play01:28

now if this was an app that i was

play01:30

putting in the app store i would

play01:31

probably use

play01:32

core data to do this but i haven't

play01:35

covered core data in the swift ui

play01:36

bootcamp

play01:37

so even simpler than core data we're

play01:39

just going to use user defaults

play01:42

and before we get into it i want to

play01:43

quickly note that user defaults

play01:46

should predominantly be used for smaller

play01:48

pieces of data

play01:50

so something like a user id or your

play01:52

current user's

play01:53

name but in this case our item model is

play01:56

fairly

play01:57

small pieces of data it is just a title

play02:00

and a boolean

play02:01

and we only have a couple item models we

play02:03

don't have like

play02:04

thousands or a hundred thousand

play02:06

different items in our to-do list

play02:08

so it's not a big deal that we're using

play02:10

user defaults

play02:12

but if we had a larger database i just

play02:14

want you guys to be aware that user

play02:16

defaults should

play02:17

really be used for smaller pieces of

play02:19

information

play02:20

and it do not start incorporating large

play02:22

data sets into

play02:24

user defaults but with that said user

play02:27

defaults is

play02:28

very easy to use and it is perfect for

play02:30

what we are going to do

play02:31

here so we are going to create a new

play02:34

function at the bottom of our

play02:36

list view model so i'm in the list view

play02:38

model i'm going to go down to the bottom

play02:40

and after update item i'm going to add

play02:42

func save

play02:43

items open close parenthesis and open

play02:46

the brackets

play02:49

and i did a whole video in the swiftjaw

play02:50

bootcamp on app storage

play02:52

and you might be wondering why we're not

play02:53

using app storage in right now and

play02:56

that's because we are in a class

play02:58

an app storage should be used if you're

play03:00

going to use it in the view directly but

play03:01

since we're in a class

play03:03

it's better to use user defaults so

play03:05

we're going to call

play03:08

userdefault.standard.set

play03:10

and if we look at these value types here

play03:13

that we can

play03:13

add you'll notice that none of them

play03:16

clearly say

play03:17

item model and that's because user

play03:19

defaults has no idea what an item model

play03:21

is

play03:22

and we can't actually save an item model

play03:24

into user defaults

play03:26

so i'm going to delete this quickly and

play03:29

what we're going to do is basically

play03:30

convert

play03:31

all these item models into json

play03:34

data so if you have been a developer

play03:37

maybe like a web developer or something

play03:39

you're probably familiar with json data

play03:41

is basically just a data blob and it's

play03:44

going to take our item model it's going

play03:45

to convert it into this data blob

play03:47

we're going to put that data into user

play03:50

defaults

play03:51

and then when we retrieve it we're just

play03:53

going to convert it back from the data

play03:55

to item models so to do that

play03:59

we're going to jump into the item model

play04:01

i'm going to jump to definition

play04:03

and all we have to do is make this

play04:05

conform to codable so i'll do comma

play04:07

space

play04:08

codable and in the next series i'm going

play04:10

to do a whole video on codable because

play04:12

there's a lot going on behind the scenes

play04:14

here and this is super powerful

play04:16

but all you need to know is that because

play04:18

item model is now conforming to

play04:20

codable we can decode and

play04:23

encode this item so we can transform an

play04:26

item model

play04:27

into basically into data and then out of

play04:30

data

play04:32

so now that it's conformed to codable

play04:34

let's go back

play04:37

to our save items and we're going to

play04:40

take

play04:41

this items array here which is an array

play04:43

of item model

play04:44

and try to encode it into data

play04:48

so very simply we'll say if let

play04:52

encoded data equals

play04:56

try with a question mark

play05:00

we're going to create a json encoder

play05:02

with an open and close parenthesis

play05:05

and then on this encoder we're going to

play05:06

call dot encode

play05:08

and what do we want to encode here we

play05:11

want to include our items array

play05:13

then we're going to open the brackets

play05:15

because this is an if let's statement

play05:17

we have to use this try method for the

play05:18

json encoder

play05:20

and that basically is just going to try

play05:23

to do this and it's possible that it

play05:26

might fail but that's okay and that's

play05:27

why we're in this if let's statement

play05:30

so if this is successful we would have

play05:33

created a json encoder

play05:35

which which can convert things into json

play05:38

data

play05:39

and then we're going to encode our items

play05:41

so our items array is going to convert

play05:42

from an items array

play05:44

into json data and that's what this

play05:47

encoded data is and we're just going to

play05:49

put this encoded data

play05:50

into our user defaults so we'll call

play05:55

userdefaults.standard.set

play05:56

we'll go down to the value with any and

play05:59

we're going to include our encoded data

play06:03

and for key and now we need a key for

play06:05

the user defaults and i'm just going to

play06:06

call this

play06:07

items underscore list

play06:11

now we're going to have to reference

play06:12

this key again when we want to pull the

play06:14

data back from user defaults

play06:16

so i don't like that we're typing it in

play06:18

here directly because if we

play06:20

later have to have to refer to this key

play06:22

and maybe we

play06:23

mistype it or we forget what we put here

play06:25

it could run into issues

play06:26

so i'm going to make this key a

play06:28

standalone variable

play06:30

so at the top underneath our items let's

play06:33

call

play06:34

let items key of type string

play06:37

and we'll set it equal to items

play06:39

underscore list

play06:41

we're going to take this items key and

play06:43

we're going to put it down here

play06:46

as our 4 key now we have our function to

play06:50

save items it's going to convert our

play06:51

items array into data

play06:53

and then put that data into user

play06:54

defaults

play06:56

and all we have to do now is actually

play06:58

call save items

play06:59

from somewhere in our code and i want to

play07:02

make sure that these user defaults are

play07:04

is always updated and in line with

play07:06

what's going on in our list

play07:08

so if we delete an item i want to call

play07:10

save item

play07:11

if we move an item if we add an item if

play07:13

we update an item

play07:14

i always want to call save items so i

play07:17

could

play07:17

go and add this to the end of all these

play07:20

functions and that would work

play07:23

but i'm going to undo this and what's

play07:26

what we're really doing here is anytime

play07:28

we change this

play07:29

item's array so we're changing it in all

play07:32

of these functions

play07:33

anytime we change this items array we

play07:35

want to call save items

play07:37

so an even more efficient way than

play07:39

adding it into each of these functions

play07:41

we're going to scroll up to the items

play07:44

array here

play07:45

and we're going to add a computed

play07:46

property so at the end of this i'm just

play07:48

going to open the brackets

play07:50

and then i'm going to use a swift

play07:52

function called did

play07:53

set and open the brackets

play07:56

and basically this did set gets called

play07:59

any time

play08:00

we set this item's right so anytime we

play08:02

change

play08:03

this items array this function will get

play08:06

called

play08:07

and i'm just going to call in here save

play08:09

items

play08:10

it's that simple so if we do anything

play08:12

that affects this item's array

play08:14

which includes deleting moving adding

play08:18

updating we're always going to call save

play08:20

items which is perfect

play08:23

and the last thing we need to do here is

play08:26

get items

play08:27

because instead of always getting these

play08:30

three fake items

play08:31

we actually want to take the items that

play08:33

are saved in user default and then

play08:35

append

play08:36

those items so i'm going to highlight

play08:39

all these lines

play08:40

press the command and backslash to

play08:42

comment them out because we don't need

play08:43

them anymore

play08:44

and let's try to get the items from our

play08:47

user defaults

play08:49

so first we need to get the data from

play08:51

user defaults we'll say let data

play08:54

equals userdefaults.standard.data4key

play08:58

and we already have our key here because

play09:00

we've created a constant

play09:02

so add in items key

play09:06

and if we hold the option button and

play09:08

click on data

play09:09

you'll see that it is optional because

play09:11

there is a chance that there is nothing

play09:13

saved at this

play09:14

key so what we need to do is safely

play09:17

unwrap this

play09:19

so instead of just let data let's say

play09:22

guard

play09:23

let data equals this else open brackets

play09:26

and return

play09:30

so it's going to try to get the data

play09:32

from this

play09:34

key here and if it's true it's going to

play09:36

run the rest of this code

play09:38

if it's false it's going to return out

play09:40

of this function and that's because we

play09:42

need this data to move forward

play09:46

and then next we need to convert this

play09:47

data from

play09:49

a data json blob into our array of

play09:52

item models so what we're going to do is

play09:54

basically the opposite of what we did

play09:56

down here with our

play09:57

if-let statement here we took the json

play10:00

encoder and

play10:01

encoded the items and now we're going to

play10:03

decode with a json decoder

play10:05

from the data back to items

play10:10

so we'll say let saved items

play10:14

equal try the question mark

play10:19

json decoder open close parenthesis dot

play10:22

decode from and we need to now tell it

play10:26

what kind

play10:26

of data type we want to decode it to

play10:30

so to this decodable.protocol is and we

play10:33

made our item model

play10:34

conform to codeable before so we can use

play10:37

item models here

play10:38

but we know this saved items is an array

play10:40

of item models so we'll do array of

play10:42

item model and we want to be

play10:46

the type of this so not an actual array

play10:49

so we'll do

play10:49

dot self

play10:54

and then from data is our data that we

play10:56

just got right here so we'll do

play10:58

data if i hold the option button and

play11:02

click on

play11:02

saved items you'll see that it now gives

play11:05

us an optional array of item

play11:07

model so again this is optional i want

play11:09

to make sure that we do have items

play11:11

so we can add another guard let

play11:14

else return

play11:17

and what's kind of cool about these

play11:19

guard statements is that we can actually

play11:21

combine

play11:22

multiple multiple of them so in this

play11:24

first one we're checking that we have

play11:25

data

play11:26

and then the second one we're checking

play11:27

that we can convert that data

play11:30

into items but we can actually just

play11:32

combine both of these

play11:33

statements so what i'm going to do is

play11:36

press enter before this let and

play11:39

press enter before this else

play11:43

so first we're going to say guard let

play11:45

data equals this

play11:46

comma and then i'm going to copy our

play11:50

let's save items equals this and paste

play11:53

it in here

play11:54

so now this guard statement actually has

play11:56

both of these combined and we can delete

play11:58

the second one

play11:59

so guard let data equals this let's save

play12:02

items equals this

play12:04

and we'll return out of the function if

play12:06

we can't get the data or the items

play12:08

but if we get both we will continue down

play12:10

here

play12:12

and now if we hold the option button and

play12:14

click on

play12:15

saved items it is not optional it is an

play12:18

actual array of item

play12:19

model so we can call self.items

play12:22

equals saved items

play12:25

and now our items array will be updated

play12:27

with that saved data

play12:30

i'm going to delete this because we

play12:33

don't need it anymore you can leave it

play12:34

in your code if you want

play12:36

and let's click play on

play12:40

the simulator and see if it works

play12:47

all right so right now we have nothing

play12:48

in our screen

play12:50

and that's because we're not appending

play12:51

those three fake items anymore

play12:53

so let's start adding some items we're

play12:55

going to add

play12:56

let's do my first item

play13:00

save bounce is back let's add another

play13:04

second item save

play13:07

i'm going to make this first item

play13:09

completed

play13:10

and then i'm going to edit them and make

play13:12

the first item actually

play13:14

second press done and now let's

play13:17

close the app and open it up again

play13:21

and now we can see that our items have

play13:22

actually saved and persisted

play13:25

because every time we edit these items

play13:27

we

play13:28

saved them to user defaults and when we

play13:30

open our app we are fetching the saved

play13:33

data from user defaults

play13:35

and putting it back in our app so i hope

play13:38

this wasn't too confusing you guys don't

play13:39

have to

play13:40

worry too much about if this tried

play13:42

decoder decode it looks confusing

play13:45

all we're doing is converting an array

play13:47

of item model

play13:48

to json data and then back from json

play13:51

data

play13:53

and i wanted to give you guys some

play13:54

practice here with guard

play13:56

let statements as well as if let

play13:59

statements

play14:00

because time we use ifled or garlic

play14:02

they're kind of interchangeable so we

play14:04

could have used a guard

play14:06

statement here and we also could have

play14:08

used an if let statement here

play14:10

but now you guys have practice with both

play14:12

of them and our code is looking

play14:13

so efficient so clean i hope you guys

play14:16

are getting excited we are almost done

play14:19

with this app as always thank you guys

play14:22

for watching

play14:23

i'm nick this is swiffle thinking do not

play14:26

forget to the subscribe button if you're

play14:27

learning something if you're enjoying

play14:29

this course

play14:30

and stay excited and i will see you all

play14:32

in the next video

Rate This

5.0 / 5 (0 votes)

Related Tags
Swift UIUserDefaults数据持久化待办事项教程编码解码CodableJSON应用开发iOS
Do you need a summary in English?