Is this a sane Objective-C Block Implementation?

2019-02-10 22:04发布

I wanted a variation of NSRegularExpression's – stringByReplacingMatchesInString:options:range:withTemplate: method that takes a block instead of a template. The return value of the block would be used as the replacement value. This is more flexible than a template, as you can imagine. Sort of like using the /e modifier in Perl regular expressions.

So I wrote a category to add the method. This is what I came up with:

@implementation NSRegularExpression (Block)

- (NSString *)stringByReplacingMatchesInString:(NSString *)string
                                       options:(NSMatchingOptions)options
                                         range:(NSRange)range
                                    usingBlock:(NSString* (^)(NSTextCheckingResult *result))block
{
    NSMutableString *ret = [NSMutableString string];
    NSUInteger pos = 0;

    for (NSTextCheckingResult *res in [self matchesInString:string options:options range:range]) {
        if (res.range.location > pos) {
            [ret appendString:[string substringWithRange:NSMakeRange(pos, res.range.location - pos)]];
        }
        pos = res.range.location + res.range.length;
        [ret appendString:block(res)];
    }
    if (string.length > pos) {
        [ret appendString:[string substringFromIndex:pos]];
    }
    return ret;
}

@end

This is my first attempt to play with blocks in Objective C. It feels a little weird, but it seems to work well. I have a couple of questions about it, though:

  1. Does this seem like a sane way to implement such a method?
  2. Is there some way to implement its internals using -enumerateMatchesInString:options:range:usingBlock: ? I tried it, but could not assign to pos from within the block. But if there was a way to make it work, it'd be cool to also pass the NSMatchingFlags and BOOL and handle them in the same way as that method. Do-able?

Update

Thanks to the answer from Dave DeLong, I've got a new version using a block:

@implementation NSRegularExpression (Block)

- (NSString *)stringByReplacingMatchesInString:(NSString *)string
                                       options:(NSMatchingOptions)options
                                         range:(NSRange)range
                                    usingBlock:(NSString * (^)(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop))block
{
    NSMutableString *ret = [NSMutableString string];
    __block NSUInteger pos = 0;

    [self enumerateMatchesInString:string options:options range:range usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop)
    {
        if (match.range.location > pos) {
            [ret appendString:[string substringWithRange:NSMakeRange(pos, match.range.location - pos)]];
        }
        pos = match.range.location + match.range.length;
        [ret appendString:block(match, flags, stop)];
    }];
    if (string.length > pos) {
        [ret appendString:[string substringFromIndex:pos]];
    }
    return [NSString stringWithString:ret];
}

@end

Works great, thanks!

1条回答
太酷不给撩
2楼-- · 2019-02-10 22:59

Being able to assign to pos from within the block would be as simple as changing the declaration from:

NSUInteger pos = 0;

To:

__block NSUInteger pos = 0;

More info on the __block keyword: __block Variables

查看更多
登录 后发表回答