ObjectC-为什么使用class_copyPropertyList函数无法正确获取属性?

ObjectC-Why can't I get the properties correctly using the class_copyPropertyList function?

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <iostream>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class clazz = NSClassFromString(@"NSString");
        uint32_t count = 0;
        objc_property_t* properties = class_copyPropertyList(clazz, &count);
        for (uint32_t i = 0; i < count; i++){
            const char* name = property_getName(properties[i]);
            std::cout << name << std::endl;
        }
        free(properties);
    }
    return 0;
}

我会截取一些输出片段:

hash
superclass
description
debugDescription
hash
superclass
description
debugDescription
vertexID
sha224
NS_isSourceOver
hash
superclass
description
debugDescription
...

从输出中我们可以发现,hash、description、superclass等属性会重复出现几次,而有些属性(如UTF8String)则不会出现在结果列表中。 我应该如何正确获取属性列表? 我将不胜感激。

您没有看到 UTF8String 作为 属性 出现的原因是它没有在 main[=40= 中声明为 属性 ] NSString 的声明,而是在 类别 中。在 macOS 12.2.1/Xcode 13.2.1 上,NSString 的声明归结为:

@interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>
@property (readonly) NSUInteger length;
- (unichar)characterAtIndex:(NSUInteger)index;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
@end

NSString 上的所有其他属性和方法随后立即在类别中声明:

@interface NSString (NSStringExtensionMethods)

#pragma mark *** Substrings ***

/* To avoid breaking up character sequences such as Emoji, you can do:
    [str substringFromIndex:[str rangeOfComposedCharacterSequenceAtIndex:index].location]
    [str substringToIndex:NSMaxRange([str rangeOfComposedCharacterSequenceAtIndex:index])]
    [str substringWithRange:[str rangeOfComposedCharacterSequencesForRange:range]
*/
- (NSString *)substringFromIndex:(NSUInteger)from;
- (NSString *)substringToIndex:(NSUInteger)to;

// ...

@property (nullable, readonly) const char *UTF8String NS_RETURNS_INNER_POINTER; // Convenience to return null-terminated UTF8 representation

// ...

@end

当 属性 在类型的类别中声明时,它不会作为实际的 Obj-C 属性 发出,因为类别 can only add methods to classes, and not instance variables。当类别在类型上声明 属性 时,它必须由 方法 支持,而不是传统的 属性.

您也可以通过自定义 class 看到这一点 — 在我的机器上,

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface MyClass: NSObject
@property (nullable, readonly) const char *direct_UTF8String NS_RETURNS_INNER_POINTER;
@end

@interface MyClass (Extensions)
@property (nullable, readonly) const char *category_UTF8String NS_RETURNS_INNER_POINTER;
@end

@implementation MyClass
- (const char *)direct_UTF8String {
    return "Hello, world!";
}

- (const char *)category_UTF8String {
    return "Hi there!";
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class clazz = NSClassFromString(@"MyClass");
        
        printf("%s properties:\n", class_getName(clazz));
        uint32_t count = 0;
        objc_property_t* properties = class_copyPropertyList(clazz, &count);
        for (uint32_t i = 0; i < count; i++){
            printf("%s\n", property_getName(properties[i]));
        }
        
        free(properties);
        
        puts("-----------------------------------------------");
        
        printf("%s methods:\n", class_getName(clazz));
        Method *methods = class_copyMethodList(clazz, &count);
        for (uint32_t i = 0; i < count; i++) {
            SEL name = method_getName(methods[i]);
            printf("%s\n", sel_getName(name));
        }
        
        free(methods);
    }
    
    return 0;
}

产出

MyClass properties:
direct_UTF8String
-----------------------------------------------
MyClass methods:
direct_UTF8String
category_UTF8String

如果从 class 中删除 *UTF8String 方法的实际实现,属性 仍然声明,但类别方法消失(因为它实际上没有由于类别的工作方式而综合实施):

MyClass properties:
direct_UTF8String
-----------------------------------------------
MyClass methods:
direct_UTF8String

至于如何对此进行调整:这取决于您尝试获取属性的目的,以及您可能特别需要 UTF8String 的原因。

NSString 在其接口中声明它实现了方法,但实际上并没有实现它们,这就是为什么当您在运行时打印其方法的列表,但它不会打印您期望的内容。 这些方法由其他私有 classes 实现,当您初始化 NSString 的新实例时,您得到的不是 NSString 的实例,而是私有 class 的实例] 有实际实施。

可以看到通过打印class类型的字符串,下面打印的是NSCFString或者NSTaggedPointerString,而不是NSString:

NSString* aString = [NSString stringWithFormat: @"something"];
NSLog(@"%@", [aString class]);

这会打印出 __NSCFConstantString:

NSLog(@"%@", [@"a constant string" class]);

此模式称为 class 集群模式。 如果您修改以转储 NSCFString 的方法,您将得到一个“redactedDescription”,看起来您是 prevented to query these classes.