如何使用ARC优化Objective-C单例?

时间:2014-02-18 07:26:34

标签: objective-c performance singleton automatic-ref-counting

在Objective-C中使用单例的标准模式,ARC仍会在每次使用单例时自动生成保留和释放调用,即使我们知道该对象永远不会被释放。在对性能敏感的代码中,这些ARC生成的调用会导致大量额外开销。有没有办法告诉编译器不要为单例生成保留/释放代码?

就我而言,我正在编写一个对性能敏感的日文文本解析器。作为解析的一部分,我经常需要访问一个汉字字符的NSCharacterSet,我在NSCharacterSet的类别中定义了它。

+ (id)kanjiCharacterSet
{
    static NSCharacterSet* kanjiCharacterSet = nil;
    static dispatch_once_t onceToken;

    dispatch_once( &onceToken, ^
    {
        NSRange range = { .location = 0x4e00, .length = 0x9faf - 0x4e00 };
        kanjiCharacterSet = [NSCharacterSet characterSetWithRange:range];
    } );

    return kanjiCharacterSet;
}

我访问此字符集的最常见位置之一是检查字符串中是否存在汉字字符。此代码位于NSString上的一个类别中。

- (BOOL)containsKanji
{
    return [self rangeOfCharacterFromSet:[NSCharacterSet kanjiCharacterSet]].location != NSNotFound;
}

当我通过分析器运行时,[NSString containsKanji]中花费的整个时间的大约40%都在保留/释放代码中。对于[NSCharacterSet kanjiCharacterSet],排除我们实际生成字符集的第一个调用,每个调用的大约80%用于保留/释放代码。

如果我在objc_retainobj_releaseobjc_autorelease中设置了断点,我可以看到一个保留和一个自动释放被添加到[NSCharacterSet kanjiCharacterSet]的return语句中,当[NSString containsKanji]收到[NSCharacterSet kanjiCharacterSet]返回的值时,会在[NSString containsKanji]中添加一个保留,当我们从[NSString containsKanji]返回时,会添加一个版本。似乎所有这些调用对于单身人士来说都是不必要的。有没有办法告诉编译器不要生成这些调用?

我找到了一个解决方法,即向{{1}}添加一个静态变量来存储字符集,但我很想找到更通用的解决方案。

3 个答案:

答案 0 :(得分:1)

您没有说明-containsKanji中总时间的百分比是多少,这是您应该优化该例程的唯一相关指标。如果它是,例如,总时间的1%,那么它是否花费所有时间保留或旋转或做菜都没关系。

如果 实际上是你的大时间同步,你应该停止使用该方法中的对象,有点......这是Objective-C的一大优势,我们可以写低 - 我们的高级代码中间的级别代码。

在你的情况下,由于汉字从0x4e00到0x9faf,我只想写下这样的东西:

- (BOOL)containsKanji;
{
    const NSUInteger bufferSize = 256;
    unichar buffer[bufferSize];

    for (NSUInteger characterIndex = 0; characterIndex < self.length; characterIndex += bufferSize) {
        const NSUInteger charactersToFetch = MIN(bufferSize, self.length - characterIndex);
        [self getCharacters:buffer range:(NSRange){characterIndex, charactersToFetch}];

        for (NSUInteger checkCharacterIndex = 0; checkCharacterIndex < charactersToFetch; checkCharacterIndex++) {
            unichar characterToCheck = buffer[characterIndex + checkCharacterIndex];
            if (characterToCheck >= 0x4e00 && characterToCheck <= 0x9faf)
                return YES;
        }
    }
    return NO;
}

注意我还没有编译这段代码,因此您可能需要修复拼写错误等。

答案 1 :(得分:1)

我通过在方法声明中添加__attribute__((ns_returns_retained))找到了部分解决方案:

+ (id)kanjiCharacterSet __attribute__((ns_returns_retained));

根据documentation,这会强制编译器处理方法的方式与处理以alloccopyinit,{mutableCopy开头的方法相同。 {1}}或new。它只生成两个:而不是四个保留/释放相关的调用:[NSCharacterSet kanjiCharacterSet]中的单个保留和调用代码中的单个释放。

答案 2 :(得分:1)

在重复调用kanjiCharacterSet的方法中,缓存副本。显然,只有在性能至关重要时才应该做这种事情。

- (BOOL)containsKanji
{
    static NSCharacterSet* cachedKanji = nil;
    static dispatch_once_t onceToken;

    dispatch_once (&onceToken, ^{
        cachedKanji = [NSCharacterSet kanjiCharacterSet];
    });

    return [self rangeOfCharacterFromSet: cachedKanji].location != NSNotFound;
}