如何自定义NSArray的描述?

时间:2015-11-08 12:51:15

标签: objective-c cocoa nsarray

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

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

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

当然,我可以继承NSArray并在我的子类中实现此方法,但同样,因为NSArray是一个类集群,我必须为一堆不同的方法提供实现,比如objectAtIndex:而且我不想这样做,因为这对于简单地改变阵列打印到控制台的方式来说太过分了。

你有什么想法,伙计们?谢谢

1 个答案:

答案 0 :(得分:2)

  

你有什么想法,伙计们?感谢

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

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

当然,你应该做比我更多的测试,因为我所做的一切都是在教堂之后进行的,因为它似乎有点有趣(它正在下雨所以野餐被取消了)。

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

另外,请注意关于调配的正常注意事项,并且在玩类集群时它们会更加明显。

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