如何自定义NSArray的描述?
How to customise description of NSArray?
我想为 NSArray class 的 description
方法提供我自己的实现,这样我就可以像这样简单地使用它:
NSLog(@"%@", @[]);
我的想法是为 NSArray
提供一个类别并简单地覆盖那里的 description
方法。但是它不起作用,因为 NSArray
是一个 class 集群,而它真正的 class 是 __NSArrayI
,所以我的类别实现从未被调用。很遗憾,我无法为 __NSArrayI
提供类别,因为此 class 不可用。
当然,我可以只使用 subclass NSArray
并在我的 subclass 中实现这个方法,但是同样,因为 NSArray
是一个 class cluster 我必须为一堆不同的方法提供一个实现,比如 objectAtIndex:
我不想这样做,因为这对于简单地改变数组打印到控制台的方式来说工作太多了。
伙计们,你们有什么想法吗?谢谢
Do you have any ideas, guys? Thanks
我们有想法。解决方案......你必须决定。
从documentation about format specifiers开始,你不能只担心description
。具体来说,从该文件...
Objective-C object, printed as the string returned by
descriptionWithLocale: if available, or description otherwise. Also
works with CFTypeRef objects, returning the result of the
CFCopyDescription function.
除非我们想破解链接器 and/or 动态加载器,否则我们对 CFTypeRef
对象无能为力。
然而,我们可以对 description
和 descriptionWithLocale:
做一些 的事情,虽然这有点粗糙。
您可能还想考虑 debugDescription
。
这是实现目标的一种方法,尽管我认为它 "educational",您应该根据自己的最佳判断来决定是否要走这条路。
首先,您需要确定替代 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
的所有子 classes 并替换它们的方法...
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
当然,你应该比我做更多的测试,因为我所做的只是在教堂之后把它搞砸了,因为它看起来有点有趣(下雨所以野餐被取消了)。
如果需要调用原始实现,则需要创建原始实现的缓存,以 class 为键,并相应地调用它们。但是,对于像这种情况下您想要修改返回的字符串,您可能不需要这样做,并且无论如何都应该是直接的。
此外,请注意关于 swizzling 的一般注意事项,在使用 class 集群时它们会更加严重。
注意,你也可以做类似的事情来在运行时创建自定义子classes。您甚至可以以正常方式将子 class 定义为 NSArray
的直接子 class ,然后在不影响任何 class 的情况下调整它们的类型不是你的……或者一堆不同的东西……记住 ObjectiveC 运行时是你的朋友。
我想为 NSArray class 的 description
方法提供我自己的实现,这样我就可以像这样简单地使用它:
NSLog(@"%@", @[]);
我的想法是为 NSArray
提供一个类别并简单地覆盖那里的 description
方法。但是它不起作用,因为 NSArray
是一个 class 集群,而它真正的 class 是 __NSArrayI
,所以我的类别实现从未被调用。很遗憾,我无法为 __NSArrayI
提供类别,因为此 class 不可用。
当然,我可以只使用 subclass NSArray
并在我的 subclass 中实现这个方法,但是同样,因为 NSArray
是一个 class cluster 我必须为一堆不同的方法提供一个实现,比如 objectAtIndex:
我不想这样做,因为这对于简单地改变数组打印到控制台的方式来说工作太多了。
伙计们,你们有什么想法吗?谢谢
Do you have any ideas, guys? Thanks
我们有想法。解决方案......你必须决定。
从documentation about format specifiers开始,你不能只担心description
。具体来说,从该文件...
Objective-C object, printed as the string returned by descriptionWithLocale: if available, or description otherwise. Also works with CFTypeRef objects, returning the result of the CFCopyDescription function.
除非我们想破解链接器 and/or 动态加载器,否则我们对 CFTypeRef
对象无能为力。
然而,我们可以对 description
和 descriptionWithLocale:
做一些 的事情,虽然这有点粗糙。
您可能还想考虑 debugDescription
。
这是实现目标的一种方法,尽管我认为它 "educational",您应该根据自己的最佳判断来决定是否要走这条路。
首先,您需要确定替代 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
的所有子 classes 并替换它们的方法...
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
当然,你应该比我做更多的测试,因为我所做的只是在教堂之后把它搞砸了,因为它看起来有点有趣(下雨所以野餐被取消了)。
如果需要调用原始实现,则需要创建原始实现的缓存,以 class 为键,并相应地调用它们。但是,对于像这种情况下您想要修改返回的字符串,您可能不需要这样做,并且无论如何都应该是直接的。
此外,请注意关于 swizzling 的一般注意事项,在使用 class 集群时它们会更加严重。
注意,你也可以做类似的事情来在运行时创建自定义子classes。您甚至可以以正常方式将子 class 定义为 NSArray
的直接子 class ,然后在不影响任何 class 的情况下调整它们的类型不是你的……或者一堆不同的东西……记住 ObjectiveC 运行时是你的朋友。