I am using an instance of the this class to pass necessary values to a function for sending Socket Data:
#interface SsdpParameters : NSObject
{
CFDataRef msg;
CFDataRef addr;
CFSocketRef sock;
}
#property CFDataRef msg;
#property CFDataRef addr;
#property CFSocketRef sock;
#end
This is the function that is responsible for sending the socket data:
-(void)SendSsdpResponse:(id)parameters
{
SsdpParameters *params = parameters;
CFDataRef msg = (CFDataRef)params.msg;
CFDataRef addr = (CFDataRef)params.addr;
CFSocketRef sock = (CFSocketRef)params.sock;
CFSocketError err = CFSocketSendData (sock, addr, msg, 0); // Program received signal: "EXC_BAD_ACCESS".
if (err)
{
NSLog(#"Error sending Valid Response");
}
}
This function sets up the socket and calls SendSsdpResponse after a 1 second delay:
- (void) sendValidResponses
{
NSMutableString *message = nil;
CFSocketRef sock = [self newSSDPSendSocket];
if(sock != nil)
{
struct sockaddr_in SSDPaddr;
memset(&SSDPaddr, 0, sizeof(SSDPaddr));
SSDPaddr.sin_family=AF_INET;
SSDPaddr.sin_addr.s_addr=inet_addr(SSDP_ADDRESS);
SSDPaddr.sin_port=htons(SSDP_PORT);
// Loop through list, sending SSDP
for (NSString *aKey in list)
{
message = [[NSMutableString alloc] initWithString:#"NOTIFY * HTTP/1.1\r\n"];
// Append more lines to message.
CFDataRef addr = CFDataCreate(NULL, (const UInt8*)&SSDPaddr, sizeof(SSDPaddr));
CFDataRef msg = CFDataCreate(NULL, (const UInt8*)[message UTF8String], [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
SsdpParameters *parameters = [[SsdpParameters alloc] init];
parameters.msg = msg;
parameters.addr = addr;
parameters.sock = sock;
[self performSelector:#selector(SendSsdpResponse:) withObject:parameters afterDelay:1.0];
CFRelease(addr);
CFRelease(msg);
[message release];
}
CFRelease(sock);
}
}
As you can see by the comment, in SendSsdpResponse, I an getting a "EXC_BAD_ACCESS" error when trying to call CFSendSocketData. I have a suspicion that it's because I am "passing" sock, addr, and msg and there is something the runtime does not like about this. I have run into this problem before with passing variables of the CF data types to other functions. I have yet to find an answer and hope someone here can finally help me understand what's going on behind the scenes.
Thanks!
EXC_BAD_ACCESS errors generally indicate that you're attempting to invoke a method on an instance that has already been deallocated. This is often the case when you fail to properly retain variables, especially when they're being passed between functions.
When you perform the selector with the delay, on this line:
[self performSelector:#selector(SendSsdpResponse:) withObject:parameters afterDelay:1.0];
You're providing the method with the parameters variable, which has the properties as defined in the header you provided. The problem is that right after the performSelector: method, you're releasing the memory referred to addr and msg. Since you're performing this selector with a delay, the memory that addr and msg point to is being deallocated prior to its use.
So, once this memory has been released, it is no longer "good" in the SsdpParameters object. You should (in the setter for these properties, on the SsdpParameters object) copy this memory, so that the original callers can release it safely.
You should investigate this function:
CFDataRef CFDataCreateCopy (
CFAllocatorRef allocator,
CFDataRef theData
);
Then, your setter could look like:
#implementation SsdpParameters
...
- (void)setMsg:(CFDataRef)m {
// You should also release msg if it already exists
msg = CFDataCreateCopy(CFAllocatorGetDefault(), m);
}
...
#end
To troubleshoot, try replacing the call to
[self performSelector:#selector(SendSsdpResponse:) withObject:parameters afterDelay:1.0];
with an immediate call to
[self SendSsdpResponse:parameters];
and see what happens.
If you still get "EXC_BAD_ACCESS", it means your parameters are set up incorrectly somehow. If, on the other hand, you don't get an error, it means that the parameter object is ok initially, but becomes invalid before the delay completes. For example, is the parameters object retaining or copying "addr" and "msg"?
Related
In a third-party lib I use, I am getting the warning
"Block captures an autoreleasing out-parameter"
What is the problem and how can I fix it?
- (BOOL)register:(NSString *)param error:(NSError **)errPtr
{
__block BOOL result = YES;
__block NSError *err = nil;
dispatch_block_t block = ^{ #autoreleasepool {
NSMutableArray *elements = [NSMutableArray array];
/**** Block captures an autoreleasing out-parameter,
which may result in use-after-free bugs ****/
/* on errPtr */
[self registerWithElements:elements error:errPtr];
}};
if (errPtr)
*errPtr = err;
return result;
}
When you have a method with an indirect non-const parameter (T **param) Clang with ARC automatically qualify such a parameter with __autoreleasing (T *__autoreleasing*). This happens because Clang reasonably assumes, that the calling side is not always required to release such an object, so it puts a requirement on the function to assign autoreleasing objects only. Thus this:
- (void)myMethod:(NSObject **)param {
*param = [NSObject new];
}
Turns into this under ARC:
- (void)myMethod:(NSObject *__autoreleasing*)param {
*param = [[NSObject new] autorelease];
}
This in turn imposes special requirements on the arguments for such a method, so in common scenario where you actually just pass some (strongly retained) object to the function:
NSObject *obj;
[self myMethod:&obj];
ARC in fact makes a temporary autoreleasing argument:
NSObject *__strong obj = nil;
NSObject *__autoreleasing tmp = obj;
[self myMethod:&tmp];
obj = [tmp retain];
What is the problem...
If, instead of (indirectly) passing strongly retained pointer, you pass your own indirect pointer, ARC doesn't make any temporary in between:
NSObject *__autoreleasing obj;
NSObject *__autoreleasing *objPtr = &obj;
[self myMethod:objPtr];
It means that the object "returned" by myMethod: doesn't get retained anymore, thus will be destroyed when current autorelease pool is drained. The same is true if you pass a parameter with the same semantic:
- (void)anotherMethod:(NSObject **)param {
[self myMethod:param];
}
Thus if, for any reason, you decide to wrap the invocation of myMethod: with an autorelease block, the code here ends up with a zombie object:
- (void)anotherMethod:(NSObject **)param {
#autoreleasepool {
[self myMethod:param]; // object was created and assigned to a autoreleasing pointer
} // ref. count of the object reached zero. `*param` refers to a released object
}
The same can potentially happen if you wrap the invocation with a block:
- (void)anotherMethod:(NSObject **)param {
void(^block)(void) = ^{
// "Block captures an autoreleasing out-parameter, which may result in use-after-free bugs" warning appears
[self myMethod:param];
};
block();
}
For this specific implementation no problem will happen, and you could just silence the error by explicitly giving the indirect pointer __autoreleasing qualifier (by which you inform Clang that you are well aware of possible consequences):
- (void)anotherMethod:(NSObject *__autoreleasing*)param {
void(^block)(void) = ^{
[self myMethod:param];
};
block();
}
But now you has to be very careful, because block is a first-party object, which can be retained and called from anywhere and there are countless scenarios where additional autorelease pool is spawned. E.g. this code will case the same zombie-object error:
- (void)anotherMethod:(NSObject *__autoreleasing*)param {
void(^block)(void) = ^{
[self myMethod:param];
};
... some code here ...
#autoreleasepool {
block();
}
}
The same if the autorelease pool is right in the block body:
- (void)anotherMethod:(NSObject **)param {
void(^block)(void) = ^{
#autoreleasepool {
[self myMethod:param];
}
};
block();
}
Having that said, Clangs doesn't warn about the error (it's actually obvious in your case, because you wrap the body of your block with an #autoreleasepool block), it just wants you to double check that you are aware of possible problems (as you can see, it's still possible to implement things like that, but you will have hard time to track all the errors if they appear).
how can I fix it?
This depends on your definition of "fix". You either can remove the autorelease pool block from the body of your block and qualify __autoreleasing parameter explicitly (provided it's merely called in the same thread somewhere in the method):
- (BOOL)register:(NSString *)param error:(NSError *__autoreleasing*)errPtr {
....
dispatch_block_t block = ^{
....
[self registerWithElements:elements error:errPtr];
};
block();
....
}
Or you can introduce another "local" variable to capture and pass it inside a block:
- (BOOL)register:(NSString *)param error:(NSError **)errPtr {
....
__block NSError *err;
dispatch_block_t block = ^{
#autoreleasepool {
....
[self registerWithElements:elements error:&err];
}
};
block();
*errPtr = err;
....
}
This again implies that the block is called synchronously in the method, but not necessarily within the same autorelease pool block. If you want to store the block for later use, or call it asynchronously, then you will need another NSError variable with prolonged lifetime to capture inside the block.
I am facing a weird OC Exception, it looks that I am sending a message to a released address, but when I
try to check if it is NULL, it still crashs.
try to debug or add #try #catch, it catches nothing but runs for days, no crash at all.
exception
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000010
VM Region Info: 0x10 is not in any region. Bytes before following region: 4304617456
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
UNUSED SPACE AT START
--->
__TEXT 100934000-100a98000 [ 1424K] r-x/r-x SM=COW ...x/APP
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [18302]
Triggered by Thread: 22
code below is not strict ,just shows the logic ( code is running in one serial dispatch queue )
struct st_type {
void *arg;
};
static NSMutableDictionary *dictionary;
// init values
void init ()
{
// there is a static dictionary to retain the object
dictionary = [[NSMutableDictionary alloc]init];
// an object reference saved in dictionary and a struct holds it's pointer
NSObject *obj = [[NSObject alloc]init];
struct st_type *st = malloc(sizeof(struct st_type));
st->arg = (__bridge void *)obj;
dictionary[#"cached"] = obj;
// then the struct * passes to every where, I think it's safe because the object always in the dictionary.
...
}
// the only place to release the nsobject, so I think there is no chance to EXC_BAD_ACCESS, but ...
void release_object(struct st_type *st, NSObject obj){
[dictionary removeObjectForKey:#"cached"];
st->arg = NULL;
}
// some where to use the struct
void use_struct(struct st_type *st){
if(st->arg == NULL){
return;
}
// if I add try catch, it never crashs
// #try {
NSObject *obj = (__bridge NSObject*)st->arg;
[obj description]; // crash here.
// } #catch (NSException *exception) { print some log but it never reaches here... }
}
Could anyone help me what I can do next to fix this error?
If I understand correctly you want to store a reference to an Objective-C object as a void *. This is a similar use case as the old-style context or userInfo pointers that were passed as callbacks to sheets, for example.
See ARC: __bridge versus __bridge_retained using contextInfo test case for an example. I also assume you're using ARC (please read Casting and Object Lifetime Semantics).
Assuming the lifetime of the struct is longer than that of the Objective-C object, and that you explicitly set and release the object in the struct, you shouldn't need a dictionary for memory management.
Once the struct and object are allocated (using malloc and [[XX alloc] init] respectively), you can transfer ownership of the object out of ARC and store it in st->arg using a (__bridge_retained void *) cast.
To use the object, cast using (__bridge NSObject *). This will not change ownership.
When you are ready to release the object, pass ownership back to ARC by casting using (__bridge_transfer NSObject *). Then you can set the void * pointer to NULL.
So overall something like:
struct st_type {
void *arg;
};
void init()
{
NSObject *obj = [[NSObject alloc] init];
struct st_type *st = malloc(sizeof(struct st_type));
// transfer ownership out of ARC
st->arg = (__bridge_retained void *)obj;
}
void use_struct(struct st_type *st){
// no change in ownership
NSObject *obj = (__bridge NSObject *)st->arg;
// use `obj`
}
void release_object(struct st_type *st){
// transfer ownership back to ARC
NSObject *obj = (__bridge_transfer NSObject *)st->arg;
st->arg = NULL;
}
I am trying to write a unit test for a method which itself creates and passes a block to another object (so it can be called later). This method is using socket.io-objc to send a request to a server. It passes a callback block to socketio's sendEvent:withData:andAcknowledge that will be invoked when it receives a response from the server). Here is the method i want to test:
typedef void(^MyResponseCallback)(NSDictionary * response);
...
-(void) sendCommand:(NSDictionary*)dict withCallback:(MyResponseCallback)callback andTimeoutAfter:(NSTimeInterval)delay
{
__block BOOL didTimeout = FALSE;
void (^timeoutBlock)() = nil;
// create the block to invoke if request times out
if (delay > 0)
{
timeoutBlock = ^
{
// set the flag so if we do get a response we can suppress it
didTimeout = TRUE;
// invoke original callback with no response argument (to indicate timeout)
callback(nil);
};
}
// create a callback/ack to be invoked when we get a response
SocketIOCallback cb = ^(id argsData)
{
// if the callback was invoked after the UI timed out, ignore the response. otherwise, invoke
// original callback
if (!didTimeout)
{
if (timeoutBlock != nil)
{
// cancel the timeout timer
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(onRequestTimeout:) object:timeoutBlock];
}
// invoke original callback
NSDictionary * responseDict = argsData;
callback(responseDict);
}
};
// send the event to the server
[_socketIO sendEvent:#"message" withData:dict andAcknowledge:cb];
if (timeoutBlock != nil)
{
// if a timeout value was specified, set up timeout now
[self performSelector:#selector(onRequestTimeout:) withObject:timeoutBlock afterDelay:delay];
}
}
-(void) onRequestTimeout:(id)arg
{
if (nil != arg)
{
// arg is a block, execute it
void (^callback)() = (void (^)())arg;
callback();
}
}
This all appears to be working fine when running for real. My problem comes in when I run my unit test (which uses SenTestingKit and OCMock):
-(void)testSendRequestWithNoTimeout
{
NSDictionary * cmd = [[NSDictionary alloc] initWithObjectsAndKeys:
#"TheCommand", #"msg", nil];
__block BOOL callbackInvoked = FALSE;
__block SocketIOCallback socketIoCallback = nil;
MyResponseCallback requestCallback = ^(NSDictionary * response)
{
STAssertNotNil(response, #"Response dictionary is invalid");
callbackInvoked = TRUE;
};
// expect controller to emit the message
[[[_mockSocket expect] andDo:^(NSInvocation * invocation) {
SocketIOCallback temp;
[invocation getArgument:&temp atIndex:4];
// THIS ISN'T WORKING AS I'D EXPECT
socketIoCallback = [temp copy];
STAssertNotNil(socketIoCallback, #"No callback was passed to socket.io sendEvent method");
}] sendEvent:#"message" withData:cmd andAcknowledge:OCMOCK_ANY];
// send command to dio
[_ioController sendCommand:cmd withCallback:requestCallback];
// make sure callback not invoked yet
STAssertFalse(callbackInvoked, #"Response callback was invoked before receiving a response");
// fake a response coming back
socketIoCallback([[NSDictionary alloc] initWithObjectsAndKeys:#"msg", #"response", nil]);
// make sure the original callback was invoked as a result
STAssertTrue(callbackInvoked, #"Original requester did not get callback when msg recvd");
}
To simulate a response, I need to capture (and retain) the block that is created by the method i'm testing and is passed to sendEvent. By passing an 'andDo' argument to my expectation, I am able to access the block i'm looking for. However, it seems like it is not being retained. So, when sendEvent unwinds and I go to invoke the callback, all the values that should have been captured in the block show up as null. The result is that the test crashes when I invoke socketIoCallback and it goes to access the 'callback' value that was originally captured as part of the block (and is now nil).
I am using ARC and so I expect that "__block SocketIOCallback socketIoCallback" will retain values. I've tried to "-copy" the block into this variable but still it does not seem to retain past the end of sendCommand. What can I do to force this block to retain long enough for me to simulate a response?
Note: I've tried calling [invocation retainArguments] which works but then crashes somewhere in objc_release when cleaning up after the test is complete.
I was finally able to reproduce the problem and I suspect that the error is in this code section:
SocketIOCallback temp;
[invocation getArgument:&temp atIndex:4];
Extracting the block this way does not work correctly. I'm not exactly sure why but it may have to do with some of the magic ARC does in the background. If you change the code to the following it should work as you'd expect:
void *pointer;
[invocation getArgument:&pointer atIndex:4];
SocketIOCallback temp = (__brigde SocketIOCallback)pointer;
#aLevelOfIndirection is correct in that it is due to call to getArgument:atIndex:.
To understand why, remember that SocketIOCallback is an object pointer type (it is a typedef for a block pointer type), which is implicitly __strong in ARC. So &temp is type SocketIOCallback __strong *, i.e. it is a "pointer to strong". When you pass a "pointer to strong" to a function, the contract is that if the function replaces the value pointed to by the pointer (which is __strong), it must 1) release the existing value, and 2) retain the new value.
However, NSInvocation's getArgument:atIndex: does not know anything about the type of the thing pointed to. It takes a void * parameter, and simply copies the desired value binary-wise into the location pointed to by the pointer. So in simple terms, it does a pre-ARC non-retained assignment into temp. It does not retain the value assigned.
However, since temp is a strong reference, ARC will assume it was retained, and release it at the end of its scope. This is an over-release, and thus will crash.
Ideally, the compiler should disallow conversions between "pointer to strong" and void *, so this doesn't accidentally happen.
The solution is to not pass a "pointer to strong" to getArgument:atIndex:. You can pass either a void * as aLevelOfIndirection showed:
void *pointer;
[invocation getArgument:&pointer atIndex:4];
SocketIOCallback temp = (__brigde SocketIOCallback)pointer;
or a "pointer to unretained":
SocketIOCallback __unsafe_unretained pointer;
[invocation getArgument:&pointer atIndex:4];
SocketIOCallback temp = pointer;
and then assign back into a strong reference afterwards. (or in the latter case, you could use the unretained reference directly if you are careful)
I have a problem that seems to be a premature release of an in-use object in an ARC based app. I'm trying to create a folder on an FTP server. The relevant parts of code are below; i'll describe the problem first.
problem with the code is, that the debug output in the
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
method is never called.
Instead, i just get an _EXC_BAD_ACCESS_ error.
While debugging i found out two things:
the error only appears if the following line of code (createDir method) is executed:
[ftpStream open];
if that message isn't sent, the rest of the code doesn't really make sense - but it doesn't crash either...
I tracked the EXC_BAD_ACCESS down with NSZombieEnabled: With zombie objects enabled, the GDB produces the following debugger info:
*** -[FTPUploads respondsToSelector:]: message sent to deallocated instance 0x9166590
The referred address 0x9166590 is the address of my FTPUploads object.
It looks like the streams delegate is deallocated before it can handle messages.
Why does the system deallocate an in-use object? How can i prevent it from being deallocated prematurely?
code:
FTPUploads.h excerpt:
#import <Foundation/Foundation.h>
enum UploadMode {
UploadModeCreateDir,
UploadModeUploadeData
};
#class UploadDatasetVC;
#interface FTPUploads : NSObject<NSStreamDelegate> {
#private
NSString *uploadDir;
NSString *ftpUser;
NSString *ftpPass;
NSString *datasetDir;
NSArray *files;
/* FTP Upload fields */
NSInputStream *fileStream;
NSOutputStream *ftpStream;
// some more fields...
enum UploadMode uploadMode;
UploadDatasetVC *callback;
}
- (id) initWithTimeseriesID: (int) aTimeseriesID
fromDatasetDir: (NSString *) aDir
withFiles: (NSArray *) filesArg
andCallbackObject: (UploadDatasetVC *) aCallback;
- (void) createDir;
#end
FTPUploads.m excerpt
#import "FTPUploads.h"
#import "UploadDatasetVC"
#implementation FTPUploads
- (id) initWithTimeseriesID: (int) aTimeseriesID
fromDatasetDir: (NSString *) aDir
withFiles: (NSArray *) filesArg
andCallbackObject: (UploadDatasetVC *) aCallback {
self = [super init];
if (self) {
uploadDir = [NSString stringWithFormat: #"ftp://aServer.org/%i/", aTimeseriesID];
ftpUser = #"aUser";
ftpPass = #"aPass";
datasetDir = aDir;
files = filesArg;
bufferOffset = 0;
bufferLimit = 0;
index = 0;
callback = aCallback;
}
return self;
}
- (void) createDir {
uploadMode = UploadModeCreateDir;
NSURL *destinationDirURL = [NSURL URLWithString: uploadDir];
CFWriteStreamRef writeStreamRef = CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) destinationDirURL);
assert(writeStreamRef != NULL);
ftpStream = (__bridge_transfer NSOutputStream *) writeStreamRef;
[ftpStream setProperty: ftpUser forKey: (id)kCFStreamPropertyFTPUserName];
[ftpStream setProperty: ftpPass forKey: (id)kCFStreamPropertyFTPPassword];
ftpStream.delegate = self;
[ftpStream scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
// open stream
[ftpStream open];
CFRelease(writeStreamRef);
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
NSLog(#"aStream has an event: %i", eventCode);
switch (eventCode) {
// all cases handled properly
default:
// no event
NSLog(#"default mode; no event");
break;
}
}
EDIT: added creation code that is used in the class UploadDatasetVC:
FTPUploads *uploads = [[FTPUploads alloc] initWithTimeseriesID: timeseries_id
fromDatasetDir: datasetDir
withFiles: files
andCallbackObject: self];
[uploads createDir];
It looks to me like the only reference to your FTPUploads object is the delegate property on the stream. This won't retain your object, so if nothing else has a reference to the object, the object will be dealloced. A.R.C. doesn't try to prevent this scenario.
What you need to do is have the code that allocates the FTPUploads object keep a reference to the object until it completes.
It also wouldn't be a bad idea to set the ftpStream.delegate property to nil in your FTPUploads dealloc method, as this will prevent a crash if the object is dealloced prematurely.
The problem is that your ftpStream object is being deallocated. You create it with CFWriteStreamCreateWithFTPURL(), then release it with CFRelease(). You used a __bridge cast, which basically means "don't do any memory management on this assignment". So ARC didn't retain it when you assigned it to ftpStream. Since your intention was to transfer ownership from CF to ARC, that was the wrong cast to use.
You actually wanted either __bridge_retained or __bridge_transfer. I can never remember which is which, though. Luckily, there's another option—the CFBridgingRetain() and CFBridgingRelease() macros. They resolve down to those same bridging casts, but are named far more clearly.
In this case, you want CF to release it, but bridge it over to ARC. So you want CFBridgingRelease(). That will tell ARC to take ownership of the object, and then do a CFRelease. In short, replace this:
ftpStream = (__bridge NSOutputStream *) writeStreamRef;
with this:
ftpStream = CFBridgingRelease(writeStreamRef);
And then remove the call to CFRelease() a few lines later.
My guess is that you should either wait until the stream is finished to do CFRelease(writeStreamRef), or do a __bridge_transfer to transfer the ownership over to ftpStream before you release your writeStreamRef
I'm using NSInvocation to get some method returns, and unfortunately I seem to have a leak, but can't figure out how to free the void* I'm allocating, after I've returned it from NSInvocation.
In the following implementation I tried to free it with a block that gets performed on the next run loop, but I get a crash due to returnBuffer not being allocated.
Why can't I free returnBuffer in the block, and if it hasn't been allocated why is it getting through returnBuffer!=NULL?
This is a special method that has to do with IMP swizzling so I DON'T know the method return type. Putting it in NSData or something will not work.
NSUInteger length = [[invocation methodSignature] methodReturnLength];
if(length!=0){
void* returnBuffer = (void *)malloc(length);
[invocation getReturnValue:&returnBuffer];
if(returnBuffer!=NULL){
void(^delayedFree)(void) = ^{ free(returnBuffer); };
[[NSOperationQueue mainQueue] addOperationWithBlock:delayedFree];
}
return returnBuffer;
}
return nil;
ANSWER
Got it to work the following way thanks to Josh's -[NSMutableData mutableBytes] trick
NSUInteger length = [[invocation methodSignature] methodReturnLength];
if(length!=0){
NSMutableData * dat = [[NSMutableData alloc] initWithLength:length];
void* returnBuffer = [dat mutableBytes];
[invocation getReturnValue:&returnBuffer];
void(^delayedFree)(void) = ^{ [dat release]; };
[[NSOperationQueue mainQueue] addOperationWithBlock:delayedFree];
return returnBuffer;
}
return nil;
You can get a void * from NSMutableData, just like you can from malloc(), and essentially turn it into an instance variable, so that the lifetime of the allocation is tied to the lifetime of the instance. I got this trick from Mike Ash. I've been doing some NSInvocation monkeying myself lately; this is in a category on NSInvocation (you should use your own prefix for the method, of course):
static char allocations_key;
- (void *) Wool_allocate: (size_t)size {
NSMutableArray * allocations = objc_getAssociatedObject(self,
&allocations_key);
if( !allocations ){
allocations = [NSMutableArray array];
objc_setAssociatedObject(self, &allocations_key,
allocations, OBJC_ASSOCIATION_RETAIN);
}
NSMutableData * dat = [NSMutableData dataWithLength: size];
[allocations addObject:dat];
return [dat mutableBytes];
}
I'm not sure where the code you've posted is located; if you're in your own custom class, you don't need to deal with the associated objects bits -- just make allocations an ivar.
Tying this into your code:
NSUInteger length = [[invocation methodSignature] methodReturnLength];
if(length!=0){
void* returnBuffer = [self Wool_allocate:length];
[invocation getReturnValue:&returnBuffer];
return returnBuffer;
}
return nil;
If you use the associated object route, the allocations array will be deallocated when this instance is. Otherwise, just put [allocations release] into your dealloc.
Also, to answer your question as posed, rather than just solving the problem: free() won't operate on any pointer that you didn't get from malloc(). When the pointer gets used in the Block, I'm pretty sure it gets copied, so you end up with another pointer -- still pointing to the same memory, but one that free() doesn't think it owns. Thus, you get the error about not having allocated it.