Reputation: 21808
I have a block property defined like this:
@property (nonatomic, strong) void(^block)(void);
and then I'm trying to use it as a @synchronized
lock:
@synchronized (self.block)
{
//doing something
}
The compiler doesn't like this. It says @synchronized requires an Objective-C object type ('void (^)(void)' invalid)
So I'm cheating the compiler by casting the block to id
@synchronized ((id)self.block)
{
//doing something
}
And it seems to work but I'm not sure if I'm doing a right thing. There may be some caveats I'm not aware of. What do you think about this? Is it even acceptable to use a block like this or is it a bad practice due to some reasons unknown to me?
Upvotes: 0
Views: 463
Reputation: 122439
I think that, in practice, it should be safe to do what you are doing. Objective-C blocks are instances of NSObject
(though this is an implementation detail), and you are generally able to do anything with a block that you can do with any other object.
There are sometimes tricky things with NSStackBlock
s, which represents blocks that capture variables, which initially reside on the stack before they are copied for the first time. They are tricky because you must copy them, not just retain them, in order to keep them around for use later. However, in your case, you are dealing with a block stored in a property. If you wrote your code correctly, a block you store in a property must be copied before it is stored (since the property might outlive a stack frame). Either the property is synthesized with copy
, or you implemented the property via getters and setters, in which case ARC should copy it when you assign something to an instance variable of block type. (If you are using MRC then you have to make sure it is copied before storing it in an instance variable.) Therefore, assuming it is copied, it must be an NSGlobalBlock
(representing blocks that don't capture variables) or NSMallocBlock
(representing blocks that capture variables that have been copied). Both of these behave like normal Objective-C objects, and remain valid for as long as there is a strong reference.
The only caveat I can think of is if it is an NSGlobalBlock
(i.e. it is a block that doesn't capture variables), there is just one block object for all the uses of that block. So synchronizing on different instances of that block would all share the same lock, which may be stronger locking than you might have intended.
Upvotes: 1
Reputation: 3018
You can sync on NSObject
and subclasses.
Deep down a block at some stage becomes a __NSMallocBlock__
or a __NSStackBlock__
but that is not something you should rely on or even really be aware of.
I think you need to have a separate object to be the lock. Why don't you just do
@property (nonatomic,strong) NSObject * lock;
and sync on that whenever you need to sync. Also, you can set that whenever you set the block, e.g.
-(void)setBlock:(...)block
{
_block = block;
self.lock = NSObject.new;
}
and you are ready to roll.
EDIT
See this NSDictionary and Objective-C block quirk - and be careful of blocks.
The compiler optimises the way in which they are stored and this can lead to trouble in some cases. If you have to sync on a block then rather do something like
__strong NSObject * p = block;
@synchronize ( p )
{
// ....
}
to ensure you and the compiler are in agreement about what you are syncing on. Here the __strong
is overkill and you can omit it, but just showing explicitly why you need it.
Upvotes: 1