调试 NSJSONSerialization 内存泄漏

Debugging NSJSONSerialization memory leaks

我在 Swift 2.0 项目中使用 SwiftyJSON 2.3.0 进行 JSON 解析:

extension NSData {
    func JSONDict() -> Dictionary<String, AnyObject>? {
        do {
            return try NSJSONSerialization.JSONObjectWithData(self, options: NSJSONReadingOptions(rawValue: 0)) as? Dictionary<String, AnyObject>
        } catch let error as NSError {
            DLog("NSData: error converting data to JSON: \n\(self.asString)\nerror: \n\(error)")
            return nil
        }
    }

}

class NetworkOperation {

    lazy var defaultCompletionHandler: NSURLSessionCompletionHandler = { [weak self] (data, response, error) in
        guard let s = self where !s.cancelled else {
            return
        }

        if let httpResponse = response as? NSHTTPURLResponse {
            switch httpResponse.statusCode {
                case 200..<300:
                    s.successResponse(data, response: response, error: error)

                // ...
            }
        } else {
            // ...
        }            
    }

    func successResponse(data: NSData!, response: NSURLResponse!, error: NSError!) {
        DLog("NetworkOperation: '\(name!)' finished")

        if let jsonDict = data.JSONDict() {
            let json = JSON(jsonDict)
            // process JSON
        }
    }
}

一切正常,但我的应用程序似乎每次解析时都会泄漏大量内存 JSON。我尝试使用 Instruments 进行世代分析,结果如下:

似乎 NSJSONSerialization.JSONObjectWithData 是原因。然而,泄漏的内存块是 malloc_zone_malloc/malloc_zone_calloc(即不是 ARC-managed),所以我看不到引用计数。

如何调试这个问题?

此问题的起因是 Swift 2.0 错误(参见 Xcode 7 release notes):

"对具有 as 模式的多个类型使用 switch 可能会导致内存泄漏。例如,避免这种 switch 语句:

switch x {
  case let a as A: ...
  case let b as B: ...
  case let c as C: ...
  default: ...
}

重写代码以使用 if let a = x as?一个语句而不是开关。此模式执行类型检查以避免内存泄漏。 (22587077)

顺便说一下,这正是 SwiftyJSON 在内部使用的 switch 语句类型。有一个未解决的问题:https://github.com/SwiftyJSON/SwiftyJSON/issues/323

此错误已在 Swift 2.1 中修复(根据@zaph)。目前,解决方案是将 SwiftyJSON.swift 中的以下代码替换为:

switch newValue {
    case let number as NSNumber:
        if number.isBool {
            _type = .Bool
        } else {
            _type = .Number
        }
        self.rawNumber = number
    case  let string as String:
        _type = .String
        self.rawString = string
    case  _ as NSNull:
        _type = .Null
    case let array as [AnyObject]:
        _type = .Array
        self.rawArray = array
    case let dictionary as [String : AnyObject]:
        _type = .Dictionary
        self.rawDictionary = dictionary
    default:
        _type = .Unknown
        _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"])
}

与:

if let number = newValue as? NSNumber {
    if number.isBool {
        _type = .Bool
    } else {
        _type = .Number
    }
    self.rawNumber = number
} else if let string = newValue as? String {
    _type = .String
    self.rawString = string
} else if let _ = newValue as? NSNull {
    _type = .Null
} else if let array = newValue as? [AnyObject] {
    _type = .Array
    self.rawArray = array
} else if let dictionary = newValue as? [String : AnyObject] {
    _type = .Dictionary
    self.rawDictionary = dictionary
} else {
    _type = .Unknown
    _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"])
}

(归功于 tehong