在绕过allocWithZone的覆盖(子类)版本的同时访问NSObject的(超类)版本的allocWithZone

时间:2013-10-18 16:25:57

标签: objective-c

在Big Nerd Ranch(第3版)的iOS编程书中,他们在第194页上说 知识渊博的程序员仍然可以通过allocWithZone:创建一个BNRItemStore实例,它可以绕过我们偷偷摸摸的alloc trap。为了防止这种可能性,在BNRItemStore.m中覆盖allocWithZone:返回单个BNRItemStore实例。

+(id) allocWithZone:(NSZone *)zone
{
    return [self sharedStore];
}

这句话似乎让我感到困惑。以下代码不能以某种方式证明这是错误的 -

#import <Foundation/Foundation.h>

@interface BNRItemStore : NSObject
+(BNRItemStore *)sharedStore;
+(id)retrieveObject;
@end
@implementation BNRItemStore
+(BNRItemStore *)sharedStore{
    static BNRItemStore *sharedStore=nil;
    if (!sharedStore){
        NSLog(@"Test2");
        sharedStore= [[super allocWithZone:nil] init];
    }
    NSLog(@"sharedStore-> %@",sharedStore);
    return sharedStore;
}
+(id)allocWithZone:(NSZone *)zone{
    NSLog(@"Test1");
    return [self sharedStore];
}
+(id)alloc{
    NSLog(@"Retrieving super object");
    NSLog(@"%@", [super allocWithZone:nil]);//Bypassing the subclass version of    allocWithZone.
    return [super allocWithZone:nil];
}
@end

int main(){
    [[BNRItemStore alloc] init]; //the alloc message triggers a call to the subclass  (overriding) version of +(id)alloc method
}

输出结果为:

  • 2013-10-18 18:24:40.132 BNRItemStore [381:707]检索超级对象
  • 2013-10-18 18:24:40.134 BNRItemStore [381:707] BNRItemStore:0x7f8c72c091e0

如果子类'alloc'方法内的调用[super allocWithZone:nil]会触发对子类allocWithZone的调用,则控制台将记录“Test1”和“Test2”,最后会导致静态指针被分配。但这并没有发生。 这意味着如果我们直接调用[NSObject allocWithZone:nil]或[super allocWithZone:nil],该消息将不会重定向到allocWithZone的重写版本(子类版本),但会直接访问NSAllocateObject()函数,该函数执行实际操作分配。 NSObject中的+(id)allocWithZone代码必须看起来像这样 -

+(id)allocWithZone:(NSZone *)zone{
    return NSAllocateObject();
}

如果这个实现(NSObject的allocWithZone :)包含类似[self allocWithZone]的东西,则消息调度机制将包含allocWithZone的子类版本,这将使我们通过涉及调用sharedStore方法的“偷偷摸摸”陷阱。以下就是我所说的情况。现在,如果是这种情况,代码肯定会有无限循环。很明显,情况并非如此。

+(id)allocWithZone:(NSZone *)zone{
    if([self allocWithZone:zone])      //this would trigger a call to subclass ver. which would call sharedStore method which would then have [super allocWithZone:nil].Infinite Loop
    return NSAllocateObject();
}

所以有人可以清除这个所谓的“鬼鬼祟祟”陷阱的查询。该陷阱是否意味着阻止任何人单独实例化。即不能使用NSObject的allocWithZone,除非在sharedStore方法内部?请澄清..

2 个答案:

答案 0 :(得分:1)

这里第一个也是最重要的一课是你不应该覆盖+allocWithZone:。我知道BNR书描述了它(而且BNR书一般都非常好)。你不应该这样做。我知道Apple包含了一些代码来实现它。你不应该这样做。 (并且Apple在解释中注意到很少需要这个。)单身人士应该使用dispatch_once pattern创建。

您没有提供初始代码,但我怀疑他们的示例代码会覆盖alloc,但不会覆盖allocWithZone:。他们只是说,如果来电者使用allocWithZone:,则不会通过alloc,因此他们也会覆盖alloc来捕获它。 (当然,正确的答案只是覆盖allocWithZone:而不是 alloc。但在任何情况下都不应该覆盖这些方法。)


编辑:

我相信你误解了“我们偷偷摸摸的分配陷阱”在这里意味着什么。作者在文本的这一点上假设了以下代码:

@interface BNRItemStore : NSObject
+(BNRItemStore *)sharedStore;
@end

@implementation BNRItemStore   
+(BNRItemStore *)sharedStore{
    static BNRItemStore *sharedStore=nil;
    if (!sharedStore){
        sharedStore = [[super allocWithZone:nil] init];
    }
    return sharedStore;
}
@end

就是这样;根本没有+alloc覆盖。然后它指出“要强制执行单例状态......您必须确保无法分配另一个BNRItemStore实例。” (*)

作者继续建议我们可以通过覆盖+alloc来强制执行单身人士状态,但会立即注意到这是不够的,因为调用者可以使用+allocWithZone:。由于记录了[NSObject alloc]调用[self allocWithZone:],因此覆盖+allocWithZone:并且不必要且不足以覆盖+alloc是必要且充分的。

您在代码中所做的工作证明您可以修改BNRItemStore以致电[super allocWithZone:]中的+alloc。这不是重点。如果您可以修改BNRItemStore,也可以将其设为非单身人士。关键是外部调用者(在您的情况下为main())是否可以绕过单例实例化,而她不能。 (**)

(*)在这一点上,并且可能应该做的一点是,当呼叫者要求你分配一个新的时候,通过悄悄地返回一个单身来“强制执行单身人士状态”通常是一个坏主意。宾语。如果您需要来强制执行单例状态,则最好通过init中的断言来执行此操作,因为第二次分配的请求表示编程错误。也就是说,有时候不可变对象的“透明”单例可能因性能原因而有用,例如特殊单例NSNumber提供某些常见整数,并且这种技术在这些情况下是合适的。 (通过“透明”,我的意思是单例是调用者不应该担心的实现细节。这至少假定对象是不可变的。)

(**)如果她决定这样做,她实际上可以。她总是可以自己拨打NSAllocateObject(),完全绕过+alloc,然后拨打-init。这当然是疯了,没有理由“保护”她自己做这件事。 SDK的工作不是保护自己免受呼叫者的侵害。只有SDK的工作才能保护呼叫者免受可能的错误。呼叫者永远不是敌人。

答案 1 :(得分:0)

我不确定这是否能完全回答你的问题,但当天使用“allocWithZone:”来分区分配的内存。从那时起,苹果已经摆脱了这个概念,并希望将所有内容分配到同一个堆空间中。 “allocWithZone:”甚至不像以前那样运作,苹果特别表示不使用它。