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” [email protected],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; [email protected],&,VidRetain
@property(copy) id idCopy; [email protected],C,VidCopy
@property(nonatomic) int intNonatomic; Ti,VintNonatomic
@property(nonatomic, readonly, copy) id idReadonlyCopyNonatomic; [email protected],R,C,VidReadonlyCopyNonatomic
@property(nonatomic, readonly, retain) id idReadonlyRetainNonatomic; [email protected],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:@"[email protected]"]) {
    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;}
时间: 2024-10-12 16:51:34

Objective-C –反射篇的相关文章

黑马程序员—反射篇

--Java培训.Android培训.iOS培训..Net培训.期待与您交流! -- 1:获取字节码的三种方式: Employee emp=new Employee(); Class clazz1=Employee.class; Class clazz2=Class.forName("java.util.HashSet"); Class clazz3=emp.getClass(); 2:通过字节码获取对象: Employee enp=(Employee) clazz2.newInsta

Java高频面试题汇总--Java职场范儿

经历了两周的面试,终于收到了几个满意的offer.换工作的过程是痛苦的,除了一天马不停蹄地跑好几家公司面试,剩下的时间基本就是背面试题了.想找到一份适合自己的面试题并不简单,比如我找的是高级Java开发的职位.出于之前公司系统架构的设计,需要准备Java.spring.springboot.mysql.mybatis.mycat.zookeeper.dubbo.kafka.redis.网络等面试题.我结合之前面试的20多家公司,以及从CSDN/简书/掘金/公众号等相关渠道搜集到的面试题,从中整理

.Net 特性分析与妙用

一.特性是什么 1.想象很多小伙伴们都看过在一个类上方.或者在控制器见过类似的东东,加上之后就可以标识这个类或者方法就具备了某些特点 ,那我们就进入它的内心一探究竟吧. 2.我们进入某个特性之后,可以发现它又单独继承于Attribute 它的意思就是属性.特质的意思.那它到底能干嘛呢?能让我们写代码飞起吗?? 二.走进特性 1.我们也写一个自己的特性,说干就来吧来.带着问题一步一步是魔鬼的步伐,兄弟们要我们干就完了,噢力给!!! 2.首先我们创建一个类(特性就是一个很单纯的类,我们一般以Attr

诗经 全文

诗经 全文 (带注释和译文) http://www.edu009.com/Article/HTML/Article_60756.html <诗经> 春秋·孔丘 <诗经>是我国第一部诗歌总集,先秦时代称为“诗”或“诗三百”,孔子加以了整理.汉武帝采纳董仲舒“罢黜百家,独尊儒术”的建议,尊“诗”为经典,定名为<诗经>. <诗经>现存诗歌 305 篇,包括西周初年到春秋中叶共 500 余年的民歌和朝庙乐章,分为风.雅.颂三章. “风”包括周南.召南.邶.鄘.卫.王

第16篇-JAVA 类加载与反射

第16篇-JAVA 类加载与反射 每篇一句 :敢于弯曲,是为了更坚定的站立 初学心得: 追求远中的欢声笑语,追求远中的结伴同行 (笔者:JEEP/711)[JAVA笔记 | 时间:2017-05-12| JAVA 类加载与反射 ] 1.类加载 类加载器负责将 .class 文件(可能在磁盘上, 也可能在网络上) 加载到内存中, 并为之生成对应的 java.lang.Class 对象 当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载.连接.初始化三个步骤来对该类进行初始化,如果没

Python学习之旅 —— 基础篇(七)反射、冒泡排序算法

本篇要点: 冒泡算法 反射 一.冒泡排序 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.这步做完后,最后的元素会是最大的数. 针对所有的元素重复以上的步骤,除了最后一个. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较. 代码示例: li = [5, 67, 2, 8, 5, 19, 6, 33, 98, ] times = len(li) for i in range(times): for j in

iOS开发——技术精华Swift篇&amp;Swift 2.0和Objective-C2.0混编之第三方框架的使用

Swift 2.0和Objective-C2.0混编之第三方框架的使用 swift 语言是苹果公司在2014年的WWDC大会上发布的全新的编程语言.Swift语言继承了C语言以及Objective-C的特性,且克服了C语言的兼容性问题.Swift语言采用安全编程模式,且引入了多种新功能,使得编程工作更加简便,灵活! 2015年6月9日苹果又一次给所有开发之者带来了一个惊喜,那就是今年年底swift讲开源,者队iOS开发着来说无疑是一个值得兴奋的消息,可是就在这短短的几个月里面swift吸引了越来

程序集加载与反射(二):实例篇

目录: 上篇:程序集加载与反射(一):基础篇 Demo:下载 一.Demo 下面这个Demo,使用了策略模式模仿了一下插件机制.我们举个一邮件发送的例子: 1.一个策略类库:Strategy,里面定义了邮件需要实现的接口:IEmailStrategy. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespa

Java反射机制(四)—番外篇,实例化方法深入

反射机制这几篇博客写下来发现涉及到Java类的加载机制,这部分的内容也比较独立的一部分,因此单另一篇来写.在JAVA中任何的类都是需要加载到JVM中才能运行的.之前Class Loader介绍了类的加载机制,那么这里要说的是不同加载方式之间的对比,好能对JAVA类的实例化过程有更深刻的体会. new和Class.newInstance 我们说代码里出现new关键字意味着对于可能变动的代码,耦合过高了.遇到这种情况我们会用反射机制来去除new关键字,这在代理模式里我们见过了.实际上也就是用了Cla