ios invoke block target with copy method,but crashed - ios

I use invocation call block copy, I think it's equals to [block copy],but crashed why?
#implementation MyService
+ (void)load {
[MyService startRequest:^(id _Nonnull responseObject, NSError * _Nonnull error) {
NSLog(#"%#",self);
}];
}
+ (void)startRequest:(void (^)(id responseObject,NSError *error))object {
SEL sel = #selector(copy);
NSMethodSignature* methodSign = [object methodSignatureForSelector:sel];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:methodSign];
[invocation setSelector:sel];
[invocation setTarget:object];
[invocation invoke];
}
#end

Due to an undocumented feature (see this answer), invoking NSInvocation on a block actually calls the block, instead of sending a message to the block object. The method signature you provided is not the method signature of the actual block call, so it leads to undefined behavior.

Related

EXC_BAD_ACCESS from setting pass-by-writeback error within enumerateObjectsUsingBlock

The following code causes an EXC_BAD_ACCESS upon attempting to set *error.
- (void)triggerEXC_BAD_ACCESS
{
NSError *error = nil;
[self doSetErrorInBlock:&error];
}
- (void)doSetErrorInBlock:(NSError * __autoreleasing *)error
{
[#[#(0)] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
*error = [NSError errorWithDomain:#"some.domain" code:100 userInfo:nil]; // <--- causes EXC_BAD_ACCESS
}];
}
However, I'm not sure why the EXC_BAD_ACCESS is occurring.
Replacing the enumerateObjectsUsingBlock: call with the following function, which tries to reproduce the function signature of enumerateObjectsUsingBlock:, will let the function triggerEXC_BAD_ACCESS run without error:
- (void)doSetErrorInBlock:(NSError * __autoreleasing *)error
{
[self runABlock:^(id someObject, NSUInteger idx, BOOL *anotherWriteback) {
*error = [NSError errorWithDomain:#"some.domain" code:100 userInfo:nil]; // <--- No crash here
}];
}
- (void)runABlock:(void (NS_NOESCAPE ^)(id obj, NSUInteger idx, BOOL *stop))block
{
BOOL anotherWriteback = NO;
block(#"Some string", 0, &anotherWriteback);
}
Not sure if I'm missing anything about how ARC works here, or if it's specific to the version of Xcode that I'm using (Xcode 12.2).
I couldn't reproduce a crash in -doSetErrorInBlock:, but I can reproduce a crash in -triggerEXC_BAD_ACCESS with "-[NSError retain]: message sent to deallocated instance" in debug node (I am not sure if it's due to NSZombie or some other debug options).
The reason for it is that *error in -doSetErrorInBlock: has type NSError * __autoreleasing, and the implementation of -[NSArray enumerateObjectsUsingBlock:] (which is closed-source but it's possible to examine the assembly) happens to internally have an autorelease pool around the execution of the block. An object pointer which is __autoreleasing means that we don't retain it, and we assume it is alive by being retained by some autorelease pool. That means it is bad to assign something to an __autoreleasing variable inside an autorelease pool, and then try to access it after the autorelease pool ended, because the end of the autorelease pool could have deallocated it, so you can be left with a dangling pointer. This section of the ARC spec says:
It is undefined behavior if a non-null pointer is assigned to an
__autoreleasing object while an autorelease pool is in scope and
then that object is read after the autorelease pool’s scope is left.
The reason why the crash message says it is trying to retain it, is because of what happens when you try to pass a "pointer to __strong" (e.g. &error in -triggerEXC_BAD_ACCESS) to a parameter of type "pointer to __autoreleasing" (e.g. the parameter of -doSetErrorInBlock:). As you can see from this section of the ARC spec, a "pass-by-writeback" process happens, where they create a temporary variable of type __autoreleasing, assign the value of the __strong variable to it, make the call, and then assign the value of the __autoreleasing variable back to the __strong variable, so your triggerEXC_BAD_ACCESS method is really kind of like this:
NSError *error = nil;
NSError * __autoreleasing temporary = error;
[self doSetErrorInBlock:&temporary];
error = temporary;
The last step of assigning the value back to the __strong variable performs a retain, and that's when it encounters the deallocated instance.
I could reproduce the same crash in your second example if I change the -runABlock: to:
- (void)runABlock:(void (NS_NOESCAPE ^)(id obj, NSUInteger idx, BOOL *stop))block
{
BOOL anotherWriteback = NO;
#autoreleasepool {
block(#"Some string", 0, &anotherWriteback);
}
}
You shouldn't really use __autoreleasing in a new method that you write. __strong is much better because the strong reference makes sure you don't accidentally have dangling references and problems like that. The main reason why __autoreleasing exists is because back in manual reference counting days, there were no explicit ownership qualifiers, and the "convention" was that retain counts were not transferred into or out of methods, and so objects returned from a method (including objects returned by pointer using an out-parameter) would be autoreleased rather than retained. (And those methods would be responsible for ensuring that the object is still valid when the method returns.) And since your program can be used on different OS versions, they cannot change the behavior of APIs in new OS versions, so they are stuck with this "pointer to __autoreleasing" type. However, in a method that you yourself write in ARC (which does have explicit ownership qualifiers), which is only going to be called by your own ARC code, by all means use __strong. If you wrote your method using __strong, it would not crash (by default pointer-to-object-pointers are interpreted as __autoreleasing, so you have to specify __strong explicitly):
- (void)doSetErrorInBlock:(NSError * __strong *)error
{
[#[#(0)] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
*error = [NSError errorWithDomain:#"some.domain" code:100 userInfo:nil];
}];
}
If you for some reason insist on taking a parameter of type NSError * __autoreleasing *, and want to do the same thing you were doing but safely, you should be using a __strong variable for the block, and only assign it into the __autoreleasing at the end:
- (void)doSetErrorInBlock:(NSError * __autoreleasing *)error
{
__block NSError *result;
[#[#(0)] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
result = [NSError errorWithDomain:#"some.domain" code:100 userInfo:nil];
}];
*error = result;
}

How to create instance of my own class using NSInvocation?

I am trying to create a new instance of my custom class (custom init method call, with a BOOL parameter) dynamically. How can I use NSInvocation to do that?
This is what I have so far:
NSMethodSignature* signature = [NSClassFromString(className) instanceMethodSignatureForSelector: sel];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: signature];
[invocation setTarget: [NSClassFromString(className) alloc]];
[invocation setSelector:sel];
[invocation setArgument:&value atIndex:2];
[invocation invoke];
[invocation getReturnValue:&obj];
Above sample throws error in line [invocation invoke]; error is "message sent to deallocated instance".
Your code doesn't work because the NSInvocation doesn't retain the target or any arguments unless you tell it to (with retainArguments). So, you alloc an instance and then it gets destroyed before you invoke the NSInvocation.
Alternatively, create an instance variable and store the alloc'd instance there, then pass that to the NSInvocation.

Perform selector warning vs dispatch _async

[NOT DUPLICATE: read well the question and the already given answers, I've already read them]
I'm facing that problem, I need to substitute -performSelector method since it causes that warning in the compiler with ARC
performSelector may cause a leak because its selector is unknown
I'm aware that there different questions about that topic:
One
Two
Three
and I'm also aware about the techniques to avoid that warning.
Sometimes as a solution I found that the most suggested advice is to use dispatch_async(dispatch_get_main_queue(),^(void) { WHATEVER });
but as far as I know dispatching would require the execution of the block in the next run loop, -performSelector (without delay) is executed immediately.
Why this mental masturbation? Imagine that you are using the new Gamekit method for authentication, game kit could send you inside the auth block a view controller to make the user do some operation (such as creating the id, log in, etc). It could be useful to warn the user if he/She wants to see that view controller. To do that and other stuffs I'm writing a protocol. In particular that method should return a BOOL - (BOOL)shouldLoadGameKitViewController: (UIViewController*) viewController;, I'd like to call it using -performSelector. Here is the point, if the method dosen't return immediately I can't get the answer from the delegate.
I'm using NSInvocation to make that happen but is verbose, does exist some other way?
[UPDATE WITH CODE] Pay attention that now I'm using invocation, the thread-check part is not still implemented. Commented there is the part that gave the warning
- (void) dispatchToDelegate: (SEL) selector withArg: (id) arg error: (NSError*) err
{
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setTarget:self.delegate];
if([self.delegate respondsToSelector: selector])
{
if(arg != NULL) {
[invocation setArgument:&arg atIndex:2];
[invocation setArgument:&err atIndex:3];
// [_delegate performSelector: selector withObject: arg withObject: err];
}else {
[invocation setArgument:&err atIndex:2];
// [_delegate performSelector: selector withObject: err];
}
[invocation invoke];
}
else
DLog(#"Method not implemented in the delegate");
}
[SORT OF SOLUTION STILL UNTESTED]
- (BOOL) dispatchToDelegate: (SEL) selector withArg: (id) arg error: (NSError*) err
{
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setTarget:self.delegate];
BOOL result = NO;
if([self.delegate respondsToSelector: selector])
{
if(arg != NULL) {
[invocation setArgument:&arg atIndex:2];
[invocation setArgument:&err atIndex:3];
// [_delegate performSelector: selector withObject: arg withObject: err];
}else {
[invocation setArgument:&err atIndex:2];
// [_delegate performSelector: selector withObject: err];
}
if ([NSThread isMainThread]) {
[invocation invoke];
}
else{
[invocation performSelectorOnMainThread:#selector(invoke) withObject:nil waitUntilDone:YES];
}
[invocation getReturnValue:&result];
}
else
NSLog(#"Missed Method");
return result;
}
Using the NSMethodSignature method, is possible to gear up, and ask for the return type. I still didn't test but it should made the trick.
Try to make this to avoid leak or "unknown selector send to instance" issue:
SEL selector = NSSelectorFromString(#"methodName::");
if ([obj respondsToSelector:selector])
[obj performSelector:selector withObject#[args,array]];
You can use dispatch_sync instead of dispatch_async to have the block performed immediately (your code will be blocked until the block returns).
Another alternative is to suppress the warning temporarily, as explained in this answer.

Making Core Data Thread-safe

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.

How to create an NSInvocation object using a method that takes a pointer to an object as an argument

I would like to create an NSInvocation object using a method that takes a pointer to an NSError object as an argument. An example of this would be the method -
- (BOOL)writeToFile:(NSString *)path options:(NSDataWritingOptions)mask error:(NSError **)errorPtr
I undersand that I would set my invocation up like this
NSData *myData = [[NSData alloc] init];
SEL writeToFileSelector = #selector(writeToFile:options:error:);
NSMethodSignature *signature = [NSData instanceMethodSignatureForSelector:writeToFileSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:myData];
[invocation setSelector:writeToFileSelector];
NSString *string = [NSString stringWithFormat:#"long cat"];
NSDataWritingOptions *dataOptions;
*dataOptions = NSDataWritingFileProtectionComplete;
[invocation setArgument:&string atIndex:2];
[invocation setArgument:&dataOptions atIndex:3];
For writeToFile:Options:Error: the last argument is expecting to receive a pointer instead of an object. As a result doing the following does not work -
NSError *err = nil;
[invocation setArgument:&err atIndex:4];
It seems logical that the solution might be to create a pointer to a pointer, but this causes a compiler warning. I am not sure how to execute that properly and not create a memory management problem.
You create the argument just the same as any other argument you'd pass to the method.
As you point out, the method signature wants an NSError ** for its last argument (index 4). So, you will need to declare one, but there's a bit of a gotcha.
NSError **errorPointer
Gives you a variable that points to an NSError variable. But, since you haven't told it to point to any variable, it points to nil. Therefore when you fire the invocation the selector won't be able to change the variable your error pointer points to. In other words, it would be like calling [myData writeToFile:string options:dataOptions error:NULL].
So, you'll want to also declare an NSError variable, and assign its address as the variable your errorPointer should point to:
NSError *error;
NSError **errorPointer = &error;
Now you can pass in the errorPointer as an argument, and you'll be able to inspect it later if there was a problem when you invoked the method. Check out this post on NSInvocation for a little more help (hat tip to Mark Dalrymple for pointing out the blog post)
It is important to also realize that the scope should be considered for the arguments you create and pass into your invocation. Take a look at a similar question I asked here.
The accepted answer by edelaney05 was great, but I think it needs a slight tweak for ARC.
(I can't add comments yet, so creating a new answer to document what I found)
As is, I got the compile error:
"Pointer to non-const type 'NSError *' with no explicit ownership"
I researched this and found that I needed:
NSError * __autoreleasing error = nil;
NSError * __autoreleasing *errorPointer = &error;
References that led me to this answer:
NSInvocation & NSError - __autoreleasing & memory crasher
Automatic Reference Counting: Pointer to non-const type 'NSError *' with no explicit ownership
http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
"__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return."
The parameter type is NSError **, which you get from taking the address of an NSError * that you want the error to be written to. To set an argument in an NSInvocation, you need to pass the address of a value of the argument to setArgument:, so you need to put your NSError ** in a variable (I call it errPointer here), and take the address of that (which will be an NSError ***) to pass to setArgument:. You don't need the errPointer variable afterwards.
NSError *err = nil;
NSError **errPointer = &err;
[invocation setArgument:&errPointer atIndex:4];

Resources