【问题标题】:How to customise description of NSArray?如何自定义 NSArray 的描述?
【发布时间】:2015-11-08 12:51:14
【问题描述】:

我想为 NSArray 类的description 方法提供我自己的实现,这样我就可以像这样简单地使用它:

NSLog(@"%@", @[]);

我的想法是为NSArray 提供一个类别并简单地覆盖description 那里的方法。但是它不起作用,因为NSArray 是一个类集群,它的真正类是__NSArrayI,所以我的类别实现永远不会被调用。不幸的是,我无法为__NSArrayI 提供类别,因为此类不可用。

当然,我可以只继承NSArray 并在我的子类中实现这个方法,但同样,由于NSArray 是一个类集群,我必须为一堆不同的方法提供一个实现,比如objectAtIndex: 和我不想这样做,因为这对于简单地更改数组打印到控制台的方式来说工作量太大。

各位,你们有什么想法吗?谢谢

【问题讨论】:

  • 您已经大致了解了选项和缺点。我认为没有其他选择。我想你可以混搭,但你可能不得不忍受不完整的覆盖,除非你能详尽地覆盖所有类集群成员。记住 NSMutableArray 也是一个类集群 :)
  • 如果您想为特定设计中的特定数组执行此操作,那么编写一个方法/函数来生成数组所有者中的描述可能是最简单的,无论是 ViewController 还是数组的包装器对象,或其他一些旨在描述此集合的特殊类 -(NSString )describeMyArray:(NSArray)array{...}
  • 为什么要提供自定义description?目标是什么?对于这个问题,可能有比在基础集合类中添加功能更好的解决方案。

标签: objective-c cocoa nsarray


【解决方案1】:

各位,你们有什么想法吗?谢谢

我们得到的想法。解决方案...您必须做出决定。

documentation about format specifiers,您不能只担心description。具体来说,从那个文件...

Objective-C 对象,打印为返回的字符串 descriptionWithLocale:如果可用,否则描述。还 使用 CFTypeRef 对象,返回结果 CFCopyDescription 函数。

除非我们想用链接器和/或动态加载器破解,否则我们无法对 CFTypeRef 对象做太多事情。

但是,我们可以对descriptiondescriptionWithLocale:一些事情,尽管这有点麻烦。

您也可以考虑debugDescription

这是实现目标的一种方式,尽管我认为它“具有教育意义”,您应该根据自己的最佳判断来决定是否要走这条路。

首先,您需要确定您的替换 description 实现会是什么样子。我们将像这样声明description 的简单替换(忽略原始实现)。

static NSString * swizzledDescription(id self, SEL _cmd)
{
    NSUInteger count = [self count];
    NSMutableString *result = [NSMutableString stringWithFormat:@"Array instance (%p) of type %@ with %lu elements", (void*)self, NSStringFromClass([self class]), (unsigned long)count];
    int fmtLen = snprintf(0,0,"%lu",count);
    for (NSUInteger i = 0; i < count; ++i) {
        [result appendFormat:@"\n%p: %*lu: %@", (void*)self, fmtLen, i, self[i]];
    }
    return result;
}

还有一个更简单的descriptionWithLocale: 实现,它完全忽略了语言环境。

static NSString * swizzledDescriptionWithLocale(id self, SEL _cmd, id locale) {
    return swizzledDescription(self, _cmd);
}

现在,我们如何让NSArray 实现使用这段代码?一种方法是找到NSArray 的所有子类并替换它们的方法...

static void swizzleMethod(Class class, SEL selector, IMP newImp) {
    Method method = class_getInstanceMethod(class, selector);
    if (method) {
        IMP origImp = method_getImplementation(method);
        if (origImp != newImp) {
            method_setImplementation(method, newImp);
        }
    }
}

static void swizzleArrayDescriptions() {
    int numClasses = objc_getClassList(NULL, 0);
    if (numClasses <= 0) return;
    Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
    numClasses = objc_getClassList(classes, numClasses);

    Class target = [NSArray class];
    for (int i = 0; i < numClasses; i++) {
        for (Class c = classes[i]; c; c = class_getSuperclass(c)) {
            if (c == target) {
                c = classes[i];
                swizzleMethod(c, @selector(description), (IMP)swizzledDescription);
                swizzleMethod(c, @selector(descriptionWithLocale:), (IMP)swizzledDescriptionWithLocale);
                break;
            }
        }
    }
    free(classes);
}

调用swizzleArrayDescriptions 的合理位置是在您的应用委托的+initialize 方法中。

@implementation AppDelegate

+ (void)initialize {
    if (self == [AppDelegate class]) {
        swizzleArrayDescriptions();
    }
}

现在,你应该可以玩它了,看看你是怎么相处的。

作为一个非常简单的测试...

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
    NSArray *array = @[@"One", @"Two", @3, @"4", @"FIVE", @(6.0), @".7.", @8, @9, @10, @"Eleven" ];
    NSLog(@"%@", array);
    NSLog(@"%@", [array mutableCopy]);
}

产生这个输出...

2015-11-08 14:30:45.501 TestApp[72183:25861219] Array instance (0x6000000c5780) of type __NSArrayI with 11 elements
0x6000000c5780:  0: One
0x6000000c5780:  1: Two
0x6000000c5780:  2: 3
0x6000000c5780:  3: 4
0x6000000c5780:  4: FIVE
0x6000000c5780:  5: 6
0x6000000c5780:  6: .7.
0x6000000c5780:  7: 8
0x6000000c5780:  8: 9
0x6000000c5780:  9: 10
0x6000000c5780: 10: Eleven
2015-11-08 14:30:45.501 TestApp[72183:25861219] Array instance (0x600000045580) of type __NSArrayM with 11 elements
0x600000045580:  0: One
0x600000045580:  1: Two
0x600000045580:  2: 3
0x600000045580:  3: 4
0x600000045580:  4: FIVE
0x600000045580:  5: 6
0x600000045580:  6: .7.
0x600000045580:  7: 8
0x600000045580:  8: 9
0x600000045580:  9: 10
0x600000045580: 10: Eleven

当然,你应该比我做更多的测试,因为我所做的只是在教堂结束后修改它,因为它看起来有些有趣(正在下雨,所以野餐被取消了)。

如果您需要调用原始实现,则需要创建原始实现的缓存,以类为键,并相应地调用它们。但是,对于这样的情况,您想要修改返回的字符串,您可能不需要这样做,而且无论如何都应该是直截了当的。

另外,请注意关于 swizzling 的一般注意事项,在使用类集群时它们会更加突出。

注意,您也可以这样做以在运行时创建自定义子类。您甚至可以以正常方式将您的子类定义为 NSArray 的直接子类,然后在不影响任何不属于您的类的情况下调整它们的类型......或一堆不同的东西......记住 ObjectiveC 运行时是你的朋友。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-06
    • 1970-01-01
    • 1970-01-01
    • 2010-10-09
    • 1970-01-01
    • 2010-12-29
    • 2019-10-02
    相关资源
    最近更新 更多