Aaron Brager
Aaron Brager

Reputation: 66302

Thread safety in Objective-C categories

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.

  1. Is my conclusion about this method's thread safety correct?
  2. 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.)

  3. 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

Answers (2)

Ken Thomases
Ken Thomases

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

Bryan Chen
Bryan Chen

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

Related Questions