Passing objects safely as opaque context params un

2019-06-21 23:40发布

问题:

Under manual memory management, I use this pattern fairly often:

NSString * myStr = /* some local object */
[UIView beginAnimation:@"foo" context:(void *)[myStr retain]];

And then, later and asynchronously:

- (void)animationDidStop:(NSString *)anim finished:(NSNumber *)num context:(void *)context
{
    NSString * contextStr = (NSString *)context;
    // ...
    [contextStr release];
}

i.e. I manually managed the lifetime of an object used as an opaque context. (This is true for the old UIView animations but also for other kinds of API that I use.)

Under ARC, my instinct is that I want to __bridge_retained going in and __bridge_transfer in the handler, as suggested here. But this treats a Cocoa object as a CFType not because it's really bridged, but just for the purpose of shoving a retain down its throat.

Is this valid, and is this stylistically acceptable? If not, what's the better* solution?

(The accepted answer in this question gives a different answer, saying that __bridge alone is OK, but that seems to me to be wrong, since the original string would be at risk of being deallocated as soon as it goes out of scope in the first function. Right?)

*Please don't say "use block-based animations instead". (That's not what I'm asking about.)

回答1:

Go with your instinct. __bridge_retained transfers management of an object to you from ARC, while __bridge_transfer does the reverse, don't worry about treating the object as a CFType - you're not really doing that just taking over management.

The other approach you see recommended is to construct your code so that ARC retains management, but this can easily come across as contrived (and get messy). Having the API you're using maintain the value as it is designed to do is clean; just comment the code appropriately where management is handed to the API and returned back to ARC.



回答2:

Even if using __bridge_retained/__bridge_transfer seems fine to me (transferring ownership to CoreFoundation or to any C code or to yourself is quite the same you just tell ARC that you are responsible for the object ownership at some point and give the ownership back to ARC later), you can instead keep a strong reference on your object you use as your context somewhere if you prefer, so that ARC does not reclaim its memory.

This can be achieved by using a @property(strong) in your class for example, affecting it to the value when you previously did your retain and assigning it to nil when you previously did your release to let the string go.


Note that if you need to keep around multiple contexts in the same class, you may opt for the option to use an NSMutableArray that keeps your context strings around instead of declaring a property for each context.

@interface YourClass ()
@property(strong) NSMutableArray* runningAnimationContexts;
@end

@implementation YourClass
-(id)init {
  self = [super init];
  if (self) {
    self.runningAnimationContexts = [NSMutableArray array];
  }
  return self;
}

-(void)someMethod
{
  // Example with two different parallel animations using old API

  NSString * myStr = /* some local object */
  [self.runningAnimationContexts addObject:myStr]; // ~ retain
  [UIView beginAnimation:@"foo" context:(__bridge)myStr];
  ...
  [UIView commitAnimations];

  NSString * myStr2 = /* some other local object */
  [self.runningAnimationContexts addObject:myStr2]; // ~ retain
  [UIView beginAnimation:@"foo2" context:(__bridge)myStr2];
  ...
  [UIView commitAnimations];

}
- (void)animationDidStop:(NSString *)anim finished:(NSNumber *)num context:(void *)context
{
  NSString * contextStr = (__bridge NSString *)context;
  // ...
  [self.runningAnimationContexts removeObject:contextStr]; // ~ release
}
@end