简体   繁体   中英

How to determine when the value pointed to by a pointer is nil

I have a situation where troops can attack buildings. Each troop keeps a pointer to its target.

@property (nonatomic, weak) Building *target;

In an update loop, the troops periodically cause damage to their target.

if (_target)  
{
    if (/*enough time has passed since last attack, attack again*/)
    {
        [_target attack];
        if (_target.health <= 0)
        {
            [_target removeFromParentAndCleanup:YES];   //Cocos2d
            _target = nil;
        }
    }
}
else /* Find new target */ 

The problem is:

  • troop1 deals the blow that fells building1 and moves on to building2
  • troop2 was attacking building1 but waits until its next attack to determine that building1 is now nil .

I realise the problem is that troop2 's pointer has not been set to nil and instead I should be checking that the value of the pointer is nil.

I tried using if (*_target) but was met with the message

Statement requires expression of scalar type

If there a way to achieve this kind of comparison in Objective-C? What other options are there for determining when a value has changed? KVO? Some extensive delegate pattern?

It is the pointer itself that is set to nil when the object it points to is deallocated. if (objectPointer == nil) is always the way to check if an object is nil in Objective-C/Cocoa. If the pointer is not nil, it means the object in question has not in fact been deallocated. If you dereference a pointer to an object, you get a struct, hence the compiler error about needing a scalar value in the if expression.

So, in your case, if if(self.target != nil) is not giving you the result you expect, you should look for remaining strong references to the target (from other objects).

More broadly, as hinted at by trojanfoe's answer, you're relying on ARC's zeroing weak reference behavior for real program logic. In theory this is OK, as (contrary to his initial statement), ARC's zeroing weak behavior is reliable/deterministic. But, it does mean that you have to ensure that targets are always deallocated when they're no longer on the playing field (or whatever). This is a bit fragile. Zeroing weak references are intended as a way to avoid retain cycles (essentially a form of memory leak), rather than as a way to implement logic the way you're doing. The gist of trojanfoe's solution, where you explicitly register and unregister targets as necessary, is probably a more robust solution.

There may be something that I have overlooked here, but to check if the target2 property is nil , just do:

if ( self.target2 == nil ) {
     // Something
}

I think you are relying too heavily on the implementation of ARC in that you only know if an object has been removed if the pointer is nil . This is non-portable and can you make any guarantee between the object being released and the pointer becoming nil ?

Instead, use a central dictionary of objects, mapped against their unique ID and store just this unique ID rather than the object pointer itself. In this example I'm using a NSNumber for the key using an incrementing integer, but there are probably better keys that can be used. Also Object is the base class of any object you want to store in this dictionary:

// Probably ivars in a singleton class
unsigned _uniqueId = 1;
NSMutableDictionary *_objects;

- (NSNumber *)addObject:(Object *)object
{
    NSNumber *key = [NSNumber numberWithUnsignedInt:_uniqueId++];
    [_objects setObject:object forKey:key];
    return key;
}

- (void)removeObjectForKey:(NSNumber *)key
{
    [_objects removeObjectForKey:key];
}

- (Object *)getObjectForKey:(NSNumber *)key
{
    return [_objects objectForKey:key];
}

And in your target, simply store the building key:

@property (strong) NSNumber *buildingKey;

and get the building via the methods provided:

Building *building = (Building *)[objectDictionary objectForKey:buildingKey];
if (building != nil)
{
    // building exists
}
else
{
    // building does not exist; throw away the key
    buildingKey = nil;
}

Since target is a weak reference, your code should work "as-is", assuming that [_target removeFromParentAndCleanup:YES]; removes all strong references to the target.

When the last strong reference is removed, all of the weak properties pointing to it will automatically be set to nil . If they are not automatically set to nil , then there is still a strong reference to the target somewhere.

Find and remove that reference, and this will work fine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM