swift语言Codable 用法及原理详解

目录
  • Codable
    • Codable 的用法
  • JSON 和 模型的相互转换
    • 解码(JSON Data -> Model):
    • 编码(Model -> JSON Data):
  • Codable 支持的数据类型
    • 基础数据类型
    • Date
    • 嵌套对象
    • 枚举
  • 自定义 CodingKeys
  • Codable 的原理
    • Decodable 协议
    • Container
  • 核心原理分析(Container <--> JSON)
    • JSONDecoder 的解码过程
  • 编译器帮我们做了什么?
  • 默认值问题
  • 属性包装器 @propertyWrapper
    • 枚举
    • 数组
    • 对象
    • 属性包装器的使用

Codable

Codable 本身就是个类型别名

typealias Codable = Decodable & Encodable

代表一个同时符合 Decodable 和 Encodable 协议的类型,即可解码且可编码的类型。

Codable 也可以代表苹果为 Swift 开发的一套编解码系统,从 Swift 4 开始引入,包含了 Encoder 和 Decoder 协议和他们的两个实现 JSONEncoderJSONDecoderPropertyListEncoderPropertyListDecoder。其中 Codable 及其相关协议放在了标准库中,而具体的 Encoder、Decoder 类放在了 Foundation 框架中。

Codable 的用法

Codable 是用来做系统自身数据结构和外部公共数据结构做转换的。系统内部数据结构可以是基础类型、结构体、枚举、类等,外部公共数据结构可以是 JSON、XML 等。

JSON 和 模型的相互转换

用 Objective-C 做 JSON 和模型转换时,一般要使用一些第三方库,这些第三方库基本上都是利用了 Objective-C Runtime 的强大特性来实现 JSON 和模型互转的。

但是 Swift 是一门静态语言,本身是没有像 Objective-C 那样的动态 Runtime 的。虽然在 Swift 中也可以通过继承 NSObject 的方式,来使用基于 OC Runtime 的 JSON 模型互转方案。但是这样就很不 Swift,也放弃了 Swift 作为一门静态语言的高性能,等于说自己降低了整个项目的运行性能,这是无法忍受的。

好在苹果提供了 JSONEncoderJSONDecoder 这两个结构体来方便得在 JSON 数据和自定义模型之间互相转换。苹果可以利用一些系统私有的机制来实现转换,而不需要通过 OC Runtime

只要让自己的数据类型符合 Codable 协议,就可以用系统提供的编解码器进行编解码。

struct User: Codable {
    var name: String
    var age: Int
}

具体编解码代码如下:

解码(JSON Data -> Model):

let json = """
    {
        "name": "zhangsan",
        "age": 25
    }
    """.data(using: .utf8)!
let user = JSONDecoder().decode(User.self, from: json)

编码(Model -> JSON Data):

let data = JSONEncoder().encode(user)

Codable 支持的数据类型

基础数据类型

在 Swift 标准库的声明文件中可以看到,基础类型都通过 extension 实现了 Codable 协议。

对于基础类型的属性,JSONEncoder 和 JSONDecoder 都可以正确的处理。

Date

JSONEncoder 提供了 dateEncodingStrategy 属性来指定日期编码策略。 同样 JSONDecoder 提供了 dateDecodingStrategy 属性。

就拿 dateDecodingStrategy 为例,它是一个枚举类型。枚举类型有以下几个 case:

case 名 作用
case deferredToDate 默认的 case
case iso8601 按照日期的 ios8601 标准来解码日期
case formatted(DateFormatter) 自定义日期解码策略,需要提供一个 DateFormatter 对象
case custom((_ decoder: Decoder) throws -> Date) 自定义日期解码策略,需要提供一个 Decoder -> Date 的闭包

通常使用比较多的就是 .iso8601 了,因为后端返回日期通常都是已 ios8601 格式返回的。只要 JSON 中的日期是 ios8601 规范的字符串,只要设置一行代码就能让 JSONDecoder 完成日期的解码。

struct User: Codable {
    var name: String
    var age: Int
    var birthday: Date
}
let json = """
    {
        "name": "zhangsan",
        "age": 25,
        "birthday": "2022-09-12T10:25:41+00:00"
    }
    """.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let user = decoder.decode(User.self, from: json)
// user.birthday 正确解码为 Date 类型

嵌套对象

在自定义模型中嵌套对象的时候,只要这个嵌套对象也符合 Codable 协议,那整个对象就可以正常使用 JSONEncoderJSONDecoder 编解码。

struct UserInfo: Codable {
    var name: String
    var age: Int
}
struct User: Codable {
    var info: UserInfo
}

枚举

枚举类型必须它的 RawValue 的类型是可解码的,并且 RawValue 的类型和 JSON 字段类型对应,即可正确解码。

自定义 CodingKeys

自定义 CodingKeys 主要是两个目的

  • 当数据类型属性名和 JSON 中字段名不同时,做 key 的映射。
  • 通过在不添加某些字段的 case,来跳过某些字段的编解码过程。
struct User: Codable {
    var name: String
    var age: Int
    var birthday: Date?
    enum CodingKeys: String, CodingKey {
        case name = "userName"
        case age = "userAge"
    }
}

CodingKeys 必须是一个 RawValue 为 String 类型的枚举,并符合 CodingKey 协议。以上代码实现的效果为,为 name 和 age 字段做了 key 映射,让编解码过程中不包含 birthday 字段。

Codable 的原理

了解了 Codable 的用法,下面我们来看一看 Codable 的原理。

Decodable 协议

由于编码和解码的原理差不多只是方向不同,我们仅探索用的更多的解码过程。

如果想让一个对象支持解码应该怎么做呢,当然是符合 Decodable 协议。我们先看看一个对象符合 Decodable 协议需要做哪些事情。

Decodable 协议的定义如下:

public protocol Decodable {
    init(from decoder: Decoder) throws
}

也就是说只要实现一个传入 Decoder 参数的初始化方法,于是我们自己来实现 User。

struct User: Decodable {
    var name: String
    var age: Int
    init(from decoder: Decoder) throws {
    }
}

现在要来看看怎样让 User 的两个属性的值能从 Decoder 这个对象得到。

查看 Decoder 的定义,它是一个协议。 有两个属性:

var codingPath: [CodingKey] { get }
var userInfo: [CodingUserInfoKey : Any] { get }

还有三个方法:

func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey
func unkeyedContainer() throws -> UnkeyedDecodingContainer
func singleValueContainer() throws -> SingleValueDecodingContainer

会发现这三个方法返回的都是 XXXContainer,从字面上理解是个容器,容器里面一定是容纳了某些东西。

Container

再查看这些 Container 的定义,会发现里面都有一系列 decode... 方法,来对各种类型进行 decode。

一共有三种类型的 Container:

Container 类型 作用
SingleValueDecodingContainer 代表容器中只保存了一个值
KeyedDecodingContainer 代表容器中保存的数据是按照键值对的形式保存的
UnkeyedDecodingContainer 代表容器中保存的数据是没有键的,也就是说,保存的数据是一个数组

回到上面 User 的例子,JSON 数据如下:

{
    "user": "zhangsan",
    "age": 25
}

这种数据显然是键值对,因此要用 KeyedDecodingContainer 来取数据。KeyedDecodingContainer 应该是最常用的 Container 了。

struct User: Decodable {
    var name: String
    var age: Int
    init(from decoder: Decoder) throws {
        decoder.container(keyedBy: &lt;#T##CodingKey.Protocol#&gt;)
    }
}

参数需要传一个符合 CodingKey 协议的对象的类型,于是这里必须自己实现 CodingKeys 枚举,并把 CodingKeys.self 传入参数。

struct User: Decodable {
    var name: String
    var age: Int
    enum CodingKeys: String, CodingKey {
        case name
        case age
    }
    init(from decoder: Decoder) throws {
        let container = decoder.container(keyedBy: CodingKeys.self)
    }
}

然后就可以从 container 中取数据出来赋给自身的属性。由于这几个方法都会抛出异常,因此都要加上 try

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    name = try container.decode(String.self, forKey: .name)
    age = try container.decode(Int.self, forKey: .age)
}

同样的,我们也可以实现出编码。这时把 User 实现的协议改成 Codable

struct User: Codable {
    var name: String
    var age: Int
    enum CodingKeys: String, CodingKey {
        case name
        case age
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        age = try container.decode(Int.self, forKey: .age)
    }
    func encode(to encoder: Encoder) throws {
        var encoder = encoder.container(keyedBy: CodingKeys.self)
        try encoder.encode(name, forKey: .name)
        try encoder.encode(age, forKey: .age)
    }
}

编码的过程就是和解码反过来,因为是键值对,从 encoder 中拿到 KeyedEncoderContainer,然后调用 encode 方法把属性的数据编码到 container 中,然后由 JSONEncoder 来处理接下来的事情。

接下来我们好奇的是,Container 中的数据是怎么保存的,Container 中的数据和 JSON 又是怎么互相转换的。

核心原理分析(Container <--> JSON)

JSONDecoder 的解码过程

JSONDecoder().decode(User.self, from: json) 这句开始分析。打开 swift-corelibs-foundation 中 JSONDecoder 的源码。

// 1
var parser = JSONParser(bytes: Array(data))
let json = try parser.parse()
// 2
return try JSONDecoderImpl(userInfo: self.userInfo, from: json, codingPath: [], options: self.options)
    .unwrap(as: T.self) // 3

decode 方法的实现主要是这三行代码。

  • 先把 data 转化为一个类型为 JSONValue 的 json 对象。
  • 然后构造一个 JSONDecoderImpl 对象
  • 调用 JSONDecoderImpl 对象的 unwrap 方法得到要转换成的对象。

查看 JSONValue 的定义,它通过枚举嵌套把 JSON 的类型定义了出来。具体的数据通过关联值携带在了这个枚举类型中。

enum JSONValue: Equatable {
    case string(String)
    case number(String)
    case bool(Bool)
    case null
    case array([JSONValue])
    case object([String: JSONValue])
}

在获取 KeyedDecodingContainer 的时候也就是通过 JSONValue 构建 Container 对象。

// 这里 self.json 是保存在 JSONDecoderImpl 中的 JSONValue 类型
switch self.json {
case .object(let dictionary): // JSONValue 和 .object 这个 case 匹配,取出字典数据
    let container = KeyedContainer<Key>(
        impl: self,
        codingPath: codingPath,
        dictionary: dictionary // 传入字典数据
    )
    return KeyedDecodingContainer(container)

可以看到,KeyedDecodingContainer 只有当 self.json 匹配为字典时才能正确创建。数据在里面以 let dictionary: [String: JSONValue] 形式保存。

再看其他代码可以发现:

SingleValueContainer 就是直接存了一个 let value: JSONValue 在里面。

UnkeyedDecodingContainer 则是存了一个数组 let array: [JSONValue]

因此在 Container 调用 decode 方法获取数据时,就是根据参数 key 和类型从自身保存的数据中获取数据。这个源码很简单,看一下就明白了。

最后一步的 unwrap 方法,通过源码可以看到,最终调用的就是对象自己实现的 init(from decoder: Decoder) 方法

因此可以得出 JSON -> Model 的步骤如下:

  • JSONParser 对传入的二进制 JSON data 进行解析,解析为 JSONValue 对象。
  • 构建 JSONDecoderImpl,将相关的数据保存在里面。
  • 调用 JSONDecoderImpl 的 unwrap 方法,开始调用对象实现的 init(from: decoder: Decoder) 方法
  • 在 ``init(from: decoder: Decoder)` 方法中,首先根据数据类型获取对应的 Container。
  • 调用 Container 的 decodeXXX 方法得到具体的值赋值给属性。

Model -> JSON 的步骤也是差不多的,只是方向反过来,有兴趣可以自己看一下源码。

在 Swift 的 JSON 转模型方法中,通过观察 Github 上的开源库可以发现一共有三种实现方案:

  • Objective-C Runtime 一众本身就是 OC 开发的库基本都用的这个方案,比如 YYModel,这种方案使用起来非常简单,代码非常少,但不符合 Swift。
  • Key 映射 比如 ObjectMapper 就是这种,这种的缺点是每个对象都要写一大堆映射代码,比较麻烦
  • 利用对象底层内存布局 SwiftyJSON 就属于这种,这种方法使用起来一样很方便,但是依赖苹果的私有代码,苹果如果调整了内部实现就会失效。

通过上面分析 Codable 原理发现,Codable 基本上就是 Key 映射的方案,只不过编译器帮我们自动合成了很多代码来让我们使用起来一样可以非常简单。由于编译器不会帮第三方库合成代码,因此 Codable 秒杀了一众基于 key 映射实现的第三方库。

编译器帮我们做了什么?

我们发现,只要让自己的对象符合 Codable 协议,就可以正常用 JSONEncoderJSONDecoder 编解码,并不需要实现协议中定义的方法。

那是因为编译器帮我们生成了。这种编译器合成代码在很多地方都会用到,例如为结构体和枚举自动合成实现 Equatable 和 Hashable 的代码,为枚举合成实现 CaseIterable 的代码等。

上面的 User 例子,编译器为我们合成的代码如下:

struct User: Codable {
    var name: String
    var age: Int
    // 编译器合成
    enum CodingKeys: String, CodingKey {
        case name
        case age
    }
    // 编译器合成
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        age = try container.decode(Int.self, forKey: .age)
    }
    // 编译器合成
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(age, forKey: .age)
    }
}

可以见到,编译器自动合成了 CodingKeys 枚举的定义,并合成了实现 Encodable 和 Decodable 协议的代码。这给开发人员提供了方便。

默认值问题

编译器自动生成的编解码实现有个问题就是不支持默认值。如果需要支持默认值就需要自己来用 decodeIfPresent 来实现:

struct User: Decodable {
    var name: String
    var age: Int
    enum CodingKeys: String, CodingKey {
        case name
        case age
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
        age = try container.decodeIfPresent(Int.self, forKey: .age) ?? 0
    }
}

但是这样每个结构体都要自己实现一次,非常麻烦。其实这个网上已经有很多文章在说了,就是用 @propertyWrapper 属性包装器来解决这个问题。

属性包装器 @propertyWrapper

属性包装器用来给属性和定义属性的结构之间包装一层,用来实现一些通用的 setter 和 getter 逻辑或初始化逻辑等。

例如对于 Int 型,可以如下定义属性包装器。

@propertyWrapper
public struct DefaultInt: Codable {
    public var wrappedValue: Int
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = (try? container.decode(BaseType.self)) ?? 0
    }
    public func encode(to encoder: Encoder) throws {
        try wrappedValue.encode(to: encoder)
    }
}

以上代码实现了 init(from decoder: Decoder) 方法来为属性在解码失败时提供一个默认值 0。实现 encode(to encoder: Encoder) 是为了编码时直接编码内部值而不是编码整个属性包装类型。

其它的很多基础类型都是一样的逻辑,为了避免重复代码,可以用范型来统一实现。

public protocol HasDefaultValue {
    static var defaultValue: Self { get set }
}
@propertyWrapper
public struct DefaultBaseType&lt;BaseType: Codable &amp; HasDefaultValue&gt;: Codable {
    public var wrappedValue: BaseType
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = (try? container.decode(BaseType.self)) ?? BaseType.defaultValue
    }
    public func encode(to encoder: Encoder) throws {
        try wrappedValue.encode(to: encoder)
    }
}

然后可以考虑用类型别名来定义出各个类型的属性包装关键字。因为如果包含 <. 等字符,写起来会比较麻烦。

typealias DefaultInt = DefaultBaseType<Int>
typealias DefaultString = DefaultBaseType<String>

但是有些类型需要特殊实现一下。

枚举

枚举类型可以利用 rawValue 来进行数据和类型相互转换。

@propertyWrapper
public struct DefaultIntEnum<Value: RawRepresentable & HasDefaultEnumValue>: Codable where Value.RawValue == Int {
    private var intValue = Value.defaultValue.rawValue
    public var wrappedValue: Value {
        get { Value(rawValue: intValue)! }
        set { intValue = newValue.rawValue }
    }
    public init() {
    }
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        intValue = (try? container.decode(Int.self)) ?? Value.defaultValue.rawValue
    }
    public func encode(to encoder: Encoder) throws {
        try intValue.encode(to: encoder)
    }
}

数组

由于数组需要通过 UnkeyedDecodingContainer 拿数据,需要单独特殊处理。

@propertyWrapper
public struct DefaultArray<Value: Codable>: Codable {
    public var wrappedValue: [Value]
    public init() {
        wrappedValue = []
    }
    public init(wrappedValue: [Value]) {
        self.wrappedValue = wrappedValue
    }
    public init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        var results = [Value]()
        while !container.isAtEnd {
            let value = try container.decode(Value.self)
            results.append(value)
        }
        wrappedValue = results
    }
    public func encode(to encoder: Encoder) throws {
        try wrappedValue.encode(to: encoder)
    }
}

对象

因为对象的结构都是不一样的,没法给出一个的默认值。因此设计了一个 EmptyInitializable 协议,里面只有一个无参数的初始化方法。

public protocol EmptyInitializable {
    init()
}

需要提供默认值的对象可以实现这个协议。不过这里需要权衡一下,如果对内存空间占用有比较高的要求,用可选值可能是更好的方案,因为一个空对象占用的空间和有数据的对象占用的空间是一样多的。

属性包装器的使用

使用属性包装器封装各个类型后,只要像这样使用就可以了,decode 的时候就如果不存在对应字段数据属性就会初始化为默认值。

struct User {
    @DefaultString var name: String
    @DefaultInt var age: Int
}

我简单封装了一个库,目前我们的新 Swift 项目在使用,完整代码在这里: github.com/liuduoios/C…

参考资料:

《the swift programming language》

《Advanced Swift》

swift-corelibs-foundation 源码

以上就是swift语言Codable 用法及原理详解的详细内容,更多关于swift Codable用法原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • LeetCode 刷题 Swift 两个数组的交集

    目录 题目 方法一:两个集合 思路及解法 代码 复杂度分析 方法二:排序 + 双指针 思路及解法 代码 复杂度分析 题目 给定两个数组 nums1 和 nums2,返回 它们的交集 .输出结果中的每个元素一定是 唯一 的.我们可以 不考虑输出结果的顺序 . 示例 1: 输入: nums1 = [1,2,2,1], nums2 = [2,2] 输出: [2] 示例 2: 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出: [9,4] 解释: [4,9] 也是可

  • SwiftUI 登录界面布局实现示例详解

    目录 引言 页面分析-元素构成 实战编程-背景图片 实战编程-说明文字 实战编程-登录方式 实战编程-辅助文字 本章小结 引言 为了更好地了解和学习SwiftUI,我们快速学习SwiftUI的三种基本布局:HStack水平布局容器.VStack垂直布局容器.ZStack层叠布局容器. 在实际开发过程中,登录页面是移动端产品必不可少的页面,也是很好的练手项目. 接下来,我们将用10分钟来构建一个登录页面布局,以下面UI设计稿为例: 页面分析-元素构成 采用自顶向下的设计思想拆解UI设计稿的元素,可

  • LeetCode 题解 Swift 有效的完全平方数

    目录 题目 方法一:使用内置的库函数 思路及解法 复杂度分析 方法二:暴力 思路及解法 代码 复杂度分析 方法三:二分查找 思路及解法 细节 代码 复杂度分析 题目 给定一个 正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 true,否则返回 false. 进阶:不要 使用任何内置的库函数,如 sqrt. 示例 1: 输入: num = 16 输出: true 示例 2: 输入: num = 14 输出: false 方法一:使用内置的库函数 思路及解法 根据完全平方数的性

  • Swift使用SnapKit模仿Kingfisher第三方扩展优化

    目录 正文 SnapKit扩展方式简要思考 Kingfisher扩展方式简要思考 自行模仿尝试 最后 正文 我们平时用swift写第三方扩展(OC中的分类)时,可能会直接就往扩展里面写方法,简单又方便,然而当我们看一些常用你的三方(例如:Kingfisher.SnapKit)等,都会用一个简单的参数引出(例如:kf.snp),下面来探索一下怎么用的,然后在总结其优缺点 SnapKit扩展方式简要思考 以 SnapKit为例,使用如下,发现引入了 snp var iv = UIImageView(

  • swift语言AutoreleasePool原理及使用场景

    目录 使用场景 NSAutoreleasePool @autoreleasepool __autoreleasing 源码分析 __AtAutoreleasePool结构体 AutoreleasePoolPage POOL_BOUNDARY 多层嵌套 push autoreleaseFast autoreleaseFullPage autoreleaseNoPage add pop popPage releaseUntil autorelease hotPage coldPage 调试 _obj

  • SwiftUI 引导页界面实现示例

    目录 引言 页面分析-元素构成 实战编程-创建项目 实战编程-引导图片 实战编程-引导文字 实战编程-引导按钮 实战编程-轮博滚动 整体效果-预览 本章小结 引言 当用户首次启用App时,客户端应用常常会出现一段过渡的App功能说明页面,帮助用户快速了解并熟悉App的基本功能和亮点. 引导页是用户了解产品的第一个窗口,能给用户留下最初的印象. 一个好的引导页可以很好地传达产品设计理念和产品设计调性,也是企业传达企业文化很好的窗口.当然对于开发者来说,也是必不可少的练手项目. 接下来,我们同样将用

  • swift语言Codable 用法及原理详解

    目录 Codable Codable 的用法 JSON 和 模型的相互转换 解码(JSON Data -> Model): 编码(Model -> JSON Data): Codable 支持的数据类型 基础数据类型 Date 嵌套对象 枚举 自定义 CodingKeys Codable 的原理 Decodable 协议 Container 核心原理分析(Container <--> JSON) JSONDecoder 的解码过程 编译器帮我们做了什么? 默认值问题 属性包装器 @

  • python super用法及原理详解

    这篇文章主要介绍了python super用法及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 概念 super作为python的内建函数.主要作用如下: 允许我们避免使用基类 跟随多重继承来使用 实例 在单个继承的场景下,一般使用super来调用基类来实现: 下面是一个例子: class Mammal(object): def __init__(self, mammalName): print(mammalName, 'is a wa

  • java synchronized的用法及原理详解

    目录 为什么要用synchronized 使用方式 字节码语义 对象锁(monitor) 锁升级过程 为什么要用synchronized 相信大家对于这个问题一定都有自己的答案,这里我还是要啰嗦一下,我们来看下面这段车站售票的代码: /** * 车站开两个窗口同时售票 */ public class TicketDemo { public static void main(String[] args) { TrainStation station = new TrainStation(); //

  • C语言 scanf的工作原理详解

    目录 原理解释 解决办法 总结 原理解释 先来观察一段代码和运行结果: #include <iostream> using namespace std; int main() { int a; char c; scanf("%d", &a); printf("a = %d", a); scanf("%c", &c); printf("c = %c", c); } 该代码明明有两个 scanf ,但在

  • golang 一次性定时器Timer用法及实现原理详解

    目录 前言 Timer timer结构体 创建定时器 停止定时器 重置定时器 实现原理 数据结构 runtimeTimer 创建Timer 停止Timer 重置Timer 前言 定时器在Go语言应用中使用非常广泛,Go语言的标准库里提供两种类型的计时器,一种是一次性的定时器Timer,另外一种是周期性的定时器Ticker.本文主要来看一下Timer的用法和实现原理,需要的朋友可以参考以下内容,希望对大家有帮助. Timer Timer是一种单一事件的定时器,即经过指定的时间后触发一个事件,因为T

  • Go Ticker 周期性定时器用法及实现原理详解

    目录 前言 Ticker 应用示例 创建定时器 停止定时器 实现原理 数据结构 runtimeTimer 创建Ticker 停止Ticker Ticker 与 Timer 区别 小结 前言 定时器在Go语言应用中使用非常广泛,Go语言的标准库里提供两种类型的计时器,一种是一次性的定时器Timer,另外一种是周期性的定时器Ticker.本文主要来看一下Ticker的用法和实现原理,需要的朋友可以参考以下内容,希望对大家有帮助. Ticker Ticker是周期性定时器,即周期性的触发一个事件,它会

  • Python模块future用法原理详解

    这篇文章主要介绍了Python模块future用法原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 计算机的知识太多了,很多东西就是一个使用过程中详细积累的过程.最近遇到了一个很久关于future的问题,踩了坑,这里就做个笔记,免得后续再犯类似错误. future的作用:把下一个新版本的特性导入到当前版本,于是我们就可以在当前版本中测试一些新版本的特性.说的通俗一点,就是你不用更新python的版本,直接加这个模块,就可以使用python

  • Java JDK动态代理(AOP)用法及实现原理详解

    Java-JDK动态代理(AOP)使用及实现原理分析 第一章:代理的介绍 介绍:我们需要掌握的程度 动态代理(理解) 基于反射机制 掌握的程度: 1.什么是动态代理? 2.动态代理能够做什么? 后面我们在用Spirng和Mybatis的时候,要理解怎么使用的. 1.什么是代理? 代理,在我们日常生活之中就有体现,代购,中介,换ip,商家等等. 比如有一家美国的大学,可以对全世界招生.留学中介(代理 ) 留学中介(代理):帮助这家美国的学校招生,中介是学校的代理中介是代替学校完成招生功能 代理特点

  • C语言 structural body结构体详解用法

    目录 结构体 结构体类型的声明 举个现实例子 程序实例 结构体成员的类型: 结构体变量的定义和初始化 程序一 结构体嵌套情况下,初始化和定义 结构体成员的访问 结构体传参 程序一: 程序二 结构体 结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量 结构体类型的声明 创建 结构体类型 没有占 内存空间,因为还 没有 创建变量 举个现实例子                 盖房子 图纸 --------------------> 房子 结构体类型        结构体变量

  • C语言 链式二叉树结构详解原理

    目录 前言 二叉树节点声明 二叉树的遍历 构建二叉树 1.前序遍历 2.中序遍历 3.后序遍历 二叉树节点的个数 二叉树叶子节点的个数 二叉树第K层节点个数 二叉树的高度/深度 二叉树查找值为x的节点 整体代码 前言 二叉树不同于顺序表,一颗普通的二叉树是没有增删改查的意义.普通的二叉树用来存储数据是不方便的.但是二叉树的一些基本实现结构,例如前序遍历,中序遍历...等等都是对我们学习更深层次的二叉树打下夯实的基础. 二叉树节点声明 typedef char BTDataType; typede

随机推荐