Reputation: 66302
I have a category on NSString:
- (CGSize) agb_sizeWithFont:(UIFont *)font width:(CGFloat)width lineBreakMode:(NSLineBreakMode)lineBreakMode {
if (!font) return CGSizeZero;
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = lineBreakMode;
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
// this line is not threadsafe
NSAttributedString *as = [[NSAttributedString alloc] initWithString:self attributes:attributes];
CGRect bounds = [as boundingRectWithSize:CGSizeMake(width, 10000) options:(NSStringDrawingUsesLineFragmentOrigin) context:nil];
return CGSizeMake(width, bounds.size.height);
}
I've observed intermittent EXC_BAD_ACCESS
crashes when running this code on multiple threads (sometimes in initWithString: attributes:
, sometimes in boundingRectWithSize:options:context:
).
I believe my code is not thread-safe because self
might deallocate on one thread while initWithString: attributes:
is executing on the other.
Would this code make it thread-safe?
NSString *strongSelf = self;
NSAttributedString *as = [[NSAttributedString alloc] initWithString:strongSelf attributes:attributes];
(By maintaining a reference to self
, I attempt to ensure the object in memory is not deallocated while I'm using it.)
Is there any way to declare this method as not thread-safe? I'd love to generate a warning, for example, if a string declared as a nonatomic
property is passed into this method.
Note: In case it's not obvious, I'm using ARC.
Upvotes: 3
Views: 765
Reputation: 90701
Any NSMutableString
is-a NSString
, too. Any method you add to NSString
in a category can therefore be invoked on an NSMutableString
, too. It is not safe to operate on a mutable string on one thread if it might be being mutated at the same time on another thread. It is safe to read a mutable string while it is being read on another thread.
Since your invocation of -[NSAttributedString initWithString:attributes:]
is reading from the string self
, that's not safe to do if self
is a mutable string and might be mutated on another thread at the same time.
You might be tempted to review all calls of -agb_sizeWithFont:width:lineBreakMode:
to see if the type of the receiver is NSMutableString*
. However, that's not sufficient. An instance of NSMutableString
may be pointed to by a variable of type NSString*
(or other things like NSObject*
or id
), too.
You won't be able to make -agb_sizeWithFont:width:lineBreakMode:
thread-safe, as such. Thread-safety doesn't decompose. It will have to be designed into the code which manages whatever strings it might be invoked on. That is, all of the code which touches a given string object and might invoke your method and, in other places, might mutate it, must be in charge of making sure that only one of those things is happening at a time.
Upvotes: 6
Reputation: 46608
Your code looks like ARC enabled so I assume it is enabled.
self might deallocate on one thread while initWithString: attributes: is executing on the other.
this is not possible with ARC, because in order to execute agb_sizeWithFont...
method, you must doing something like this
NSString *str = /*...*/; // this will create a strong reference to str
[str agb_sizeWithFont:/*...*/]; // even str is released in other thread at this point, it won't be deallocated
NSString *strongSelf = self;
this code just create an extra strong reference to self
, but since self
must be retained somewhere already, it won't change anything
I don't think you can declare anything to be "not thread-safe" (actually everything are not thread-safe by default) and I don't understand how you want to use such warning.
methods in category have no difference compare to method in @implementation
regards to thread-safety.
Upvotes: 5