Why is a method invoked from a singleton using a different instance object than the NSNotificationCenter?

StackOverflow https://stackoverflow.com/questions/15736931

  •  31-03-2022
  •  | 
  •  

سؤال

The Short Version:

I ran into an issue where a method was being invoked on a different instance of an object than the NSNotificationCenter was pushing notifications to (though only one instance was created).

The Long Version:

I have a "Puzzle" object instance which is being operated on by an OpenGL update/draw loop.

I created a Control singleton to manage different touch events like so:

@implementation Controls

static Controls *SharedGameControls = nil;

-(id)init {
    self = [super init];
    return self;
}

+ (Controls*)SharedGameControls{
    if (SharedGameControls == nil){
        SharedGameControls = [[super allocWithZone:NULL] init];
    }
    return SharedGameControls;
}

+ (id)allocWithZone:(NSZone *)zone
{
    return self.SharedGameControls;
}

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}


-(void)oneFingerSwipeDelegator:(UISwipeGestureRecognizer *)swipe{
    switch (GState.currentMode)
    {
        case PuzzleLayer: {
            CGPoint Origin = [swipe locationInView: _view];
            //I believe this line should call oneFingerSwipe on the object instance 
            //provided to the singleton
            [_oneFingerSwipeDelegate oneFingerSwipe:Origin Direction:swipe.direction];
            break;
        }
        default:{
            break;
        }
    }
}

-(void)setDefaultState{
    GState = [GameState SharedGameState];

    UISwipeGestureRecognizer *oneFingerSwipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(oneFingerSwipeDelegator:)];
    [oneFingerSwipeUp setDirection:UISwipeGestureRecognizerDirectionUp];
    [_view addGestureRecognizer:oneFingerSwipeUp];

    UISwipeGestureRecognizer *oneFingerSwipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(oneFingerSwipeDelegator:)];
    [oneFingerSwipeDown setDirection:UISwipeGestureRecognizerDirectionDown];
    [_view addGestureRecognizer:oneFingerSwipeDown];

    UISwipeGestureRecognizer *oneFingerSwipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(oneFingerSwipeDelegator:)];
    [oneFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
    [_view addGestureRecognizer:oneFingerSwipeLeft];

    UISwipeGestureRecognizer *oneFingerSwipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(oneFingerSwipeDelegator:)];
    [oneFingerSwipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
    [_view addGestureRecognizer:oneFingerSwipeRight];

    UITapGestureRecognizer * single = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapDelegator:)];
    single.numberOfTapsRequired = 1;
    [_view addGestureRecognizer:single];
}

-(void)singleTapDelegator:(UITapGestureRecognizer *)tap{
    CGPoint origin = [tap locationInView: _view];
    NSValue *Origin = [NSValue valueWithCGPoint:origin];
    switch (GState.currentMode)
    {
        case PuzzleLayer: {
            [[NSNotificationCenter defaultCenter] postNotificationName:@"puzzleTap" object:nil userInfo:[NSDictionary dictionaryWithObject:Origin forKey:@"Origin"]];
            break;
        }
        default:{
            break;
        }
    }
}

@end

The '_oneFingerSwipeDelegate' is defined in the .h file as

@property (nonatomic, assign) id oneFingerSwipeDelegate;

Then in the Puzzle class the events were handled thusly:

@implementation Puzzle

-(id)init{
    self =[super init];
    if (self){
        GControls = [Controls SharedGameControls];
        //I believe, possibly wrongly, that the line below will set this object instance
        //to be the object oneFingerSwipe is called on
        GControls.oneFingerSwipeDelegate = self;
        GControls.twoFingerSwipeDelegate = self;
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(singleTap:) name:@"puzzleTap" object:nil];
        _Started = false;
       //other code omitted
    }
    return self;
}

//implementation of the delegate in Controls.h
-(void)oneFingerSwipe:(CGPoint)touchPoint Direction:(UISwipeGestureRecognizerDirection)direction{
    //do some stuff with objects inside puzzle
}

//observer for 'puzzleTap'
-(void)singleTap:(NSNotification*)note{
    GLKVector3 Near,Far;
    NSDictionary *dict = [note userInfo];
    CGPoint origin = [[dict objectForKey:@"Origin"] CGPointValue ];
    //Do stuff with objects inside puzzle
}

//other code omitted
@end

So I was testing the gesture recognition to make sure everything was working and I noticed that my swipes weren't being handled.

Tapping correctly sent the notification to the singleTap method in the Puzzle object (which sets a 'selected' flag on a child object of the Puzzle instance).

Swiping correctly invoked the oneFingerSwipe method on the puzzle object, but for some reason was unable to detect which object had been 'selected' by the tap.

After taking a closer look I noticed that when I stepped in to singleTap I was being shown an address for the puzzle object which was different than the address shown when stepping into the oneFingerSwipe method called by the Control singleton.

So effectively, for some reason, the Control singleton is operating on a twin instance of the object that the notification is being sent to (or the reverse). All the objects within each instance seem to have different memory addresses than the respective twin.

As a result, when the oneFingerSwipe is called, the 'selected' flag hasn't been updated on the child object and so the logic for swiping isn't being invoked.

When I switched to using notifications for the swipe, the issue went away.

I don't really understand what is going on here. Is the Control singleton creating a copy of the Puzzle instance when I assign the puzzle instance to the oneFingerSwipe property?

I've only been working with Objective C for about 6 months and there is a great deal I don't understand.

Thanks for pushing through that wall of text, I appreciate you taking the time to have a look.

هل كانت مفيدة؟

المحلول

The reason it seemed that the direct method invocation was happening on a different object than the NSNotificationCenter was pushing notifications to is that it was, in fact, a different object. I had instantiated a number of different puzzle objects in a loop and each one was, in turn, setting itself as the target for oneFingerSwipe. When I was stepping through I happened to be looking at two incredibly similar but different objects and wrongly came to the conclusion that one was a copy of the other. Many thanks to the commenters for taking the time to look at this lengthy question and pointing me in the right direction. For now I've switched to solely using NSNotifications, but I'm going to do a little more reading into using delegates to see if that might better suit my needs.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top