将结构属性转换为字典的更有效方法

时间:2017-07-02 15:23:04

标签: swift dictionary reflection

我一直在寻找从结构创建字典的最有效方法。经过小规模的研究,我找到了通过初始化所有属性来转换它的简单方法:

func toDictionary() -> [String : AnyObject] {
    let dictionary: [String: AnyObject] = ["firstProperty" : sth, "secondProperty" : "sth2"]
    return dictionary
}

但是等等..我每次都要在我的结构中进行初始化吗?他们可能有一堆财产。它让我思考。如果我能以某种方式循环播放属性,我该怎么办?是的,可以使用Mirror反射。经过一段时间的尝试我已经得到它 - 而不是上面的功能,我已经编写了实现一个功能的协议。

protocol Mirrorable {
    func toDictionary() -> [String : AnyObject]
}

然后在我的结构中我可以简单地使用:

extension MyStructure/MyClass : Mirrorable {

    func toDictionary() -> [String : AnyObject] {
        let reflection = Mirror(reflecting: self).children
        var dictionary = [String : AnyObject]()
        for (label, value) in reflection {
            dictionary[label!] = value as AnyObject
        }
        return dictionary
    }
}

好奇心不让我停止思考它。哪种方式会更有效率?

1 个答案:

答案 0 :(得分:5)

TLDR

直接转换速度更快,但由于大多数情况下您不必将1.000.000+结构转换为字典,因此我使用协议扩展。

长答案

我创建了一个Mac cmd行应用来测试时间,正如预期的那样,直接转换更快。 这很明显,因为编译器可以对其进行优化,因为代码本身很简单。使用反射时,你会创建一堆额外的结构和变量,也会增加一些开销。

即使直接转换更快,我认为在大多数情况下使用协议扩展方法都很好,因为它很容易在您的代码库中采用。你必须记住的一件事是你的代码使用反射不考虑嵌套对象,这会使方法更慢,如果你使用递归会产生更大的开销。

以下是将1.000.000结构编码为字典的3次执行的结果:

构建设置

  • Apple LLVM 8.1 - 代码生成

    • 优化级别:-Ofast
  • Swift编译器 - 代码生成:

    • 优化级别:-O -whole-module-optimization
  

使用S1(直接转换)执行1000000次的时间:   0.569061994552612

     

使用S2(结构扩展+协议)执行1000000次的时间:   7.68360501527786

     

使用S3(协议扩展)执行1000000次的时间:   7.71803396940231

     

使用S1(直接转换)执行1000000次的时间:   0.500779032707214

     

使用S2(结构扩展+协议)执行1000000次的时间:   7.58478999137878

     

使用S3(协议扩展)执行1000000次的时间:   7.73368299007416

     

使用S1(直接转换)执行1000000次的时间:   0.492152035236359

     

使用S2(结构扩展+协议)执行1000000次的时间:   7.81585901975632

     

使用S3(协议扩展)执行1000000次的时间:   7.41855001449585

构建设置

  • Apple LLVM 8.1 - 代码生成

    • 优化级别:-Ofast
  • Swift编译器 - 代码生成:

    • 优化级别:-Onone
  

使用S1(直接转换)执行1000000次的时间:   2.92627400159836

     

使用S2(结构扩展+协议)执行1000000次的时间:   9.25952398777008

     

使用S3(协议扩展)执行1000000次的时间:   9.19355899095535

     

使用S1(直接转换)执行1000000次的时间:   2.9830749630928

     

使用S2(结构扩展+协议)执行1000000次的时间:   9.06750500202179

     

使用S3(协议扩展)执行1000000次的时间:   8.77240401506424

     

使用S1(直接转换)执行1000000次的时间:   2.81389397382736

     

使用S2(结构扩展+协议)执行1000000次的时间:   8.84287703037262

     

使用S3(协议扩展)执行1000000次的时间:   9.08754301071167

构建设置

  • Apple LLVM 8.1 - 代码生成

    • 优化级别:-O0
  • Swift编译器 - 代码生成:

    • 优化级别:-O -whole-module-optimization
  

使用S1(直接转换)执行1000000次的时间:   0.533200979232788

     

使用S2(结构扩展+协议)执行1000000次的时间:   8.15365797281265

     

使用S3(协议扩展)执行1000000次的时间:   7.80043601989746

     

使用S1(直接转换)执行1000000次的时间:   0.509769976139069

     

使用S2(结构扩展+协议)执行1000000次的时间:   7.76911997795105

     

使用S3(协议扩展)执行1000000次的时间:   8.00845402479172

     

使用S1(直接转换)执行1000000次的时间:   0.532546997070312

     

使用S2(结构扩展+协议)执行1000000次的时间:   7.99552202224731

     

使用S3(协议扩展)执行1000000次的时间:   7.86273497343063

构建设置

  • Apple LLVM 8.1 - 代码生成

    • 优化级别:-O0
  • Swift编译器 - 代码生成:

    • 优化级别:-Onone
  

使用S1(直接转换)执行1000000次的时间:   2.90398299694061

     

使用S2(结构扩展+协议)执行1000000次的时间:   9.62662398815155

     

使用S3(协议扩展)执行1000000次的时间:   9.55038601160049

     

使用S1(直接转换)执行1000000次的时间:   2.98312002420425

     

使用S2(结构扩展+协议)执行1000000次的时间:   9.62088203430176

     

使用S3(协议扩展)执行1000000次的时间:   8.82720899581909

     

使用S1(直接转换)执行1000000次的时间:   2.77569997310638

     

使用S2(结构扩展+协议)执行1000000次的时间:   8.83749902248383

     

使用S3(协议扩展)执行1000000次的时间:   8.76373296976089

代码:

import Foundation

// Direct conversion to dictionary
struct S1 {
    var a: Int
    var b: Int
    var c: Int
    func toDictionary() -> [String : AnyObject] {
        let dictionary: [String: AnyObject] = [
            "a":a as AnyObject,
            "b":b as AnyObject,
            "c":c as AnyObject
        ]
        return dictionary
    }
}

// Conversion using struct extension + protocol
protocol Mirrorable1 {
    func toDictionary() -> [String : AnyObject]
}

struct S2 {
    var a: Int
    var b: Int
    var c: Int
}

extension S2: Mirrorable1 {
    func toDictionary() -> [String : AnyObject] {
        let reflection = Mirror(reflecting: self).children
        var dictionary = [String : AnyObject]()
        for (label, value) in reflection {
            dictionary[label!] = value as AnyObject
        }
        return dictionary
    }
}

// Conversion using protocol extension
protocol Mirrorable2 {
    func toDictionary() -> [String : AnyObject]
}

extension Mirrorable2 {
    func toDictionary() -> [String : AnyObject] {
        let reflection = Mirror(reflecting: self).children
        var dictionary = [String : AnyObject]()
        for (label, value) in reflection {
            dictionary[label!] = value as AnyObject
        }
        return dictionary
    }
}

struct S3: Mirrorable2 {
    var a: Int
    var b: Int
    var c: Int
}

let listOfExecutions = [1_000_000, 1_000_000, 1_000_000]
for executions in listOfExecutions {
    // S1
    var start = CFAbsoluteTimeGetCurrent()
    for i in 0..<executions {
        S1(a: 0, b: 1, c: 2).toDictionary()
    }
    print("Time for \(executions) executions with S1(direct conversion):")
    print(CFAbsoluteTimeGetCurrent() - start)

    // S2
    start = CFAbsoluteTimeGetCurrent()
    for i in 0..<executions {
        S2(a: 0, b: 1, c: 2).toDictionary()
    }
    print("Time for \(executions) executions with S2(struct extension + protocol):")
    print(CFAbsoluteTimeGetCurrent() - start)

    // S3
    start = CFAbsoluteTimeGetCurrent()
    for i in 0..<executions {
        S3(a: 0, b: 1, c: 2).toDictionary()
    }
    print("Time for \(executions) executions with S3(protocol extension):")
    print(CFAbsoluteTimeGetCurrent() - start)
}