【问题标题】:Abusing Objective-C Shorthand Notation滥用 Objective-C 简写符号
【发布时间】:2013-08-27 23:18:50
【问题描述】:

除了让老派的 Objective-C 程序员心脏病发作之外,这样做还有其他性能影响吗:

NSMutableArray *derp = @[].mutableCopy

相对于这个:

NSMutableArray *derp = [[NSMutableArray alloc] init];

【问题讨论】:

  • 差点让我心脏病发作。
  • 我可能会把它留在里面,只是为了挑走追随我的弱者。
  • 几年前我停止了 Objective C 的工作。心脏病发作确认:)
  • 鉴于 Objective-C 滥用程序员的方式,我认为现在滥用它是公平的。
  • 另外,许多类已经实现了便利构造函数,在你的例子中:[NSMutableArray array]。据我所知,没有报告心脏病发作的病例。或者你可以使用new,它会给老Smalltalk程序员一种温暖而模糊的感觉。

标签: objective-c


【解决方案1】:

我将给出与下面不同的答案。

除非您测量了应用的性能并发现它有缺陷,并且发现该部分代码中没有正确的地方,否则您的优化是错误的。 (如果你这样做了,你可以很容易地衡量哪个更快。)

您应该针对代码的清晰度、编写速度以及正确的可能性进行优化。

在这方面,我更喜欢第二个代码 sn-p 而不是第一个。它几乎准确地说明了您要执行的操作。第一个 sn-p 不仅更难阅读;它还犯了使用点符号调用方法而不是获取属性值的样式错误。

再加上导致心脏病发作并不好。 :)

【讨论】:

    【解决方案2】:

    类簇没有有额外的分配,它与编译器魔法无关。 所以是的,您的示例之间存在显着差异(顺便说一句,点符号滥用为 -1000 个互联网点。)您的第一个示例有两个分配,第二个只有一个。 s>

    由于我们无法访问 NSArray 的实际源代码,我们可以查看 GNUStep(一个开源实现)来了解它们是如何处理它的。在 NSArray.m 中(简化并省略不相关的东西):

    static GSPlaceholderArray   *defaultPlaceholderArray;
    + (void) initialize
    {
        defaultPlaceholderArray = (GSPlaceholderArray*)
            NSAllocateObject(GSPlaceholderArrayClass, 0, NSDefaultMallocZone());
    }
    
    + (id) alloc
    {
        return defaultPlaceholderArray;
    }
    

    这里发生的事情是 NSArray 定义了一个单例占位符对象,它总是在 alloc 中返回。当在这个单例上调用 init 时,它会实例化正确的私有子类并返回它。

    那么,我们如何判断 Apple 的基金会是否也在做同样的事情呢?很简单,我们只运行这个测试:

    NSArray *a1 = [NSArray alloc];
    NSArray *a2 = [NSArray alloc];
    NSLog(@"%p, %p", a1, a2);
    
    > "0x100102f30, 0x100102f30"
    

    a1a2 确实具有相同的内存位置,这意味着 Apple 也可能使用单例方法。如果我们打印出类名,那就是__NSPlaceholderArray,这样就可以确认了。

    所以,是的,坚持[NSMutableArray new] :)

    更新: Greg Parker 指出@[] 也是一个单例,所以@[].mutableCopy 只导致一次分配。所以在性能方面,这两个例子是相同的。

    【讨论】:

      【解决方案3】:

      很难准确判断创建了多少对象,尤其是在数组字面量的情况下。

      The official docs of clang@[] 扩展为arrayWithObjects:count:,我怀疑它被实现为[[[self alloc] initWithObjects:objects count:count] autorelease]

      因此,此时可能会分配第二个对象,因为NSArrayclass cluster,而- [NSArray init] 的实现可能如下所示:

      - (id)init
      {
          [self release];
          self = [[__NSArrayI alloc] init];
          return self;
      }
      

      为了证实我的猜想,我编写了一个小程序,在不同阶段打印各种类型的NSArray 对象的类。 请注意,为了真正确定,有必要捕获NSArray 的所有分配和初始化方法。 在我们这样做之前,我们只能推测。但无论如何,这是代码:

      #import <Foundation/Foundation.h>
      
      
      int main()
      {
          NSArray *a = [NSArray alloc];
          NSLog(@"NSArray before init: %@", a.class);
      
          a = [a init];
          NSLog(@"NSArray after init: %@", a.class);
      
          NSArray *al = @[];
          NSLog(@"Array literal: %@", al.class);
      
          NSMutableArray *ma1 = [a mutableCopy];
          NSLog(@"NSMutableArray (copied): %@", ma1.class);
      
          NSMutableArray *ma2 = [NSMutableArray alloc];
          NSLog(@"NSMutableArray (manufactured) before init: %@", ma2.class);
      
          ma2 = [ma2 init];
          NSLog(@"NSMutableArray (manufactured) after init: %@", ma2.class);
      
          return 0;
      }
      

      这是它打印的内容(NSLog() 为清楚起见已删除杂乱):

      h2co3-macbook:~ h2co3$ ./quirk
      NSArray before init: __NSPlaceholderArray
      NSArray after init: __NSArrayI
      Array literal: __NSArrayI
      NSMutableArray (copied): __NSArrayM
      NSMutableArray (manufactured) before init: __NSPlaceholderArray
      NSMutableArray (manufactured) after init: __NSArrayM
      

      编辑:这里还有一些涉及挂钩的代码。结果非常有趣,但这会打印很多文本,因此鼓励您编译并运行它。结果是,基本上,初始化程序确实直接通过[NSArray init]

      #import <Foundation/Foundation.h>
      #import <objc/runtime.h>
      #import <objc/message.h>
      
      void hook(Class cls, SEL sel, IMP newimp, IMP *old)
      {
          Method m = class_getInstanceMethod(cls, sel);
          *old = method_setImplementation(m, newimp);
      }
      
      #define CLS(c) objc_getClass(#c)
      #define META(c) objc_getMetaClass(#c)
      
      IMP old_$_NSArray_$_alloc;
      IMP old_$_NSMutableArray_$_alloc;
      IMP old_$_NSPlaceholderArray_$_alloc;
      IMP old_$_NSArrayI_$_alloc;
      IMP old_$_NSArrayM_$_alloc;
      
      IMP old_$_NSArray_$_init;
      IMP old_$_NSMutableArray_$_init;
      IMP old_$_NSPlaceholderArray_$_init;
      IMP old_$_NSArrayI_$_init;
      IMP old_$_NSArrayM_$_init;
      
      id new_$_NSArray_$_alloc(id self, SEL _cmd)
      {
          printf("+ [NSArray<%p> alloc]\n", self);
          return old_$_NSArray_$_alloc(self, _cmd);
      }
      
      id new_$_NSMutableArray_$_alloc(id self, SEL _cmd)
      {
          printf("+ [NSMutableArray<%p> alloc]\n", self);
          return old_$_NSMutableArray_$_alloc(self, _cmd);
      }
      
      id new_$_NSPlaceholderArray_$_alloc(id self, SEL _cmd)
      {
          printf("+ [NSPlaceholderArray<%p> alloc]\n", self);
          return old_$_NSPlaceholderArray_$_alloc(self, _cmd);
      }
      
      id new_$_NSArrayI_$_alloc(id self, SEL _cmd)
      {
          printf("+ [NSArrayI<%p> alloc]\n", self);
          return old_$_NSArrayI_$_alloc(self, _cmd);
      }
      
      id new_$_NSArrayM_$_alloc(id self, SEL _cmd)
      {
          printf("+ [NSArrayM<%p> alloc]\n", self);
          return old_$_NSArrayM_$_alloc(self, _cmd);
      }
      
      id new_$_NSArray_$_init(id self, SEL _cmd)
      {
          printf("- [NSArray<%p> init]\n", self);
          return old_$_NSArray_$_init(self, _cmd);
      }
      
      id new_$_NSMutableArray_$_init(id self, SEL _cmd)
      {
          printf("- [NSMutableArray<%p> init]\n", self);
          return old_$_NSMutableArray_$_init(self, _cmd);
      }
      
      id new_$_NSPlaceholderArray_$_init(id self, SEL _cmd)
      {
          printf("- [NSPlaceholderArray<%p> init]\n", self);
          return old_$_NSPlaceholderArray_$_init(self, _cmd);
      }
      
      id new_$_NSArrayI_$_init(id self, SEL _cmd)
      {
          printf("- [NSArrayI<%p> init]\n", self);
          return old_$_NSArrayI_$_init(self, _cmd);
      }
      
      id new_$_NSArrayM_$_init(id self, SEL _cmd)
      {
          printf("- [NSArrayM<%p> init]\n", self);
          return old_$_NSArrayM_$_init(self, _cmd);
      }
      
      int main()
      {
          hook(META(NSArray), @selector(alloc), (IMP)new_$_NSArray_$_alloc, &old_$_NSArray_$_alloc);
          hook(META(NSMutableArray), @selector(alloc), (IMP)new_$_NSMutableArray_$_alloc, &old_$_NSMutableArray_$_alloc);
          hook(META(__NSPlaceholderArray), @selector(alloc), (IMP)new_$_NSPlaceholderArray_$_alloc, &old_$_NSPlaceholderArray_$_alloc);
          hook(META(__NSArrayI), @selector(alloc), (IMP)new_$_NSArrayI_$_alloc, &old_$_NSArrayI_$_alloc);
          hook(META(__NSArrayM), @selector(alloc), (IMP)new_$_NSArrayM_$_alloc, &old_$_NSArrayM_$_alloc);
      
          hook(CLS(NSArray), @selector(init), (IMP)new_$_NSArray_$_init, &old_$_NSArray_$_init);
          hook(CLS(NSMutableArray), @selector(init), (IMP)new_$_NSMutableArray_$_init, &old_$_NSMutableArray_$_init);
          hook(CLS(NSPlaceholderArray), @selector(init), (IMP)new_$_NSPlaceholderArray_$_init, &old_$_NSPlaceholderArray_$_init);
          hook(CLS(NSArrayI), @selector(init), (IMP)new_$_NSArrayI_$_init, &old_$_NSArrayI_$_init);
          hook(CLS(NSArrayM), @selector(init), (IMP)new_$_NSArrayM_$_init, &old_$_NSArrayM_$_init);
      
      
          NSArray *a = [NSArray alloc];
          NSLog(@"NSArray before init: %@<%p>", a.class, a);
      
          a = [a init];
          NSLog(@"NSArray after init: %@<%p>", a.class, a);
      
          NSArray *al = @[];
          NSLog(@"Array literal: %@<%p>", al.class, al);
      
          NSMutableArray *ma1 = [a mutableCopy];
          NSLog(@"NSMutableArray (copied): %@<%p>", ma1.class, ma1);
      
          NSMutableArray *ma2 = [NSMutableArray alloc];
          NSLog(@"NSMutableArray (manufactured) before init: %@<%p>", ma2.class, ma2);
      
          ma2 = [ma2 init];
          NSLog(@"NSMutableArray (manufactured) after init: %@<%p>", ma2.class, ma2);
      
          return 0;
      }
      

      【讨论】:

      • 你怎么知道类簇没有覆盖alloc?
      • 除非有一些关于永远不会覆盖 alloc 的政策,否则类集群完全有可能在那里分配正确的类型,从而导致一次分配来创建一个 NSArray
      • +[NSArray allocWithZone:] 有时会返回一个单例对象。
      【解决方案4】:

      是的,一个是分配 2 个对象,另一个不是。我做[NSMutableArray new]

      更新:Greg Parker 是对的,两者都只分配一个对象。

      【讨论】:

      • 不会马上释放第一个数组吗?
      • @LukeTheObscure 它会,但它仍然必须被分配。繁荣,你的紧循环现在需要两倍的时间来运行。
      • 嗯,实际上...第二个版本也分配了两个对象。 NSMutableArray 是一个类簇。它的init 方法类似于[self release]; self = [[__NSArrayM alloc] init]; return self;
      • @Eiko:我认为它不需要返回不同的对象,但我也不相信任何编译器中都存在任何此类优化。我也不会打赌它会被添加——它在现实世界或基准测试中几乎没有任何好处。
      • 在当前的操作系统版本中,[[NSMutableArray alloc] init]@[].mutableCopy 都只分配一个对象。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 2013-06-17
      • 2020-02-23
      • 2011-10-26
      • 1970-01-01
      • 2011-11-19
      • 1970-01-01
      相关资源
      最近更新 更多