我想为NSArray类的description
方法提供我自己的实现,以便我可以像这样使用它:
NSLog(@"%@", @[]);
我的想法是为NSArray
提供一个类别,并在那里简单地覆盖description
方法。但是它不起作用,因为NSArray
是一个类集群,它的真实类是__NSArrayI
,所以我的类别实现永远不会被调用。很遗憾,我无法为__NSArrayI
提供类别,因为此课程不可用。
当然,我可以继承NSArray
并在我的子类中实现此方法,但同样,因为NSArray
是一个类集群,我必须为一堆不同的方法提供实现,比如objectAtIndex:
而且我不想这样做,因为这对于简单地改变阵列打印到控制台的方式来说太过分了。
你有什么想法,伙计们?谢谢
答案 0 :(得分:2)
你有什么想法,伙计们?感谢
我们得到的想法。解决方案......你必须做出决定。
从documentation about format specifiers开始,您无需担心description
。具体来说,从该文件......
Objective-C对象,打印为返回的字符串 descriptionWithLocale:如果可用,或其他描述。也 使用CFTypeRef对象,返回结果 CFCopyDescription功能。
除非我们想破解链接器和/或动态加载器,否则我们无法对CFTypeRef
个对象做很多事情。
但是,我们可以对description
和descriptionWithLocale:
进行某事,尽管有些事情有点粗糙。
您可能还想考虑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运行时是你的朋友。