1

I have a C char *cArray and it's length, and I need to convert it to NSData I did it with:

var data: NSData? = NSData(bytesNoCopy: cArray, length: Int(length))

And it's working. The problem is that this is causing some memory leak. I don't know why, but I can see it at the allocation instruments that it's malloc 64 bytes and not freeing it when the function finish or when I set it to null.

This code been called a lot, so I need it to be leaks-free. What can I do to prevent the leak?

Edit: this is the code

func on_data_recv_fn(buf: UnsafeMutablePointer<CChar>, length: CInt, user_data: UnsafeMutablePointer<Void>) -> CInt {
    guard buf != nil else {
        NSLog("on_data_recv_fn buf is nil")
        return -1
    }

    //var data: NSData? = NSData(bytesNoCopy: buf, length: Int(length), freeWhenDone: true)
    var data: NSData? = NSData(bytesNoCopy: buf, length: Int(length))
    let succeededWriting = Int(PacketTunnelProvider.sendPackets(data!))
    data = nil
    return CInt(succeededWriting)
}

According to memory instruments, there is a leak here. The sendPackets function does not holding the data so the problem isn't there.

Edit: attached an image from instruments. instruments image

Witterquick
  • 6,048
  • 3
  • 26
  • 50
  • Full method code please. – Darko Feb 29 '16 at 18:36
  • 1
    What happens if you trigger a memory warning? Is the memory then released? – Darko Feb 29 '16 at 19:00
  • 1
    The bytesNoCopy initializer does not allocate any data, it just takes the given data and wraps it into a NSData object. So it's strange that you see a malloc at this point. Are you sure about it? – Darko Feb 29 '16 at 19:06
  • 1
    Where does the buffer come from? You need to ensure that it is deallocated eventually. – CouchDeveloper Feb 29 '16 at 19:51
  • 1
    If the data was allocated with malloc then NSData bytesNoCopy will deallocate also on deallocation of the NSData object. So the OP should see here only a free but no malloc. But the OP just sees a malloc and no free. That's strange. I assume: the Instruments output is misinterpreted. – Darko Mar 01 '16 at 06:32
  • This is the line I get at the instruments: [_NSPlaceholderData initWithBytes:length:copy:deallocator:] and I can see the memory grew up there. Ill check again for some bug at other place – Witterquick Mar 01 '16 at 07:30
  • I've added an image of the output from instruments. – Witterquick Mar 01 '16 at 07:46
  • 1
    This is an excellent question. The details of your code seem to obfuscate the real issue. I also had a memory leak when I tried to mix object files compiled with arc with those that were coded to do their own retain and release calls. I was also creating NSData objects from a malloc'ed buffer I had gotten from a c code. And I convinced myself that the NSData itself was leaking something, even if I converted the file to manual memory management and released the NSData myself. But your solution below did the trick. Wrap the block in an @autoreleasePool{} block and the leak was gone. – WeakPointer Dec 21 '16 at 17:49

2 Answers2

2

Well, It's seems that if I use autoreleasepool everything is OK for some reason.

Witterquick
  • 6,048
  • 3
  • 26
  • 50
1

Memory management of types backed by Objective-C is a vast and interesting topic. See, for example, here:

https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html

You may also find this question useful:

Is it necessary to use autoreleasepool in a Swift program?

Also, I think there is a danger here if the buf passed to on_data_recv_fn was dynamically allocated by some C code, which later tries to free it. Another dangerous possibility: the function is a call-back implemented in Swift and called by C code. In this case the buf might be on the stack.

I haven't played with any of these scenarios, but according to NSData documentation, the bytesNoCopy initializer makes NSData take ownership of the memory and then de-allocate it; it assumes the memory was allocated using malloc(), so any memory that was not malloc'd should not be used to construct an NSData using this initializer. See https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/#//apple_ref/occ/instm/NSData/initWithBytesNoCopy:length:

There are other NSData initializers that make a copy of the buffer and can be safer in those cases.

Community
  • 1
  • 1
Anatoli P
  • 4,791
  • 1
  • 18
  • 22