I have a framework A which is ARC. This consumes some API's from non ARC framework B.
Framework B sample code (non ARC):
#interface Settings : NSObject {
NSDictionary* settings;
}
- (NSString*)stringForKey:(NSString*)key;
#end
#implementation Settings
- (NSString*)stringForKey:(NSString*)key {
return [settings objectForKey: key];
}
#end
Framework A sample code (ARC):
{
// ...
SEL stringForKeySelector = #selector(stringForKey:);
NSMethodSignature *stringForKeySignature = [[Settings class] instanceMethodSignatureForSelector:stringForKeySelector];
NSInvocation *stringForKeyInvocation = [NSInvocation invocationWithMethodSignature:stringForKeySignature];
[stringForKeyInvocation setSelector:stringForKeySelector];
[stringForKeyInvocation setTarget:SettingsObject];
NSString *Key = #"KEY";
[stringForKeyInvocation setArgument:& Key atIndex:2];
[stringForKeyInvocation invoke];
NSString *value = nil;
[stringForKeyInvocation getReturnValue:& value];
// ...
}
Object from settings dictionary gets released after executing above code in framework A.
Any help is appreciated in advance.
Answer:
Thanks Chuck for pointing out problems with NSInvocation and ARC.
I got around this problem by returning basic data types.
This has nothing to do with memory management between ARC and non-ARC code. Instead, this is because NSInvocation does not work very cleanly with ARC. It's usually preferable to avoid NSInvocation anyway, and doubly so with ARC. There is usually a better choice for anything you'd use NSInvocation for. If you must use NSInvocation, you'll need to remember that it just deals with raw byte blobs and doesn't handle object ownership at all, so passing an ARC-managed pointer is not kosher. You'll want to have the return-value variable be a void pointer, and then use a bridging cast to assign it to a normal object-type variable.
Related
I'm receiving a static analysis error, and I'm not sure if it can be safely ignored, or if I can improve the design to remove it without to much change, this is legacy code.
This does NOT use ARC.
-(id) initCustom{
NSString* key = #"foo";
NSData* objectData = nil;
objectData = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if( objectData != nil)
{
//If this path is taken the error occurs
self = [NSKeyedUnarchiver unarchiveObjectWithData:objectData];
}
else
{
self = [super init];
}
if (self)
{
//Static analysis warns here
m_fiz = [[NSString alloc] initWithString:#"bar"];
//Instance variable used while 'self' is not set to the result of '[(super or self) init....]'
}
}
My understanding is that [NSKeyedUnarchiver unarchiveObjectWithData:objectData] will cause the "initWithCoder" to be called. This object implements NSCoding, and has the proper methods required by NSCoding implemented.
Is this a false positive from the static analysis, or can I make it better?
The compiler is warning because that is a bizarre way of doing this. :)
Move the "read from defaults or create new" logic into a class method. That will both fix the compiler warning message and be a more consistent pattern.
+ (instancetype) defaultThingamahoover
{
... check defaults database and unarchive ...
... or return new ....
}
BTW: In general, you don't want to shove anything large into the defaults database. It is pretty atypical to archive an object and stick it in there. The defaults database is generally intended for small key/value pairs.
This question already has answers here:
NSInvocation getReturnValue: called inside forwardInvocation: makes the returned object call dealloc:
(3 answers)
Closed 9 years ago.
So I come from the Java world where we are blissfully ignorant of memory management issues. For the most part, ARC has saved my butt, but here is something that has got me stumped. Basically I am using NSInvocations for some stuff, and I ran into some nasty memory issues before I made the following code modifications. Since I made these modifications, the memory crashes have gone away, but I am usually very scared of code that I dont understand. Am I doing this right?
Before: all sorts of memory issues:
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[target class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:target];
[invocation setArgument:&data atIndex:2];
[invocation setArgument:&arg atIndex:3];
[invocation invoke];
NSString *returnValue;
[invocation getReturnValue:&returnValue];
After : No memory issues, but I am not sure I got this right:
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[target class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:target];
[invocation setArgument:&data atIndex:2];
[invocation setArgument:&arg atIndex:3];
[invocation invoke];
CFTypeRef result;
[invocation getReturnValue:&result];
if (result)
CFRetain(result);
NSString *returnValue = (__bridge_transfer NSString *)result;
Edit:
I Just wanted to add on basis of the answer below, I used objc_msgSend, as such.:
NSString * returnValue = objc_msgSend(target, selector, data, arg);
And it solves all the memory issues, plus looks much simpler. Please comment if you see any issues with this.
I will answer your question like this: Don't use NSInvocation. It's just a friendly advice to avoid that if possible.
There are many nice ways to do callbacks in Objective-C, here are two that may be useful for you:
Blocks: Defined in context, choose any argument count and types, possible issues with memory too. There are many resources on how to use them.
performSelector: max 2 object arguments, invoked using:
[target performSelector:selector withObject:data withObject:args];
In addition, when I need to invoke a selector with 4 arguments I still don't use NSIvocation, but rather call objc_msgSend directly:
id returnValue = objc_msgSend(target, selector, data, /* argument1, argument2, ... */);
Simple.
Edit: With objc_msgSend you need to be careful with the return value. If your method returns an object, use the above. If it returns a primitive type, you need to cast the objc_msgSend method so the compiler knows what's going on (see this link). Here's an example for a method that takes one argument and returns a BOOL:
// Cast the objc_msgSend function to a function named BOOLMsgSend which takes one argument and has a return type of BOOL.
BOOL (*BOOLMsgSend)(id, SEL, id) = (typeof(BOOLMsgSend)) objc_msgSend;
BOOL ret = BOOLMsgSend(target, selector, arg1);
If your method returns a struct, things are a bit more complicated. You may (but not always) will need to use objc_msgSend_stret -- see here for more info.
Edit: - this line have to be added to the code, or Xcode will complain:
#import <objc/message.h>
or
#import ObjectiveC.message;
You should generally consider blocks as a superior alternative where possible (they succeeded NSInvocation).
As far as the return value, you can use this:
CFTypeRef result = NULL;
[invocation getReturnValue:&result];
NSString *returnValue = (__bridge NSString *)result;
The underlying issue here is that -getReturnValue: does not return an out object, as far as ARC is concerned. Therefore, it is likely getting the reference count operations wrong (the compiler adds these for you in ARC), because -getReturnValue:'s parameter is void*, not an out object (e.g. NSObject**).
I'm trying to understand why this code is leaking, using ARC:
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:#"Key"];
}
As you can see, I put a block inside a collection (NSMutableDictionary, but it's the same if I use NSDictionary, NSArray ecc...), then the method returns and the dictionary is deallocated. The block should then be released. But, using instruments, I see a leak
"just to be sure" that the block has no other references, I added this line at the end of the method:
[dict setObject:[NSNull null] forKey:#"Key"];
same result.
I've found this post but the answers point to another problem:
Blocks inside NSMutableArray leaking (ARC)
Then, this is the magic:
If I change this line:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:#"Key"];
to:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[aBlock copy] forKey:#"Key"];
the leak disappear.
I know that, under non-ARC, before passing a reference of a block literal, I must copy it (when declared literal, it's on the stack, so I need to copy it to the heap before passing outside the scope of the function where is declared)...but using ARC I shouldn't care about it.
Any indication?
This is happening with all versions from 5.0 to 6.1.
EDIT: I've made some tests, trying to understand if I'm doing something wrong or if there is some bug...
First: Am I reading wrong instruments informations?
I don't think, the leak is real and not my mistake. Look at this image...after executing the method 20 times:
Second: what happens if I try to do the same thing in a non arc environment?
this adds some strange behavior:
same function in NON-ARC environment:
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
[aString release];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:#"Key"];
}
With the previous non-arc implementation, I have a leak only for the block (not for the string)
Changing the implementation to use an autorelease on the mutable string declaring solves the leak!!! I can't understand why, and I'm not sure if it could be related to the main post issue
// version without leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[[NSMutableString alloc] init] autorelease];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:#"Key"];
}
CONCLUSIONS:
After various answers and further investigating, I understood some things:
1- Apple docs says that you must use [^{} copy] when you pass a block to a collection. This is because ARC doesn't add the copy itself. If you don't, the collection (array, dictionary..) sends a retain on a STACK ALLOCATED OBJECT - which does nothing. When the method ends, the block goes out of scope and becomes invalid. You will probably receive a bad access when using it. But note: this is not my case, I'm experiencing a different problem
2- the problem I'm experiencing is different: the block is over-retained (the opposite problem --> the block is still alive even when it shoulnd't be). Why?
I've found this: in my example, I'm using this code
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
this code, under NON-ARC, stores a reference (aBlock) to the literal block. The block is allocated on the stack, so if you NSLog(#"%p", aBlock) -> you will see a stack memory address
But, this is the "strange" (I don't find any indication in Apple docs), if you use the same code under ARC and NSLog aBlock address, you will see that now it's on the HEAP!
For this reason the behavior is different (no bad access)
So, both incorrect but different behavior:
// this causes a leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:#"Key"];
}
// this would cause a bad access trying to retrieve the block from the returned dictionary
- (NSMutableDictionary *)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
return [NSMutableDictionary dictionaryWithObject:^{
NSMutableString __unused *anotherString = aString;
} forKey:#"Key"];
}
3 - about my last test under NON-ARC, I think that the release is in the wrong place. I released the string before adding the block to the dictionary with copy-autorelease.
The block automatically retains the variables referenced inside the block, but the retain message is sent at the copy moment, not at the declaration. So, If I release aString before copying the block, it's retain count goes to 0, then the block sends a retain message to the "zombie" object (with unexpected behavior, it can leak the block, crash, ecc ecc)
See this question for reference iOS 5 Blocks ARC bridged cast; it demonstrates the nightmare that are Blocks and ARC.
Typically, if you assign a block to a variable that lives beyond your current scope, the compiler will be automatically able to copy the block to the heap. This means when you fall out of scope, you still have the block hanging around. Similarly, the same goes with block paramaters. The compiler is aware that it'll need to make a copy of those parameters, and hence does so.
The issue with classes such as NSArray is that they don't usually need to copy an object to keep it correctly; typically they only retain the object. Whereas an object going out of scope is part of the language (hence it copies), keeping it within an object like NSArray is an application level operation. As such, the compiler isn't clever enough yet to determine that the block needs copying (Blocks are standard Obj-C objects after all, it thinks all it needs to do is retain it). In a similar vain, thats why any properties that hold blocks need to specify the copy keyword. The automatic synthesis of the property methods aren't aware a block is being stored, and need to be given a nudge to copy them when being set.
This demonstrates why the whole thing works when you use - copy on your block, you're doing what the compiler should be doing, but is not clever enough to do so...Apple even recommends this technique within its Transitioning to ARC documentation, see the FAQs.
Bootnote: In case you're wondering why I'm on about retaining, even when you're using ARC, is that this is what ARC does under the hood. The memory management model is still the same as before, but the onus is now on the system to manage it for us based on naming and conventions, whereas previously the onus was on the developer to manage their memory correctly. It's just that for blocks, the system isn't able to manage it as fully as it should, and hence the developer needs to step in from time to time.
Blocks begin their life on the stack for performance reasons. If they should live longer than the stack is around, they have to be copied to the heap.
In MRR, you had to do that copying yourself. ARC is doing that automatically for you if you pass a block up the stack (i.e. return it from a method). But if pass a block down the stack (for example, store it in an NSMutableDictionary or NSMutableArray), you have to copy it yourself.
This is documented in Apple's Transitioning to ARC documentation, search for "How do blocks work in ARC" inside that document.
For your Non-ARC examples (as you wrote in your conclusion), the copy of the block should happen before releasing aString, as aString is retained when the block is copied. Otherwise your code will show undefined behavior, it may even crash. Here is some code that demonstrates the problem with Non-ARC:
NSObject *object = [[NSObject alloc] init];
void (^aBlock)() = ^{
NSLog(#"%#", object);
};
[object release];
aBlock(); // undefined behavior. Crashes on my iPhone.
Long story short, I'm tired of the absurd concurrency rules associated with NSManagedObjectContext (or rather, its complete lack of support for concurrency and tendency to explode or do other incorrect things if you attempt to share an NSManagedObjectContext across threads), and am trying to implement a thread-safe variant.
Basically what I've done is created a subclass that tracks the thread that it was created on, and then maps all method invocations back to that thread. The mechanism for doing this is slightly convoluted, but the crux of it is that I've got some helper methods like:
- (NSInvocation*) invocationWithSelector:(SEL)selector {
//creates an NSInvocation for the given selector
NSMethodSignature* sig = [self methodSignatureForSelector:selector];
NSInvocation* call = [NSInvocation invocationWithMethodSignature:sig];
[call retainArguments];
call.target = self;
call.selector = selector;
return call;
}
- (void) runInvocationOnContextThread:(NSInvocation*)invocation {
//performs an NSInvocation on the thread associated with this context
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
[self performSelector:#selector(runInvocationOnContextThread:) onThread:myThread withObject:invocation waitUntilDone:YES];
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
- (id) runInvocationReturningObject:(NSInvocation*) call {
//returns object types only
[self runInvocationOnContextThread:call];
//now grab the return value
__unsafe_unretained id result = nil;
[call getReturnValue:&result];
return result;
}
...and then the subclass implements the NSManagedContext interface following a pattern like:
- (NSArray*) executeFetchRequest:(NSFetchRequest *)request error:(NSError *__autoreleasing *)error {
//if we're on the context thread, we can directly call the superclass
if ([NSThread currentThread] == myThread) {
return [super executeFetchRequest:request error:error];
}
//if we get here, we need to remap the invocation back to the context thread
#synchronized(self) {
//execute the call on the correct thread for this context
NSInvocation* call = [self invocationWithSelector:#selector(executeFetchRequest:error:) andArg:request];
[call setArgument:&error atIndex:3];
return [self runInvocationReturningObject:call];
}
}
...and then I'm testing it with some code that goes like:
- (void) testContext:(NSManagedObjectContext*) context {
while (true) {
if (arc4random() % 2 == 0) {
//insert
MyEntity* obj = [NSEntityDescription insertNewObjectForEntityForName:#"MyEntity" inManagedObjectContext:context];
obj.someNumber = [NSNumber numberWithDouble:1.0];
obj.anotherNumber = [NSNumber numberWithDouble:1.0];
obj.aString = [NSString stringWithFormat:#"%d", arc4random()];
[context refreshObject:obj mergeChanges:YES];
[context save:nil];
}
else {
//delete
NSArray* others = [context fetchObjectsForEntityName:#"MyEntity"];
if ([others lastObject]) {
MyEntity* target = [others lastObject];
[context deleteObject:target];
[context save:nil];
}
}
[NSThread sleepForTimeInterval:0.1];
}
}
So essentially, I spin up some threads targeting the above entry point, and they randomly create and delete entities. This almost works the way it should.
The problem is that every so often one of the threads will get an EXC_BAD_ACCESS when calling obj.<field> = <value>;. It's not clear to me what the problem is, because if I print obj in the debugger everything looks good. Any suggestions on what the problem might be (other than the fact that Apple recommends against subclassing NSManagedObjectContext) and how to fix it?
P.S. I'm aware of GCD and NSOperationQueue and other techniques typically used to "solve" this problem. None of those offer what I want. What I'm looking for is an NSManagedObjectContext that can be freely, safely, and directly used by any number of threads to view and change application state without requiring any external synchronization.
As noa rightly pointed out, the problem was that although I had made the NSManagedObjectContext thread-safe, I had not instrumented the NSManagedObject instances themselves to be thread-safe. Interactions between the thread-safe context and the non-thread-safe entities were responsible for my periodic crashes.
In case anyone is interested, I created a thread-safe NSManagedObject subclass by injecting my own setter methods in lieu of (some of) the ones that Core Data would normally generate. This is accomplished using code like:
//implement these so that we know what thread our associated context is on
- (void) awakeFromInsert {
myThread = [NSThread currentThread];
}
- (void) awakeFromFetch {
myThread = [NSThread currentThread];
}
//helper for re-invoking the dynamic setter method, because the NSInvocation requires a #selector and dynamicSetter() isn't one
- (void) recallDynamicSetter:(SEL)sel withObject:(id)obj {
dynamicSetter(self, sel, obj);
}
//mapping invocations back to the context thread
- (void) runInvocationOnCorrectThread:(NSInvocation*)call {
if (! [self myThread] || [NSThread currentThread] == [self myThread]) {
//okay to invoke
[call invoke];
}
else {
//remap to the correct thread
[self performSelector:#selector(runInvocationOnCorrectThread:) onThread:myThread withObject:call waitUntilDone:YES];
}
}
//magic! perform the same operations that the Core Data generated setter would, but only after ensuring we are on the correct thread
void dynamicSetter(id self, SEL _cmd, id obj) {
if (! [self myThread] || [NSThread currentThread] == [self myThread]) {
//okay to execute
//XXX: clunky way to get the property name, but meh...
NSString* targetSel = NSStringFromSelector(_cmd);
NSString* propertyNameUpper = [targetSel substringFromIndex:3]; //remove the 'set'
NSString* firstLetter = [[propertyNameUpper substringToIndex:1] lowercaseString];
NSString* propertyName = [NSString stringWithFormat:#"%#%#", firstLetter, [propertyNameUpper substringFromIndex:1]];
propertyName = [propertyName substringToIndex:[propertyName length] - 1];
//NSLog(#"Setting property: name=%#", propertyName);
[self willChangeValueForKey:propertyName];
[self setPrimitiveValue:obj forKey:propertyName];
[self didChangeValueForKey:propertyName];
}
else {
//call back on the correct thread
NSMethodSignature* sig = [self methodSignatureForSelector:#selector(recallDynamicSetter:withObject:)];
NSInvocation* call = [NSInvocation invocationWithMethodSignature:sig];
[call retainArguments];
call.target = self;
call.selector = #selector(recallDynamicSetter:withObject:);
[call setArgument:&_cmd atIndex:2];
[call setArgument:&obj atIndex:3];
[self runInvocationOnCorrectThread:call];
}
}
//bootstrapping the magic; watch for setters and override each one we see
+ (BOOL) resolveInstanceMethod:(SEL)sel {
NSString* targetSel = NSStringFromSelector(sel);
if ([targetSel startsWith:#"set"] && ! [targetSel contains:#"Primitive"]) {
NSLog(#"Overriding selector: %#", targetSel);
class_addMethod([self class], sel, (IMP)dynamicSetter, "v#:#");
return YES;
}
return [super resolveInstanceMethod:sel];
}
This, in conjunction with my thread-safe context implementation, solved the problem and got me what I wanted; a thread-safe context that I can pass around to whomever I want without having to worry about the consequences.
Of course this is not a bulletproof solution, as I have identified at least the following limitations:
/* Also note that using this tool carries several small caveats:
*
* 1. All entities in the data model MUST inherit from 'ThreadSafeManagedObject'. Inheriting directly from
* NSManagedObject is not acceptable and WILL crash the app. Either every entity is thread-safe, or none
* of them are.
*
* 2. You MUST use 'ThreadSafeContext' instead of 'NSManagedObjectContext'. If you don't do this then there
* is no point in using 'ThreadSafeManagedObject' (and vice-versa). You need to use the two classes together,
* or not at all. Note that to "use" ThreadSafeContext, all you have to do is replace every [[NSManagedObjectContext alloc] init]
* with an [[ThreadSafeContext alloc] init].
*
* 3. You SHOULD NOT give any 'ThreadSafeManagedObject' a custom setter implementation. If you implement a custom
* setter, then ThreadSafeManagedObject will not be able to synchronize it, and the data model will no longer
* be thread-safe. Note that it is technically possible to work around this, by replicating the synchronization
* logic on a one-off basis for each custom setter added.
*
* 4. You SHOULD NOT add any additional #dynamic properties to your object, or any additional custom methods named
* like 'set...'. If you do the 'ThreadSafeManagedObject' superclass may attempt to override and synchronize
* your implementation.
*
* 5. If you implement 'awakeFromInsert' or 'awakeFromFetch' in your data model class(es), thne you MUST call
* the superclass implementation of these methods before you do anything else.
*
* 6. You SHOULD NOT directly invoke 'setPrimitiveValue:forKey:' or any variant thereof.
*
*/
However, for most typical small to medium-sized projects I think the benefits of a thread-safe data layer significantly outweigh these limitations.
Why not just instantiate your context using one of the provided concurrency types, and leverage performBlock / performBlockAndWait?
That implements the necessary thread confinement with having to mangle with the implementation of Core Data's accessor methods. Which, as you will soon find out will be either very painful to get right or end quite badly for your users.
A great tutorial by Bart Jacobs entitled: Core Data from Scratch: Concurrency for those that need an elegant solution for iOS 5.0 or later and/or Lion or later. Two approaches are described in detail, the more elegant solution involves parent/child managed object contexts.
I'm using Objective-C, and I don't know how to create and call a method with out parameters when compiling the code with the ARC compiler.
This is the kind of thing I'm trying to accomplish in non-ARC Objective-C (this is probably wrong anyway).
//
// Dummy.m
// OutParamTest
#import "Dummy.h"
#implementation Dummy
- (void) foo {
NSString* a = nil;
[self barOutString:&a];
NSLog(#"%#", a);
}
- (void) barOutString:(NSString **)myString {
NSString* foo = [[NSString alloc] initWithString:#"hello"];
*myString = foo;
}
#end
I've read the documentation here:
https://clang.llvm.org/docs/AutomaticReferenceCounting.html
and here:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html
...but am finding it difficult to get anything that compiles, never mind anything that is correct. Would anybody be able to rewrite the jist of the code above, in a way that is suitable for ARC Objective-C?
You need to use the __autoreleasing attribute on the out parameter:
- (void) barOutString:(NSString * __autoreleasing *)myString {
NSString* foo = [[NSString alloc] initWithString:#"hello"];
*myString = foo;
}
The prerelease documentation (which I'm not allowed to link to due to NDA) puts the __autoreleasing in the middle of the two '*'s, but it might just work as (__autoreleasing NSString **)
You also cannot use an indirect double pointer (b) as in your original code. You must use this form:
- (void) foo {
NSString* a = nil;
[self barOutString:&a];
NSLog(#"%#", a);
}
You are also calling dealloc directly on an object which is completely wrong. I suggest you read the memory management guidelines.