广告位联系
返回顶部
分享到

iOS数据持久化UserDefaults封装器使用介绍

IOS 来源:互联网 作者:佚名 发布时间:2024-02-10 19:43:39 人浏览
摘要

想象一下,你有一个应用想实现自动登录功能。你用UserDefaults封装了关于UserDefaults的读与写逻辑。你会用UserDefaults封装来保持对自动登录On/Off状态、userName的跟踪。你可能会以下面这种

想象一下,你有一个应用想实现自动登录功能。你用UserDefaults封装了关于UserDefaults的读与写逻辑。你会用UserDefaults封装来保持对自动登录”On/Off“状态、userName的跟踪。你可能会以下面这种方式来封装UserDefaults

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

struct AppData {

    private static let enableAutoLoginKey = "enable_auto_login_key"

    private static let usernameKey = "username_key"

    static var enableAutoLogin: Bool {

        get {

            return UserDefaults.standard.bool(forKey: enableAutoLoginKey)

        }

        set {

            UserDefaults.standard.set(newValue, forKey: enableAutoLoginKey)

        }

    }

    static var username: String {

        get {

            return UserDefaults.standard.string

        }

        set {

            UserDefaults.standard.set(newValueds, forKey: usernameKey)

        }

    }

}

通过Swift5.1对于属性封装器的介绍,我们可以对上面的代码进行精简,如下

1

2

3

4

5

6

struct AppData {

    @Storage(key: "enable_auto_login_key", defaultValue: false)

    static var enableAutoLogin: Bool

    @Storage(key: "username_key", defaultValue: "")

    static var username: String

}

这样就很完美了吗?接着看

什么是属性封装器?

在我们进入详细讨论之前,我们先快速地了解一下什么是属性封装器 基本上来讲,属性封装器是一种通用数据结构,可以拦截属性的读写访问,从而允许在属性的读写期间添加自定义行为。

可以通过关键字@propertyWrapper来声明一个属性封装器。你想要有一个字符串类型的属性,每当这个属性被进行读写操作的时候,控制台就会输出。你可以创建一个名为Printable的属性封装器,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@propertyWrapper

struct Printable {

    private var value: String = ""

    var wrapperValue: String {

        get {

            print("get value:\(value)")

            return value

        }

        set {

            print("set value:\(newValue)")

            value = newValue

        }

    }

}

通过上述代码我们可以看出,属性封装跟其他struct一样。然而,当定义一个属性封装器的时候,必须要有一个wrapppedValue。 wrapppedValue get set代码块就是拦截和执行你想要的操作的地方。在这个例子中,添加了打印状态的代码来输出get和set的值

接下来,我们看看,如何使用Printable属性封装器

1

2

3

4

5

struct Company {

    @Printable static var name: String

}

Company.name = "Adidas"

Company.name

需要注意的是,我们如何使用@符号来声明一个用属性封装器封装的”name“变量。如果你想要在Playground中尝试敲出上述代码的话,你会看到以下输出:

Set Value: Adidas
Get Value: Adidas

什么是UserDefault封装器

在理解了什么是属性封装器以及它是如何工作的之后,我们现在开始准备实现我们的UserDefaults封装器。总结一下,我们的属性封装器需要持续跟踪自动登录的”On/Off“状态以及用户的username。 通过使用我们上述讨论的概念,我们可以很轻松的将Printable属性封装器转化为在读写操作期间进行读写的属性封装器。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

import Foundation

@propertyWrapper

struct Storage {

    private let key: String

    private let defaultValue: String

    init(key: Stirng, defaultValue: String) {

        self.key = key

        self.defaultValue = defaultValue

    }

    var wrappedValue: String {

        get {

            return UserDefaults.standard.string(forKey: key) ?? defaultValue

        }

        set {

            UserDefaults.standard.set(newValue, forKey: key)

        }

    }

}

在这里,我们将我们的属性封装器命名为Storage。有两个属性,一个是key,一个是defaultValue。key将作为UserDefaults读写时的键,而defaultValue则作为UserDefaults无值时候的返回值。

Storage属性封装器准备就绪后,我们就可以开始实现UserDefaults封装器了。直截了当,我们只需要创建一个被Storage属性封装器封装的‘username’变量。这里要注意的是,你可以通过key和defaultValue来初始化Storage。

1

2

3

4

struct AppData {

    @Storage(key: "username_key", defaultValue: "")

    static var username: String

}

一切就绪之后,UserDefaults封装器就可以使用了

1

2

AppData.username = "swift-senpai"

print(AppData.username)

同时,我们来添加enableAutoLogin变量到我们的UserDefaults封装器中

1

2

3

4

5

6

struct AppData {

    @Storage(key: "username_key", defaultValue: "")

    static var username: String

    @Storage(key: "enable_auto_login_key", defaultValue: false)

    static var username: Bool

}

这个时候,会报下面两种错误:

Cannot convert value of type ‘Bool’ to expected argument type ‘String’

Property type 'Bool' does not match that of lthe 'WrappedValue' property of its wrapper type 'Storage'

这是因为我们的封装器目前只支持String类型。想要解决这两个错误,我们需要将我们的属性封装器进行通用化处理

将属性封装器进行通用化处理

我们必须改变属性封装器的wrappedValue的数据类型来进行封装器的通用化处理,将String类型改成泛型T。进而,我们必须使用通用方式从UserDefaults读取来更新wrappedValue get代码块

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@propertyWrapper

struct Storage<T> {

    private let key: String

    private let defaultValue: T

    init(key: String, defaultValue: T) {

        self.key = key

        self.defaultValue = defaultValue

    }

    var wrappedValue: T {

        get {

            // Read value from UserDefaults

            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue

        }

        set {

            // Set value to UserDefaults

            UserDefaults.standard.set(newValue, forKey: key)

        }

    }

}

好,有了通用属性封装器之后,我们的UserDefaults封装器就可以存储Bool类型的数据了

1

2

3

4

5

6

7

8

9

// The UserDefaults wrapper

struct AppData {

    @Storage(key: "username_key", defaultValue: "")

    static var username: String

    @Storage(key: "enable_auto_login_key", defaultValue: false)

    static var enableAutoLogin: Bool

}

AppData.enableAutoLogin = true

print(AppData.enableAutoLogin)  // true

存储自定义对象

上面的操作都是用来基本数据类型的。但是如果我们想要存储自定义对象呢?接下来我们一起看看,如何能让UserDefaults支持自定义对象的存储

这里的内容很简单,我们将会存储一个自定义对象到UserDefaults中,为了达到这个目的,我们必须改造一下Storage属性封装器的类型T,使其遵循Codable协议

然后,在wrappedValue``set代码块中我们将使用JSONEncoder把自定义对象转化为Data,并将其写入UserDefaults中。同时,在wrappedValue``get代码块中,我们将使用JSONDecoder把从UserDefaults中读取的数据转化成对应的数据类型。 如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

@propertyWrapper

struct Storage<T: Codable> {

    private let key: String

    private let defaultValue: T

    init(key: String, defaultValue: T) {

        self.key = key

        self.defaultValue = defaultValue

    }

    var wrappedValue: T {

        get {

            // Read value from UserDefaults

            guard let data = UserDefaults.standard.object(forKey: key) as? Data else {

                // Return defaultValue when no data in UserDefaults

                return defaultValue

            }

            // Convert data to the desire data type

            let value = try? JSONDecoder().decode(T.self, from: data)

            return value ?? defaultValue

        }

        set {

            // Convert newValue to data

            let data = try? JSONEncoder().encode(newValue)

            // Set value to UserDefaults

            UserDefaults.standard.set(data, forKey: key)

        }

    }

}

为了让大家看到如何使用更新后的Storage属性封装器,我们来看一下接下来的例子。 想象一下,你需要存储用户登录成功后服务端返回的用户信息。首先,需要一个持有服务端返回的用户信息的struct。这个struct必须遵循Codable协议,以至于他能被转化为Data存储到UserDefaults中

1

2

3

4

5

struct User: Codable {

    var firstName: String

    var lastName: String

    var lastLogin: Date?

}

接下来,在UserDefaults封装器中声明一个User对象

1

2

3

4

5

6

7

8

9

struct AppData {

    @Storage(key: "username_key", defaultValue: "")

    static var username: String

    @Storage(key: "enable_auto_login_key", defaultValue: false)

    static var enableAutoLogin: Bool

    // Declare a User object

    @Storage(key: "user_key", defaultValue: User(firstName: "", lastName: "", lastLogin: nil))

    static var user: User

}

搞定了,UserDefaults封装器现在可以存储自定义对象了

1

2

3

4

5

6

let johnWick = User(firstName: "John", lastName: "Wick", lastLogin: Date())

// Set custom object to UserDefaults wrapper

AppData.user = johnWick

print(AppData.user.firstName) // John

print(AppData.user.lastName) // Wick

print(AppData.user.lastLogin!) // 2019-10-06 09:40:26 +0000


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • iOS数据持久化UserDefaults封装器使用介绍
    想象一下,你有一个应用想实现自动登录功能。你用UserDefaults封装了关于UserDefaults的读与写逻辑。你会用UserDefaults封装来保持对自动登录O
  • IOS开发Objective-C Runtime使用介绍

    IOS开发Objective-C Runtime使用介绍
    Runtime是使用 C 和汇编实现的运行时代码库,Objective-C 中有很多语言特性都是通过它来实现。了解 Runtime 开发可以帮助我们更灵活的使用 Ob
  • iOS开发删除storyboard步骤介绍

    iOS开发删除storyboard步骤介绍
    删除iOS项目中的storyboard 删除项目中的storyboard, (变成一个纯代码的iOS UIKit项目), 需要几步? 找到storyboard, 删掉它. 直接用ViewController. 删除st
  • Flutter Widgets之标签类控件Chip介绍

    Flutter Widgets之标签类控件Chip介绍
    Flutter 标签类控件大全ChipFlutter内置了多个标签类控件,但本质上它们都是同一个控件,只不过是属性参数不同而已,在学习的过程中可以将
  • iOS Lotusoot模块化工具应用的动态思路
    下文,写的是 Swift 依赖 OC 库,没有命名空间 组件化的要点-约定 个人觉得 例如,URL 路由的注册,就是把约定的信息,传过去。作为服务。
  • iOS浮点类型精度问题的原因与解决办法
    前言 相信不少人(其实我觉得应该是每个人)都遇到过一个问题,那就是当服务端返回的JSON数据中出现了小数时,客户端用CGFloat去解析时
  • iOS开发实现计算器功能的代码

    iOS开发实现计算器功能的代码
    效果图 Masonry 使用数组来自动约束 NSArray *buttonArrayOne = @[_buttonAC, _buttonLeftBracket, _buttonRightBracket, _buttonDivide]; //withFixedSpacing: 每个view中间的间
  • iOS自定义雷达扫描扩散动画的代码

    iOS自定义雷达扫描扩散动画的代码
    自己自定义了 一个雷达扫描/扩散效果的View。 扫描View 效果如下: 扩散View 效果如下: 自定义的代码如下: 1. RadarView.h #import UIKit/UIKit.h t
  • iOS实现雷达扫描效果

    iOS实现雷达扫描效果
    具体内容如下 #import UIKit/UIKit.h @interface LTIndicatiorView : UIView@property(nonatomic,strong)UIColor *color;@property(nonatomic,assign)float repeatCount;@property(nonatom
  • IOS NSTimeInterval使用案例介绍
    一 ios 获取时间间隔 想在程序开始或者进入某个界面 ,到结束程序或退出某个界面,获取到这个持续时间. 获取到这个时间还需要转化一个
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计