获取一个类的信息(仿YYClassInfo类)

92 阅读3分钟
  1. BSClassInfo.h文件

//  
//  ClassInfo
//  BSClassInfo
//  Created by yuYue on 2019/2/2.
//  Copyright © 2019 yuYue. All rights reserved.
//

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

NS_ASSUME_NONNULL_BEGIN

@interface BSIvarInfo : NSObject
@property (assign, nonatomic, readonly) Ivar ivar;
@property (copy, nonatomic, readonly) NSString *name;
@property (assign, nonatomic, readonly) ptrdiff_t offset;
@property (copy, nonatomic, readonly) NSString *typeEncoding;

- (instancetype)initWithIvar:(Ivar)ivar;
@end

@interface BSMethodInfo : NSObject
@property (assign, nonatomic, readonly) Method method;
@property (copy, nonatomic, readonly) NSString *name;
@property (assign, nonatomic, readonly) SEL sel;
@property (assign, nonatomic, readonly) IMP imp;
@property (copy, nonatomic, readonly) NSString *typeEncoding;
@property (copy, nonatomic, readonly) NSString *returnTypeEncoding;
@property (copy, nonatomic, readonly) NSArray<NSString *> *argumentTypeEncoding;

- (instancetype)initWithMethod:(Method)method;
@end

@interface BSPropertyInfo : NSObject
@property (assign, nonatomic, readonly) objc_property_t property;
@property (copy, nonatomic, readonly) NSString *name;
@property (copy, nonatomic, readonly) NSString *typeEncoding;
@property (copy, nonatomic, readonly) NSString *ivarName;
@property (assign, nonatomic, readonly) SEL getter;
@property (assign, nonatomic, readonly) SEL setter;
@property (nullable, assign, nonatomic, readonly) Class cls;
@property (nullable, copy, nonatomic, readonly) NSArray<NSString *> *protocols;

- (instancetype)initWithProperty:(objc_property_t)property;

@end

@interface BSClassInfo : NSObject
@property (assign, nonatomic, readonly) Class cls;
@property (assign, nonatomic, readonly) Class supCls;
@property (assign, nonatomic, readonly) Class metaCls;
@property (assign, nonatomic, readonly) BOOL isMeta;
@property (copy, nonatomic, readonly) NSString *name;
@property (nullable, strong, nonatomic, readonly) BSClassInfo *superClassInfo;

@property (nullable, strong, nonatomic, readonly) NSDictionary<NSString *, BSIvarInfo *> *ivarInfos;
@property (nullable, strong, nonatomic, readonly) NSDictionary<NSString *, BSMethodInfo *> *methodInfos;
@property (nullable, strong, nonatomic, readonly) NSDictionary<NSString *, BSPropertyInfo *> *propertyInfos;

- (instancetype)initWithClass:(Class)cls;
@end


NS_ASSUME_NONNULL_END

  1. BSClassInfo.m 文件
//
//  NSObject+BSClassInfo.m
//  ClassInfo
//
//  Created by yuYue on 2019/2/2.
//  Copyright © 2019 yuYue. All rights reserved.
//

#import "BSClassInfo.h"

@implementation BSIvarInfo

- (instancetype)initWithIvar:(Ivar)ivar{
    if (!ivar) return nil;
    self = [super init];
    _ivar = ivar;
    const char * name = ivar_getName(ivar);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    _offset = ivar_getOffset(ivar);
    const char * typeEncoding = ivar_getTypeEncoding(ivar);
    if (typeEncoding) {
        _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
    }
    return self;
}
@end

@implementation BSMethodInfo

- (instancetype)initWithMethod:(Method)method{
    if(!method) return nil;
    self = [super init];
    _method = method;
    _sel = method_getName(method);
    const char *name = sel_getName(_sel);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    _imp = method_getImplementation(method);
    const char *typeEncoding = method_getTypeEncoding(method);
    if (typeEncoding) {
        _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
    }
    char *returnType = method_copyReturnType(method);
    if (returnType) {
        _returnTypeEncoding = [NSString stringWithUTF8String:returnType];
        free(returnType);
    }
    unsigned int argumentCount = method_getNumberOfArguments(method);
    if (argumentCount > 0) {
        NSMutableArray *arrM = [NSMutableArray array];
        for (int i = 0; i < argumentCount; i++) {
            char *argumentType = method_copyArgumentType(method, i);
            NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : @"";
            [arrM addObject:type];
            free(argumentType);
        }
        _argumentTypeEncoding = arrM;
    }
    return self;
}

@end

@implementation BSPropertyInfo
- (instancetype)initWithProperty:(objc_property_t)property{
    if(!property) return nil;
    self = [super init];
    _property = property;
    const char *name  = property_getName(property);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    
    if (_name) {
        _getter = NSSelectorFromString(_name);
        NSString *setter = [NSString stringWithFormat:@"set%@%@",[[_name substringToIndex:1]uppercaseString],[_name substringFromIndex:1]];
        _setter = NSSelectorFromString(setter);
    }
    
    unsigned int count;
    objc_property_attribute_t *attributs =  property_copyAttributeList(property, &count);
    for (int i = 0; i < count; i++) {
        switch (attributs[i].name[0]) {
            case 'T':
            {
                //typeEncoding
                _typeEncoding = [NSString stringWithUTF8String:attributs[i].value];
                if (*_typeEncoding.UTF8String == '@') {
                    size_t len = strlen(_typeEncoding.UTF8String);
                    if (len != 2) {
                        //是对象
                        NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
                        if (![scanner scanString:@"@\"" intoString:NULL]) continue;
                        
                        NSString *clsName = nil;
                        if ([scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
                            _cls = NSClassFromString(clsName);
                        }
                        
                        //protocol
                        NSMutableArray *arrM = [NSMutableArray array];
                        while ([scanner scanString:@"<" intoString:NULL]) {
                            NSString *protocol;
                            if ([scanner scanUpToString:@">" intoString:&protocol]) {
                                if (protocol.length) {
                                    [arrM addObject:protocol];
                                }
                            }
                            [scanner scanString:@">" intoString:NULL];
                        }
                        _protocols = arrM;
                    }
                }
            }
                break;
                
            case 'V':
            {
                if (attributs[i].value) {
                    _ivarName = [NSString stringWithUTF8String:attributs[i].value];
                }
            }
                break;
            default:
                break;
        }
    }
    return self;
}
@end


@implementation BSClassInfo
- (instancetype)initWithClass:(Class)cls{
    if(!cls) return nil;
    self = [super init];
    _cls = cls;
    _supCls = class_getSuperclass(cls);
    _isMeta = class_isMetaClass(cls);
    if (!_isMeta) {
        _metaCls = objc_getMetaClass(class_getName(cls));
    }
    _name = [NSString stringWithUTF8String:class_getName(cls)];
    [self update];
    
    _superClassInfo = [[BSClassInfo alloc] initWithClass:_supCls];
    
    return self;
}

- (void)update{
    _ivarInfos = nil;
    _methodInfos = nil;
    _propertyInfos = nil;
    
    //ivar
    Class cls = self.cls;
    unsigned int ivarCount = 0;
    NSMutableDictionary *dictMIvar = [NSMutableDictionary dictionary];
    Ivar *ivarList = class_copyIvarList(cls, &ivarCount);
    for (int i = 0; i < ivarCount; i++) {
        BSIvarInfo *ivarInfo = [[BSIvarInfo alloc] initWithIvar:ivarList[i]];
        [dictMIvar setObject:ivarInfo forKey:ivarInfo.name];
    }
    _ivarInfos = dictMIvar;
    free(ivarList);
    
    //method
    NSMutableDictionary *dictMMethod = [NSMutableDictionary dictionary];
    unsigned int methodCount = 0;
    Method *methodList = class_copyMethodList(cls, &methodCount);
    for (int i = 0 ; i < methodCount; i++) {
        BSMethodInfo *methodInfo = [[BSMethodInfo alloc] initWithMethod:methodList[i]];
        [dictMMethod setObject:methodInfo forKey:methodInfo.name];
    }
    _methodInfos = dictMMethod;
    free(methodList);
    
    //property
    NSMutableDictionary *dictMProperty = [NSMutableDictionary dictionary];
    unsigned int propertyCount = 0;
    objc_property_t *propertyList = class_copyPropertyList(cls, &propertyCount);
    for (int i = 0; i < propertyCount; i++) {
        BSPropertyInfo *propertyInfo = [[BSPropertyInfo alloc] initWithProperty:propertyList[i]];
        [dictMProperty setObject:propertyInfo forKey:propertyInfo.name];
    }
    _propertyInfos = dictMProperty;
    free(propertyList);
}
@end
  1. 总结
    以上代码是读完YYModel中的YYClassInfo源码学到的利用runtime获取类的信息。YYClassInfo源代码中有关于缓存的部分,而我们的目的是抽取runtime部分,所以这里没有做缓存。有兴趣的小伙伴可以和YYClassInfo源代码做下对比会发现有些部分会有些少许改动。其实就是按照自己的习惯写出来的代码会和源代码有些不同,但都达到了获取类信息的目的。该代码有助于理解YYModel源码以及可以进一步学习runtime知识,一举两得。