Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is causing a SIGSEGV using blocks?

I have the following code. I get a SIGSEGV occasionally. I have a feeling I'm missing something regarding memory management using blocks. Is it safe to pass the replacedUrls, which is autoreleased to this block? What about modifying the instance variable formattedText?

    NSMutableSet* replacedUrls = [[[NSMutableSet alloc] init] autorelease];

    NSError *error = nil; 
    NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:
                                (NSTextCheckingTypeLink | NSTextCheckingTypePhoneNumber)
                                                               error:&error];
    if (error) {
        return;
    }

    [detector enumerateMatchesInString:self.formattedText 
              options:0 
              range:NSMakeRange(0, [self.formattedText length]) 
              usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {

            @try  {
                if (result.resultType == NSTextCheckingTypePhoneNumber) {

                    if (!result.phoneNumber) {
                        // not sure if this is possible
                        return;
                    }

                    self.formattedText = [self.formattedText stringByReplacingOccurrencesOfString:result.phoneNumber
                                                                                       withString:[NSString stringWithFormat:@"<a href=\"tel://%@\">%@</a>", result.phoneNumber, result.phoneNumber]];
                }
                else if (result.resultType == NSTextCheckingTypeLink) {

                    if (!result.URL) {
                        // not sure if this is possible
                        return;
                    }

                    NSString* fullUrl = [result.URL absoluteString];

                    if (!fullUrl) {
                        return; 
                    }

                    if ([replacedUrls containsObject:fullUrl]) {
                        return; 
                    }

                    // not sure if this is possible
                    if ([result.URL host] && [result.URL path]) {
                        NSString* urlWithNoScheme = [NSString stringWithFormat:@"%@%@", [result.URL host], [result.URL path]];

                        // replace all http://www.google.com to www.google.com
                        self.formattedText = [self.formattedText stringByReplacingOccurrencesOfString:fullUrl
                                                                                           withString:urlWithNoScheme];

                        // replace all www.google.com with http://www.google.com
                        NSString* replaceText = [NSString stringWithFormat:@"<a href=\"%@\">%@</a>", fullUrl, fullUrl];
                        self.formattedText = [self.formattedText stringByReplacingOccurrencesOfString:urlWithNoScheme
                                                                                           withString:replaceText];

                        [replacedUrls addObject:fullUrl];
                    }
                }
            }
            @catch (NSException* ignore) {
                // ignore any issues
            }
        }];
like image 337
tjg184 Avatar asked Oct 01 '11 02:10

tjg184


1 Answers

It seems that the issue you are experiencing is one related to memory management. You begin by searching through the string self.formattedText. This means that, while this search takes place, your NSDataDetector instance probably needs to access the string to read characters, etc. This works all fine and nice, as long as self.formattedText doesn't get deallocated. Usually, even for block methods like this, it is the caller's responsibility to retain the arguments until the end of the function call.

When, inside of your match found block, you change the value of self.formattedText, the old value is automatically released (assuming that this is a retain property). I am not aware of caching that NSDataDetector might do, or issues pertaining to autorelease pools, etc., but I am pretty certain that this could cause an issue.

my suggestion is that you pass [NSString stringWithString:self.formattedText] as the enumerateMatchesInString: argument, rather than the plain self.formattedText. This way, you pass the NSDataDetector an instance that won't be released until the autorelease pool is drained.

like image 90
Alex Nichol Avatar answered Nov 03 '22 01:11

Alex Nichol