• Objective-C –反射篇


    今天我们来讲讲Objective-C的反射,

    一般Java开发工程师听到Objective-C支持反射一定很兴奋吧!

    基本反射

    基本反射包括

    • 获取Class 或 根据字符串获取Class
    • 检查是否有selector 以及 根据字符串 获取selector 并执行
    • 检查继承关系

    基本反射就是能通过NSObject的一些方法和简单封装好的方法直接能进行反射的操作

    Class相关的一些操作

    首先就是获取一个实例的Class: [self class]

    这个就是获取self对应实例的Class类型

    也可以通过[类名 class]的方式获取Class,比如[UIView class][[[UIView alloc] init] class]获取到的Class是一样的

    当然最主要还得有类似Java的Class.forName(String)通过字符串直接获取到Class : NSClassFromString

    比如获取UIView的Class可以 NSClassFromString(@"UIView") 直接返回的就是UIView的Class

    那么获取到Class有什么用呢?

    1. 直接通过Class来实例化对象
    2. 通过Class 你可以知道Class下面那些方法 属性 和 变量 ,并可以直接访问他们(会在后面的搞基反射里面讲)

    通过Class 直接实例化对象 很简单 比如

     Class viewClass = NSClassFromString(@"UIView");
     UIView *view = [viewClass alloc] init] ;

    可以看到viewClass和UIView是等价的,包括对 + 类型方法的调用也是即 [UIView layerClass][NSClassFromString(@"UIView") layerClass]是等价的

    selector相关

    selector对应的就是Java中的Method了 对应Method这个类 在Objective-C中是SEL

    SEL是一个结构体的指针typedef struct objc_selector *SEL;

    SEL 可以通过 @selectorNSSelectorFromString来直接获取

    SELMethod的不同在于 SEL在Mac系统中是单例的 .

    [Foo count][Bar count] 里面的count 指向的是同一个指针,

    包括@selector(count)NSSelectorFromString(@"count")指向的也都是同一个指针

    这和Java每个Class用getMethod取出的Method都是单独的实例是不同的

    SEL对应的就是方法的名字 , 这和Objective-C的实现有关,就是方法对应的是消息,而SEL就是消息的名称,所以不同的实例可以使用相同的消息名,而消息名本身是单例的,不和实例本身产生关系

    然后通过- (BOOL)respondsToSelector:(SEL)aSelector 可以判断实例是否真的有对于selector的实现,不管是否有被声明.

    而要反射调用一个selector则可以通过一系列的performSelector:方法进行实现 比如

    继承关系

    类似Java 的 instanceOf Objective-C 也有类似的方法,有

    1. - (BOOL)isKindOfClass:(Class)aClass
    2. - (BOOL)isMemberOfClass:(Class)aClass
    3. + (BOOL)isSubclassOfClass:(Class)aClass
    4. - (BOOL)conformsToProtocol:(Protocol *)aProtocol

    这几个方法都是定义在NSObject上的,区别在于

    isKindOfClass 基本和Java 的 instanceOf的功能一致 ,

    而isMemberOfClass 不能识别到父类 只能表明到底是不是这个Class ,

    而isSubclassOfClass是+类型的方法和isKindOfClass一样的,不过就是通过Class来进行调用,

    conformsToProtocol则是识别实例是否符合特定协议

    高级反射

    高级反射基本就是类似于Java的整个反射体系了,只不过Objective-C的这部分反射都是通过C调用实现的,比起来比较苦逼

    主要的一些函数有:

    1. objc_msgSend 系列
    2. class/protocol 系列
    3. method/SEL/IMP 系列
    4. ivar /property系列

    大部分的调用走包含在

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

    这两个头文件里

    objc_msgSend

    看名字就能知道 这个是objective-c的消息发送函数 ,上一篇也讲到所有的Objective-C的调用全是通过objc_msgSend来实现的

    objc_msgSend的使用还是比较简单的,看id objc_msgSend(id theReceiver, SEL theSelector, ...)就能知道.

    这里就介绍一些技巧

    由于objc_msgSend 返回的是id 那么如果方法定义的是 基本类型怎么办?

    看个样例

     unsigned retForUnsigned = ((unsigned ( *)(id, SEL)) objc_msgSend)(self, NSSelectorFromString(nsPropertyName));

    通过这种cast就可以返回cast为对于的基本类型

    而如果返回是浮点的话 可以直接调用double objc_msgSend_fpret(id self, SEL op, …)

    那么还有一种情况就是返回的是一个struct的话 需要调用 void objc_msgSend_stret(void * stretAddr, id theReceiver, SEL theSelector, ...) 来完成

    当然 他们都有对应的super函数来直接调用父类的方法,如objc_msgSendSuper

    实际上objc_XXX/object_XXX方法等方法都能找到对于的Objective-C的方法

    不过有一个比较有意思的 可以向大家介绍一下

    那就是void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) 和 id objc_getAssociatedObject(id object, const void *key)

    使用这一对函数就可以动态的为对象加getter/setter方法

    大家知道使用Categroy是不能直接加property的,但是通过上面一对就可以

    可以看AFNetworking中的代码

    static char kAFImageRequestOperationObjectKey;
    
    @interface UIImageView (_AFNetworking)
        @property(readwrite, nonatomic, retain, setter = af_setImageRequestOperation:) AFImageRequestOperation *af_imageRequestOperation;
    @end
    
    @implementation UIImageView (_AFNetworking)
    @dynamic af_imageRequestOperation;
    @end
    
    #pragma mark -
    
    @implementation UIImageView (AFNetworking)
    
    - (AFHTTPRequestOperation *)af_imageRequestOperation {
        return (AFHTTPRequestOperation *) objc_getAssociatedObject(self,&kAFImageRequestOperationObjectKey);
    }
    
    - (void)af_setImageRequestOperation:(AFImageRequestOperation *)imageRequestOperation {
        objc_setAssociatedObject(self, &kAFImageRequestOperationObjectKey,imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    不是设置@synthesize而是设置@dynamic + objc_getAssociatedObject/objc_setAssociatedObject 来完成动态的属性添加

    class/protocol

    对应的class_XXX和protocol_XXX函数 这里面的方法基本NS都包含了

    不过这里我们看一个声明

    struct objc_class {
        Class isa;
    
    #if !__OBJC2__
        Class super_class                                OBJC2_UNAVAILABLE;
        const char *name                              OBJC2_UNAVAILABLE;
        long version                                          OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                             OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                      OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;

    这是一个objectc class的原始定义 从里面就能看到一个Class 都包含了那些东西哦

    method/SEL/IMP

    这里说一下概念

    Method就是方法 实际上他包含了SEL和IMP 不同于SEL它是有宿主的,并不是单例

    SEL在上面已经介绍了实际上他就是等价于方法的名字

    而IMP实际就是方法的真正实现了

    如果要做动态方法解析 那么就可以自己作IMP来转换SEL对于的实现

    ivar /property

    ivar就是定义的变量,而property就是属性了

    这里要注意的就是取出一个class的ivar/property 用到的类似函数

    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

    注意到它是copy的,也就是说这块内存是copy 你得自己负责最后去

    例子:

     unsigned int propertyCount;
     objc_property_t *pProperty = class_copyPropertyList(class, &propertyCount);
     if (pProperty && propertyCount > 0) {
         for (unsigned int i = 0; i < propertyCount; i++) {
             [self setPropertyToObject:o pProperty:pProperty[i] withDepth:depth AndClass:class];
         }
     }
     if (pProperty) {
         free(pProperty);
     }

    不过这里有个比较苦逼的事情就是 去的ivar/property的类型值,这里Objective-C使用属性类型编码来区分类型

    所以最后通过const char *property_getAttributes(objc_property_t property)取到的是一个字符串, 得自己解析这个字符串来取得类型

    对于的编码:

    属性声明属性描述
    @property char charDefault; Tc,VcharDefault
    @property double doubleDefault; Td,VdoubleDefault
    @property enum FooManChu enumDefault; Ti,VenumDefault
    @property float floatDefault; Tf,VfloatDefault
    @property int intDefault; Ti,VintDefault
    @property long longDefault; Tl,VlongDefault
    @property short shortDefault; Ts,VshortDefault
    @property signed signedDefault; Ti,VsignedDefault
    @property struct YorkshireTeaStruct structDefault; T{YorkshireTeaStruct=”pot”i”lady”c},VstructDefault
    @property YorkshireTeaStructType typedefDefault; T{YorkshireTeaStruct=”pot”i”lady”c},VtypedefDefault
    @property union MoneyUnion unionDefault; T(MoneyUnion=”alone”f”down”d),VunionDefault
    @property unsigned unsignedDefault; TI,VunsignedDefault
    @property int (*functionPointerDefault)(char *); T^?,VfunctionPointerDefault
    @property id idDefault; Note: the compiler warns: no ‘assign’, ‘retain’, or ‘copy’ attribute is specified – ‘assign’ is assumed” T@,VidDefault
    @property int *intPointer; T^i,VintPointer
    @property void *voidPointerDefault; T^v,VvoidPointerDefault
    @property int intSynthEquals; In the implementation block:
    @synthesize intSynthEquals=_intSynthEquals; Ti,V_intSynthEquals
    @property(getter=intGetFoo, setter=intSetFoo:) int intSetterGetter; Ti,GintGetFoo,SintSetFoo:,VintSetterGetter
    @property(readonly) int intReadonly; Ti,R,VintReadonly
    @property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; Ti,R,GisIntReadOnlyGetter
    @property(readwrite) int intReadwrite; Ti,VintReadwrite
    @property(assign) int intAssign; Ti,VintAssign
    @property(retain) id idRetain; T@,&,VidRetain
    @property(copy) id idCopy; T@,C,VidCopy
    @property(nonatomic) int intNonatomic; Ti,VintNonatomic
    @property(nonatomic, readonly, copy) id idReadonlyCopyNonatomic; T@,R,C,VidReadonlyCopyNonatomic
    @property(nonatomic, readonly, retain) id idReadonlyRetainNonatomic; T@,R,&,VidReadonlyRetainNonatomic

    下面有个小程序用来解析这个属性编码

    + (PropertyAttributeInfo *)analyseProperty:(objc_property_t)pProperty WithClass:(Class)aClass {
    
    NSString *propertyAttributes = [NSString stringWithUTF8String:property_getAttributes(pProperty)];
    NSMutableString *propertyName = [NSMutableString stringWithUTF8String:property_getName(pProperty)];
    PropertyAttributeInfo *info;
    if ((info = [[PropertyAttributeInfoCache instance] getFromCacheWithClass:aClass
                                                             AndPropertyName:propertyName]) != nil) {
        return info;
    }
    TypeOfProperty typeOfProperty = NIL;
    Class class = nil;
    BOOL readOnly = NO;
    Class arrayClass = nil;
    NSString *dicPropertyName = propertyName;
    NSArray *array = [propertyAttributes componentsSeparatedByString:@","];
    NSString *typeAtt = [array objectAtIndex:0];
    if ([typeAtt hasPrefix:@"Tc"]) {
        typeOfProperty = CHAR;
    } else if ([typeAtt hasPrefix:@"Td"]) {
        typeOfProperty = DOUBLE;
    } else if ([typeAtt hasPrefix:@"Ti"]) {
        typeOfProperty = INT;
    } else if ([typeAtt hasPrefix:@"Tf"]) {
        typeOfProperty = FLOAT;
    } else if ([typeAtt hasPrefix:@"Tl"]) {
        typeOfProperty = LONG;
    } else if ([typeAtt hasPrefix:@"Ts"]) {
        typeOfProperty = SHORT;
    } else if ([typeAtt hasPrefix:@"T{"]) {
        typeOfProperty = STRUCT;
    } else if ([typeAtt hasPrefix:@"TI"]) {
        typeOfProperty = UNSIGNED;
    } else if ([typeAtt hasPrefix:@"T^i"]) {
        typeOfProperty = INT_P;
    } else if ([typeAtt hasPrefix:@"T^v"]) {
        typeOfProperty = VOID_P;
    } else if ([typeAtt hasPrefix:@"T^?"]) {
        typeOfProperty = BLOCK;
    } else if ([typeAtt hasPrefix:@"T@"]) {
        typeOfProperty = ID;
        if ([typeAtt length] > 4) {
            class = NSClassFromString([typeAtt substringWithRange:NSMakeRange(3, [typeAtt length] - 4)]);
            if ([class isSubclassOfClass:[NSArray class]]) {
                NSUInteger location = [propertyName rangeOfString:@"$"].location;
                if (location != NSNotFound) {
                    arrayClass = NSClassFromString([propertyName substringWithRange:NSMakeRange(location + 1,
                            [propertyName length] - location - 1)]);
                    dicPropertyName = [NSString stringWithString:[propertyName substringWithRange:NSMakeRange(0,
                            location)]];
                }
            }
        }
    }
    
    if ([array count] > 2) {
        for (NSUInteger i = 1; i < [array count] - 1; i++){NSString*att =[array objectAtIndex:i];if([att isEqualToString:@"R"]){
                readOnly = YES;}}}
    info =[[PropertyAttributeInfo alloc] init];
    info.readOnly = readOnly;
    info.class=class;
    info.type = typeOfProperty;
    info.arrayClass = arrayClass;
    info.dicPropertyName = dicPropertyName;
    info.oriPropertyName = propertyName;[[PropertyAttributeInfoCache instance] putToCacheWithClass:aClass AndPropertyName:propertyName
                                                      WithInfo:info];return info;}
  • 相关阅读:
    demo_38 关注页导航栏实现
    demo_37 评论列表实现_02 封装popup 及 格式化时间
    demo_37 评论列表实现_01
    SaaS
    rsyncd脚本
    rsyncd
    MySQL高可用--MHA安装
    正向代理
    zabbix 一键部署
    kvm安装
  • 原文地址:https://www.cnblogs.com/zhaoguowen/p/4570072.html
Copyright © 2020-2023  润新知