New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-825] NSCoder object causes memory leak if assigned to class param #43437
Comments
Was playing with JIRA (using it for the first time) and by mistake I put the issue to be in progress... |
Here's a version of your example that I updated to Swift 3.0, and extracted out as a single-file standalone executable: //
// ViewController.swift
// Bug
//
// Created by Damian Malarczyk on 26.02.2016.
//
import Foundation
class ViewController: OperationDelegate {
let queue = OperationQueue()
struct DataWrapper {
var data:[Info]?
}
var rawData:[Info]?
var source:DataWrapper?
static let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
var operation:Operation? = Operation(withPath: ViewController.documentsPath + "/data")
func viewDidLoad() {
operation?.delegate = self
queue.addOperation(operation!)
noOperation()
}
func noOperation(){
let runtimeData = NSMutableArray()
//dummy data
for _ in 0...10 {
let info = Info()
for _ in 0...10 {
info.source?.append(Info.BaseInfo(name: "some name"))
}
runtimeData.add(info)
}
// another issue I guess, here I have warning saying that cast from NSMutableArray to [Info] always fails while it doesn't
if let dataArray = (runtimeData as [AnyObject]) as? [Info] {
//@ leak in both cases, DataWrapper doesnt have impact on that
self.source = DataWrapper(data:dataArray)
//self.rawData = dataArray
//let source = DataWrapper(data: dataArray)
}
}
func finishedWithData(_ data: NSArray) {
if let dataArray = data as? [Info] {
let testLeak = true
if testLeak {
//memory leak occurs
self.source = DataWrapper(data: dataArray)
}else {
//no leak
let source = DataWrapper(data: dataArray)
}
}
operation?.delegate = nil
operation = nil
}
}
protocol OperationDelegate {
func finishedWithData(_ data:NSArray)
}
class Operation:Foundation.Operation {
var path:String
var delegate:OperationDelegate?
init(withPath path:String){
self.path = path
}
override func main() {
let runtimeData = NSMutableArray()
//dummy data
for _ in 0...10 {
let info = Info()
for _ in 0...10 {
info.source?.append(Info.BaseInfo(name: "some name"))
}
runtimeData.add(info)
}
// two cases - data is encoded from file and just normal data
let encodeDataToFile = false
if encodeDataToFile {
saveData(runtimeData)
let localData = readData()
if let local = localData {
delegate?.finishedWithData(local)
}
}else {
delegate?.finishedWithData(runtimeData)
}
}
deinit {
print("no reference cycle")
}
func saveData(_ data:NSArray){
let mutableData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: mutableData)
data.encode(with: archiver)
archiver.finishEncoding()
try? mutableData.write(toFile: path, options: .atomic)
}
func readData( )->NSArray? {
let data = NSData.init(contentsOfFile: path)
if let savedData = data {
let unarchiver = NSKeyedUnarchiver(forReadingWith: savedData as Data)
return NSArray(coder: unarchiver)
}
return nil
}
}
class Info:NSCoder {
static let SOURCE_KEY = "source_key"
var source:[BaseInfo]?
override init(){
super.init()
source = [BaseInfo]()
}
required init?(coder aDecoder: NSCoder) {
source = aDecoder.decodeObject(forKey: Info.SOURCE_KEY) as? [BaseInfo]
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encode(source, forKey: Info.SOURCE_KEY)
}
class BaseInfo:NSCoder {
static let NAME_KEY = "name_key"
var name:String
init(name:String){
self.name = name
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encode(name, forKey: BaseInfo.NAME_KEY)
}
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: BaseInfo.NAME_KEY) as! String
}
}
}
autoreleasepool {
ViewController().viewDidLoad()
} It doesn't appear to leak. I get "no reference cycle" as output at the end of the execution. It's not clear from your description what I need to do to reproduce the leak. Are you able to reproduce it in Xcode 8? Would it be possible to attach a complete project? |
Attached complete project and simplified code to reproduce the leak, all the Operation code was kinda boilerplate as the leak occurs regardless of using Operation. Basically if you launch the project as it is, you should encounter memory leak during: self.rawData = runtimeData Xcode's Instruments will report memory leak for ContiguousArrayStorage and BaseInfo. Hope it will be clearer now. Disclaimer: |
I added `NSCodingLeak.zip` file which contains a new highly simplified project. Environment for this new project is: macOS 10.12.1, MacBook Air '13 2014 Xcode 8.2b2 beta. The memory leak when using generic function occurs in case of both, Debug and Release compilation. Edit: updated the project again, apparently it actually can be even simpler |
Sorry for not getting back to this for so long. I gave NSCodingLeak.zip a spin with Xcode 11, and I don't see any leak, so this probably got fixed somewhere along the way. Are you still able to reproduce? |
Fixed indeed, can't reproduce it either 👍 |
Attachment: Download
Environment
OS X El Capitan 10.11.3, MacBook Air '13 2014
Xcode 7.3 beta from 25th February default toolchain as well as latest Swift development toolchain
Xcode 7.2 latest version with default toolchain
In both version error occurs with Debug and Release compilation
Additional Detail from JIRA
md5: 2c93b8bd59ce2e54384f45ea598132cc
Issue Description:
Having object that inherits from NSCoder and assigning it to a parameter of the object results in a memory leak.
While assigning it to a variable in a local context (current function) it doesn't result with a memory leak.
The resource I provide is a bit bigger, first I tried to recreate error from my project that's why I used NSOperation, but it doesn't seem to have any connection, you can see it in the #noOperation function, the leak still occurs.
Also while preparing it I found out that theres a warning that casting from NSMutableArray to Swift Array always fails, while it does not, the cast succeeds.
Hope my code and issue description are clear enough.
The text was updated successfully, but these errors were encountered: