简体   繁体   中英

Why does my NSMutableString edit sometimes not work?

I'm trying to repair some mis-numbered movie subtitle files (each sub is separated by a blank line). The following code scans up to the faulty subtitle index number in a test file. If I just 'printf' the faulty old indices and replacement new indices, everything appears just as expected.

//######################################################################
-(IBAction)scanToSubIndex:(id)sender
{
    NSMutableString* tempString = [[NSMutableString alloc] initWithString:[theTextView string]];
    int textLen = (int)[tempString length];
    
    NSScanner *theScanner = [NSScanner scannerWithString:tempString];
    while ([theScanner isAtEnd] == NO)
        {
        [theScanner scanUpToString:@"\r\n\r\n" intoString:NULL];
        [theScanner scanString:@"\r\n\r\n" intoString:NULL];
        
        if([theScanner scanLocation] >= textLen)
            break;
        else
            { // remove OLD subtitle index...
            NSString *oldNumStr;
            [theScanner scanUpToString:@"\r\n" intoString:&oldNumStr];
printf("old number:%s\n", [oldNumStr UTF8String]);                
            NSRange range = [tempString rangeOfString:oldNumStr];
                [tempString deleteCharactersInRange:range];

            // ...and insert SEQUENTIAL index
            NSString *newNumStr = [self changeSubIndex];
printf("new number:%s\n\n", [newNumStr UTF8String]);
                [tempString insertString:newNumStr atIndex:range.location];
            }
        }
printf("\ntempString\n\n:%s\n", [tempString UTF8String]);
}

//######################################################################
-(NSString*)changeSubIndex
{
    static int newIndex = 1;
    // convert int to string and return...
    NSString *numString = [NSString stringWithFormat:@"%d", newIndex];
        ++newIndex;
    
return numString;
}

When I attempt to write the new indices to the mute string however, I end up with disordered results like this:

sub 1

sub 2

sub 3

sub 1

sub 5

sub 6

sub 7

sub 5

sub 9

sub 7

sub 8

An interesting observation (and possible clue?) is that when I reach subtitle number 1000 , every number gets written to the mutable string in sequential order as required. I've been struggling with this for a couple of weeks now, and I can't find any other similar questions on SO. Any help much appreciated:-)

NSScanner & NSMutableString

NSMutableString is a subclass of NSString . In other words, you can pass NSMutableString at places where the NSString is expected. But it doesn't mean you're allowed to modify it.

scannerWithString: expects NSString . Translated to human language - I expect a string and I also do expect that the string is read-only (wont be modified).

In other words - your code is considered to be a programmer error - you give something to the NSScanner , NSScanner expects immutable string and you're modifying it.

We don't know what the NSScanner class is doing under the hood. There can be buffering or any other kind of optimization.

Even if you will be lucky with the mentioned scanLocation fix (in the comments), you shouldn't rely on it, because the under the hood implementation can change with any new release.

Don't do this. Not just here, but everywhere where you see immutable data type.

(There're situations where you can do it, but then you should really know what the under the hood implementation is doing, be certain that it wont be modified, etc. But generally speaking, it's not a good idea unless you know what you're doing.)

Sample

This sample code is based on the following assumptions:

  • we're talking about SubRip Text (SRT)
  • file is small (can easily fit memory)
  • rest of the SRT file is correct
    • especially the delimiter ( @"\r\n" )
@import Foundation;

NS_ASSUME_NONNULL_BEGIN

@interface SubRipText : NSObject

+ (NSString *)fixSubtitleIndexes:(NSString *)string;

@end

NS_ASSUME_NONNULL_END
@implementation SubRipText

+ (NSString *)fixSubtitleIndexes:(NSString *)string {
    NSMutableString *result = [@"" mutableCopy];
    
    __block BOOL nextLineIsIndex = YES;
    __block NSUInteger index = 1;
    
    [string enumerateLinesUsingBlock:^(NSString * _Nonnull line, BOOL * _Nonnull stop) {
        if (nextLineIsIndex) {
            [result appendFormat:@"%lu\r\n", (unsigned long)index];
            index++;
            nextLineIsIndex = NO;
            return;
        }
        
        [result appendFormat:@"%@\r\n", line];
        
        nextLineIsIndex = line.length == 0;
    }];
    
    return result;
}

@end

Usage:

NSString *test = @"29\r\n"
"00:00:00,498 --> 00:00:02,827\r\n"
"Hallo\r\n"
"\r\n"
"4023\r\n"
"00:00:02,827 --> 00:00:06,383\r\n"
"This is two lines,\r\n"
"subtitles rocks!\r\n"
"\r\n"
"1234\r\n"
"00:00:06,383 --> 00:00:09,427\r\n"
"Maybe not,\r\n"
"just learn English :)\r\n";

NSString *result = [SubRipText fixSubtitleIndexes:test];

NSLog(@"%@", result);

Output:

1
00:00:00,498 --> 00:00:02,827
Hallo

2
00:00:02,827 --> 00:00:06,383
This is two lines,
subtitles rocks!

3
00:00:06,383 --> 00:00:09,427
Maybe not,
just learn English :)

There're other ways how to achieve this, but you should think about readability, speed of writing, speed of running, ... Depends on your usage - how many of them are you going to fix, etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM