Reputation: 32066
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?
Upvotes: 2
Views: 285
Reputation: 21383
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.
Upvotes: 4
Reputation: 25740
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.
Upvotes: 1
Reputation: 122401
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;
}
Upvotes: 1
Reputation: 18253
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
}
Upvotes: 2