【问题标题】:SwiftyJSON Performance IssuesSwiftyJSON 性能问题
【发布时间】:2015-05-28 22:00:14
【问题描述】:

我在使用 SwiftyJson 从我们的 API 解析 JSON 并填充核心数据时遇到严重的性能问题。

使用 Alamofire 下载数据,效果很好,但使用 SwiftyJson 解析 JSON 速度非常慢。为了查看库是否真的是问题所在,我在解析数据的众多地方之一重写了 json 解析。在下面的代码中,我正在解析大约 400 个旅游景点之一的开放时间。

查看这些屏幕截图中的差异,从 7.7 秒到 185 毫秒:

快捷方式:

    let openDescription:String = json["OpeningHours"]["OpeningHoursGenericExceptions"].string!
    let monOpen:[String]    = json["OpeningHours"]["Monday"]["From"].string!.componentsSeparatedByString(":")
    let monClose:[String]   = json["OpeningHours"]["Monday"]["To"].string!.componentsSeparatedByString(":")
    let tueOpen:[String]    = json["OpeningHours"]["Tuesday"]["From"].string!.componentsSeparatedByString(":")
    let tueClose:[String]   = json["OpeningHours"]["Tuesday"]["To"].string!.componentsSeparatedByString(":")
    let wedOpen:[String]    = json["OpeningHours"]["Wednesday"]["From"].string!.componentsSeparatedByString(":")
    let wedClose:[String]   = json["OpeningHours"]["Wednesday"]["To"].string!.componentsSeparatedByString(":")
    let thuOpen:[String]    = json["OpeningHours"]["Thursday"]["From"].string!.componentsSeparatedByString(":")
    let thuClose:[String]   = json["OpeningHours"]["Thursday"]["To"].string!.componentsSeparatedByString(":")
    let friOpen:[String]    = json["OpeningHours"]["Friday"]["From"].string!.componentsSeparatedByString(":")
    let friClose:[String]   = json["OpeningHours"]["Friday"]["To"].string!.componentsSeparatedByString(":")
    let satOpen:[String]    = json["OpeningHours"]["Saturday"]["From"].string!.componentsSeparatedByString(":")
    let satClose:[String]   = json["OpeningHours"]["Saturday"]["To"].string!.componentsSeparatedByString(":")
    let sunOpen:[String]    = json["OpeningHours"]["Sunday"]["From"].string!.componentsSeparatedByString(":")
    let sunClose:[String]   = json["OpeningHours"]["Sunday"]["To"].string!.componentsSeparatedByString(":")

原生方式:

    var monOpen:[String] = []
    var monClose:[String] = []
    var tueOpen:[String] = []
    var tueClose:[String] = []
    var wedOpen:[String] = []
    var wedClose:[String] = []
    var thuOpen:[String] = []
    var thuClose:[String] = []
    var friOpen:[String] = []
    var friClose:[String] = []
    var satOpen:[String] = []
    var satClose:[String] = []
    var sunOpen:[String] = []
    var sunClose:[String] = []
    var openDescription:String = ""
    
    if let attractionsArray = orgJson as? NSArray{
        if let attraction = attractionsArray[0] as? NSDictionary{
            if let openHours = attraction["OpeningHours"] as? NSDictionary{
                if let day = openHours["Monday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        monOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        monClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Tuesday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        tueOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        tueClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Wednesday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        wedOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        wedClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Thursday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        thuOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        thuClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Friday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        friOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        friClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Saturday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        satOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        satClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Sunday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        sunOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        sunClose = close.componentsSeparatedByString(":")
                    }
                }
                if let desc = openHours["OpeningHoursGenericExceptions"] as? String{
                    openDescription = desc
                }
            }
        }
    }

这只是正在解析的数据的一部分,因此在应用程序中性能非常明显。

我想问题是,我是否错误地使用了 SwiftyJSON 或者这是意料之中的?

【问题讨论】:

    标签: ios json swift swifty-json


    【解决方案1】:

    首先,你的“native way”不等同于“Swifty way”。

    SwiftyJSON 版本有 45 个 subscript 访问,而原生方式只有 23 个 subscript 访问。

    要使“native way”等效,它应该是这样的:

    let openDescription = attraction["OpeningHours"]!["OpeningHoursGenericExceptions"] as String
    let monOpen  = (attraction["OpeningHours"]!["Monday"]!!["From"] as String).componentsSeparatedByString(":")
    let monClose = (attraction["OpeningHours"]!["Monday"]!!["To"] as String).componentsSeparatedByString(":")
    let tueOpen  = (attraction["OpeningHours"]!["Tuesday"]!!["From"] as String).componentsSeparatedByString(":")
    let tueClose = (attraction["OpeningHours"]!["Tuesday"]!!["To"]! as String).componentsSeparatedByString(":")
    // ...
    

    或“Swifty way”应该是这样的:

    let openHours = json[0]["OpeningHours"]
    var day:JSON
    
    day = openHours["Monday"]
    if let open = day["From"].string {
        monOpen = open.componentsSeparatedByString(":")
    }
    if let close = day["To"].string {
        monClose = close.componentsSeparatedByString(":")
    }
    day = openHours["Tuesday"]
    if let open = day["From"].string {
        tueOpen = open.componentsSeparatedByString(":")
    }
    if let close = day["To"].string {
        tueClose = close.componentsSeparatedByString(":")
    }
    // ...
    

    无论如何,是的,SwiftyJSON 很慢。让我们测量一下:

    import Foundation
    import XCTest
    
    let orgJson:AnyObject = [
        [
            "OpeningHours": [
                "OpeningHoursGenericExceptions": "test",
                "Monday":    ["From":"1:2:3","To":"1:2:3"],
                "Tuesday":   ["From":"1:2:3","To":"1:2:3"],
                "Wednesday": ["From":"1:2:3","To":"1:2:3"],
                "Thursday":  ["From":"1:2:3","To":"1:2:3"],
                "Friday":    ["From":"1:2:3","To":"1:2:3"],
                "Saturday":  ["From":"1:2:3","To":"1:2:3"],
                "Sunday":    ["From":"1:2:3","To":"1:2:3"],
            ]
        ]
    ]
    let json = JSON(orgJson)
    
    class JSONTestTests: XCTestCase {
    
        func testNativeSubscript() {
            measureBlock { () -> Void in
    
                for _ in 0 ..< 400 {
                    autoreleasepool {
                        if let attraction = orgJson[0] as? NSDictionary {
                            let openDescription = attraction["OpeningHours"]!["OpeningHoursGenericExceptions"] as String
                            let monOpen  = (attraction["OpeningHours"]!["Monday"]!!["From"] as String).componentsSeparatedByString(":")
                            let monClose = (attraction["OpeningHours"]!["Monday"]!!["To"] as String).componentsSeparatedByString(":")
                            let tueOpen  = (attraction["OpeningHours"]!["Tuesday"]!!["From"] as String).componentsSeparatedByString(":")
                            let tueClose = (attraction["OpeningHours"]!["Tuesday"]!!["To"] as String).componentsSeparatedByString(":")
                            let wedOpen  = (attraction["OpeningHours"]!["Wednesday"]!!["From"] as String).componentsSeparatedByString(":")
                            let wedClose = (attraction["OpeningHours"]!["Wednesday"]!!["To"] as String).componentsSeparatedByString(":")
                            let thuOpen  = (attraction["OpeningHours"]!["Thursday"]!!["From"] as String).componentsSeparatedByString(":")
                            let thuClose = (attraction["OpeningHours"]!["Thursday"]!!["To"] as String).componentsSeparatedByString(":")
                            let friOpen  = (attraction["OpeningHours"]!["Friday"]!!["From"] as String).componentsSeparatedByString(":")
                            let friClose = (attraction["OpeningHours"]!["Friday"]!!["To"] as String).componentsSeparatedByString(":")
                            let satOpen  = (attraction["OpeningHours"]!["Saturday"]!!["From"] as String).componentsSeparatedByString(":")
                            let satClose = (attraction["OpeningHours"]!["Saturday"]!!["To"] as String).componentsSeparatedByString(":")
                            let sunOpen  = (attraction["OpeningHours"]!["Sunday"]!!["From"] as String).componentsSeparatedByString(":")
                            let sunClose = (attraction["OpeningHours"]!["Sunday"]!!["To"] as String).componentsSeparatedByString(":")
                            XCTAssertEqual(monOpen, ["1","2","3"], "")
                            XCTAssertEqual(openDescription, "test")
                        }
                    }
                }
            }
        }
    
        func testJSONSubscript() {
            measureBlock { () -> Void in
                for _ in 0 ..< 400 {
                    autoreleasepool {
                        let attraction = json[0]
                        let openDescription:String = attraction["OpeningHours"]["OpeningHoursGenericExceptions"].string!
                        let monOpen:[String]    = attraction["OpeningHours"]["Monday"]["From"].string!.componentsSeparatedByString(":")
                        let monClose:[String]   = attraction["OpeningHours"]["Monday"]["To"].string!.componentsSeparatedByString(":")
                        let tueOpen:[String]    = attraction["OpeningHours"]["Tuesday"]["From"].string!.componentsSeparatedByString(":")
                        let tueClose:[String]   = attraction["OpeningHours"]["Tuesday"]["To"].string!.componentsSeparatedByString(":")
                        let wedOpen:[String]    = attraction["OpeningHours"]["Wednesday"]["From"].string!.componentsSeparatedByString(":")
                        let wedClose:[String]   = attraction["OpeningHours"]["Wednesday"]["To"].string!.componentsSeparatedByString(":")
                        let thuOpen:[String]    = attraction["OpeningHours"]["Thursday"]["From"].string!.componentsSeparatedByString(":")
                        let thuClose:[String]   = attraction["OpeningHours"]["Thursday"]["To"].string!.componentsSeparatedByString(":")
                        let friOpen:[String]    = attraction["OpeningHours"]["Friday"]["From"].string!.componentsSeparatedByString(":")
                        let friClose:[String]   = attraction["OpeningHours"]["Friday"]["To"].string!.componentsSeparatedByString(":")
                        let satOpen:[String]    = attraction["OpeningHours"]["Saturday"]["From"].string!.componentsSeparatedByString(":")
                        let satClose:[String]   = attraction["OpeningHours"]["Saturday"]["To"].string!.componentsSeparatedByString(":")
                        let sunOpen:[String]    = attraction["OpeningHours"]["Sunday"]["From"].string!.componentsSeparatedByString(":")
                        let sunClose:[String]   = attraction["OpeningHours"]["Sunday"]["To"].string!.componentsSeparatedByString(":")
                        XCTAssertEqual(monOpen, ["1","2","3"], "")
                    }
                }
            }
        }
    
        func testNativeBinding() {
            measureBlock { () -> Void in
                for _ in 0 ..< 400 {
                    autoreleasepool {
                        var monOpen:[String] = []
                        var monClose:[String] = []
                        var tueOpen:[String] = []
                        var tueClose:[String] = []
                        var wedOpen:[String] = []
                        var wedClose:[String] = []
                        var thuOpen:[String] = []
                        var thuClose:[String] = []
                        var friOpen:[String] = []
                        var friClose:[String] = []
                        var satOpen:[String] = []
                        var satClose:[String] = []
                        var sunOpen:[String] = []
                        var sunClose:[String] = []
                        var openDescription:String = ""
    
                        if let attractionsArray = orgJson as? NSArray{
                            if let attraction = attractionsArray[0] as? NSDictionary{
                                if let openHours = attraction["OpeningHours"] as? NSDictionary{
                                    if let day = openHours["Monday"] as? NSDictionary{
                                        if let open = day["From"] as? String{
                                            monOpen = open.componentsSeparatedByString(":")
                                        }
                                        if let close = day["To"] as? String{
                                            monClose = close.componentsSeparatedByString(":")
                                        }
                                    }
                                    if let day = openHours["Tuesday"] as? NSDictionary{
                                        if let open = day["From"] as? String{
                                            tueOpen = open.componentsSeparatedByString(":")
                                        }
                                        if let close = day["To"] as? String{
                                            tueClose = close.componentsSeparatedByString(":")
                                        }
                                    }
                                    if let day = openHours["Wednesday"] as? NSDictionary{
                                        if let open = day["From"] as? String{
                                            wedOpen = open.componentsSeparatedByString(":")
                                        }
                                        if let close = day["To"] as? String{
                                            wedClose = close.componentsSeparatedByString(":")
                                        }
                                    }
                                    if let day = openHours["Thursday"] as? NSDictionary{
                                        if let open = day["From"] as? String{
                                            thuOpen = open.componentsSeparatedByString(":")
                                        }
                                        if let close = day["To"] as? String{
                                            thuClose = close.componentsSeparatedByString(":")
                                        }
                                    }
                                    if let day = openHours["Friday"] as? NSDictionary{
                                        if let open = day["From"] as? String{
                                            friOpen = open.componentsSeparatedByString(":")
                                        }
                                        if let close = day["To"] as? String{
                                            friClose = close.componentsSeparatedByString(":")
                                        }
                                    }
                                    if let day = openHours["Saturday"] as? NSDictionary{
                                        if let open = day["From"] as? String{
                                            satOpen = open.componentsSeparatedByString(":")
                                        }
                                        if let close = day["To"] as? String{
                                            satClose = close.componentsSeparatedByString(":")
                                        }
                                    }
                                    if let day = openHours["Sunday"] as? NSDictionary{
                                        if let open = day["From"] as? String{
                                            sunOpen = open.componentsSeparatedByString(":")
                                        }
                                        if let close = day["To"] as? String{
                                            sunClose = close.componentsSeparatedByString(":")
                                        }
                                    }
                                    if let desc = openHours["OpeningHoursGenericExceptions"] as? String{
                                        openDescription = desc
                                    }
                                }
                            }
                        }
                        XCTAssertEqual(monOpen, ["1","2","3"], "")
                    }
                }
            }
        }
        func testJSONBinding() {
            measureBlock { () -> Void in
                for _ in 0 ..< 400 {
                    autoreleasepool {
                        var monOpen:[String] = []
                        var monClose:[String] = []
                        var tueOpen:[String] = []
                        var tueClose:[String] = []
                        var wedOpen:[String] = []
                        var wedClose:[String] = []
                        var thuOpen:[String] = []
                        var thuClose:[String] = []
                        var friOpen:[String] = []
                        var friClose:[String] = []
                        var satOpen:[String] = []
                        var satClose:[String] = []
                        var sunOpen:[String] = []
                        var sunClose:[String] = []
                        var openDescription:String = ""
    
                        let openHours = json[0]["OpeningHours"]
                        var day:JSON
    
                        day = openHours["Monday"]
                        if let open = day["From"].string {
                            monOpen = open.componentsSeparatedByString(":")
                        }
                        if let close = day["To"].string {
                            monClose
                                = close.componentsSeparatedByString(":")
                        }
                        day = openHours["Tuesday"]
                        if let open = day["From"].string {
                            tueOpen = open.componentsSeparatedByString(":")
                        }
                        if let close = day["To"].string {
                            tueClose = close.componentsSeparatedByString(":")
                        }
                        day = openHours["WednesDay"]
                        if let open = day["From"].string {
                            wedOpen = open.componentsSeparatedByString(":")
                        }
                        if let close = day["To"].string {
                            wedClose = close.componentsSeparatedByString(":")
                        }
                        day = openHours["Thursday"]
                        if let open = day["From"].string {
                            thuOpen = open.componentsSeparatedByString(":")
                        }
                        if let close = day["To"].string {
                            thuClose = close.componentsSeparatedByString(":")
                        }
                        day = openHours["Friday"]
                        if let open = day["From"].string {
                            friOpen = open.componentsSeparatedByString(":")
                        }
                        if let close = day["To"].string {
                            friClose = close.componentsSeparatedByString(":")
                        }
                        day = openHours["Saturday"]
                        if let open = day["From"].string {
                            satOpen = open.componentsSeparatedByString(":")
                        }
                        if let close = day["To"].string {
                            satClose = close.componentsSeparatedByString(":")
                        }
                        day = openHours["Sunday"]
                        if let open = day["From"].string {
                            sunOpen = open.componentsSeparatedByString(":")
                        }
                        if let close = day["To"].string {
                            sunClose = close.componentsSeparatedByString(":")
                        }
                        XCTAssertEqual(monOpen, ["1","2","3"], "")
                    }
                }
            }
        }
    
    }
    

    输出:

    <unknown>:0: Test Case '-[JSONTestTests.JSONTestTests testJSONBinding]' measured [Time, seconds] average: 0.804, relative standard deviation: 5.592%, values: [0.835687, 0.814827, 0.819685, 0.841900, 0.764961, 0.845202, 0.691442, 0.779255, 0.818213, 0.830698], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
    <unknown>:0: Test Case '-[JSONTestTests.JSONTestTests testJSONSubscript]' measured [Time, seconds] average: 4.247, relative standard deviation: 3.496%, values: [4.019640, 4.004123, 4.146146, 4.194535, 4.487171, 4.300971, 4.310613, 4.408405, 4.318354, 4.279362], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
    <unknown>:0: Test Case '-[JSONTestTests.JSONTestTests testNativeBinding]' measured [Time, seconds] average: 0.223, relative standard deviation: 2.773%, values: [0.221099, 0.227395, 0.218860, 0.225989, 0.227128, 0.222370, 0.229956, 0.214535, 0.210818, 0.229868], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
    <unknown>:0: Test Case '-[JSONTestTests.JSONTestTests testNativeSubscript]' measured [Time, seconds] average: 0.362, relative standard deviation: 17.528%, values: [0.346285, 0.316185, 0.333650, 0.339416, 0.330243, 0.354034, 0.378730, 0.269519, 0.486904, 0.467607], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
    
    • 你的 SwiftyJSON:4.247
    • 你的母语:0.223
    • 我的 SwiftyJSON:0.804
    • 我的母语:0.362

    顺便说一句,如果我是你,我会这样做:

    if let hours = orgJson[0]?["OpeningHours"] as? NSDictionary {
        let openDescription = hours["OpeningHoursGenericExceptions"] as? String ?? ""
        let monOpen  = hours["Monday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let monClose = hours["Monday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let tueOpen  = hours["Tuesday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let tueClose = hours["Tuesday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let wedOpen  = hours["Wednesday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let wedClose = hours["Wednesday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let thuOpen  = hours["Thursday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let thuClose = hours["Thursday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let friOpen  = hours["Friday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let friClose = hours["Friday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let satOpen  = hours["Saturday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let satClose = hours["Saturday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let sunOpen  = hours["Sunday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
        let sunClose = hours["Sunday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
    
        // ...
    }
    

    它相当快,安全,没那么复杂。


    为什么 SwiftyJSON 很慢?

    subscript 访问时,SwiftyJSON 会:

    1. 检查密钥是String
    2. 再次下标self[key:sub]
    3. 检查底层对象是NSDictionary
    4. 用提供的键为NSDictionary 下标
    5. 用结果构造JSON对象
    6. return

    也许编译器优化了一些步骤,但“比原生慢”有些不可避免:)

    【讨论】:

    • 感谢您非常详尽的回答!并带有基准!因为我是 Swift 的新手(我想很多人都是 :) 很多编码都是第一次尝试和错误,我按照 SwiftyJSON README 文件中的建议进行了 JSON 解析。看看解析方法的不同示例,我并不后悔从我的项目中退出 swifty,“本机”解析的复杂性并不是真正的问题,并且添加了我不认为 swifty 作为选项的性能差异。
    • 我无意冒犯 Swifty 的创建者,我认为它对很多开发人员都有很大的用处。但是谷歌搜索'swift parse json'几乎只给出使用swifty的结果给我的印象是要走的路。但也许它不适合所有人? :)
    • @Simpa 在 Swift 世界中,Google 中的很多热门歌曲都不可信。给人的印象是 Objective-C 已经死了,Cocoapods 被接受为标准等等……事实上,我认为很多这些表象都是有问题的。但是,SwiftyJSON 使用起来太慢了吗?这是另一个问题,但如果你最初得到这些结果,你可能不会注意到。
    • @DanRosenstark 现在使用 Android,情况更糟。 APIs 改变,但搜索引擎结果没有。根据我的口味,一些简单的任务需要反复试验。我确实注意到我的应用程序中是否存在性能错误,因为我总是在发布之前对它们进行分析,以查看是否写得不好:而且很多时候还有改进的余地。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-13
    • 2022-01-04
    • 1970-01-01
    • 1970-01-01
    • 2010-09-05
    相关资源
    最近更新 更多