医药公司网站模板网站优化排名分享隐迅推
医药公司网站模板,网站优化排名分享隐迅推,新乡做网站报价,普通网站与营销型网站有什么区别【iOS】Effective Objective-C第二章理解“属性”这一概念在对象内部尽量直接访问实例变量理解“对象等同性”这一概念以“类族模式”隐藏实现细节在既有类中使用关联对象存放自定义数据理解objc_msgSend的作用理解消息转发机制用“方法调配技术”调试“黑盒方法”理解类对象的…【iOS】Effective Objective-C第二章理解“属性”这一概念在对象内部尽量直接访问实例变量理解“对象等同性”这一概念以“类族模式”隐藏实现细节在既有类中使用关联对象存放自定义数据理解objc_msgSend的作用理解消息转发机制用“方法调配技术”调试“黑盒方法”理解类对象的用意理解“属性”这一概念属性的本质属性是Objective-C的一项特性用于封装对象中的数据。Objective-C对象通常会把所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”来访问。其中“获取方法”getter用于读取变量值而“设置方法”setter用于写入变量值。编写Objective-C代码时对象布局在编译器已经固定了只要访问到实例变量的代码编译器就把其替换为“偏移量”这个偏移量是“硬编码”表示该变量距离存放对象的内存区域的起始地址有多远。如果代码使用了编译器计算出来的偏移量那么在修改类定义之后必须重新编译否者就会出错Objective-C的做法是把实例变量当作一种存储偏移量所用的“特殊变量”交给“类对象”保管。偏移量会在运行期查找如果类的定义变了那么存储的偏移量也就变了这样无论何时访问实例变量总能找到正确的偏移量。这个问题的另一个解决办法就是尽量不直接访问实例变量而通过存取方法来做。在对象接口的定义中可以使用属性能够封装在对象里的数据。属性的本质是编译器会自动写出一套存取方法用以访问给定类型中具有给定名称的变量。interfaceEOCPerson:NSObjectpropertyNSString*firstName;propertyNSString*lastName;endlinterface EOCPerson:NSObject-(NSString*)firstName;-(void)setFirstName:(NSString*)firstName;-(NSString*)lastName;-(void)setLastName:(NSString*)lastName;end这两种代码等效。访问属性要访问属性可以使用“点语法”。点语法的优势在于编译器会把“点语法”转换为存取方法的调用此过程叫做“自动合成”这个过程有编译器在编译期执行。编译器自动向类中添加适当类型的实例变量并且在属性名前面加下划线以此作为实例变量的名字。也可以在类的实现代码中通过synthesize语法来指定实例变量的名字implementationEOCPersonsynthesizefirstName_myFirstName;synthesizelastName_myLastName;end这样就会将生成的实例变量命名为_myFirstName和 _myLastName而不再使用默认名字。若不想令编译器自动合成存取方法则可以自己实现如果只实现了其中一个存取方法那么另一个还是会由编译器来合成。使用dynamic关键字它会告诉编译器不要自动创建实现属性所用的实例变量也不要为其创建存取方法。而且在编译访问属性的代码时即使编译器发现没有定义存取方法也不会报错它相信这些方法能在运行期找到。属性特质属性的各种特质设定也会影响编译器所生成的存取方法。属性可以拥有的特质分为四类原子性在默认情况下不写默认atomic)由编译器所合成的方法通过锁定机制确保其原子性。如果属性具备nonatomic特质则不使用同步锁。那么atomic与nonatomic的区别是什么呢使用atomic时getter方法会通过锁定机制来确保其操作的原子性。也就是说如果两个线程读写同一属性那么不论何时总能看到有效的属性值。使用nonatomic也就是不加锁时当一个线程正在改写某属性值时另外一个线程也许会突然闯入把尚未修改好的属性值读取出来这样线程读到的属性值就可能不对。不过我们会发现平时程序中所有属性都声明为nonatomic。这样做的历史原因是在iOS中使用同步锁的开销较大这会带来性能问题。一般情况下并不要求属性必须是“原子的”因为这并不能保证线程安全若要实现“线程安全”的操作还需采用更为深层的锁定机制才行。读写权限readwrite读写属性拥有getter方法与setter方法。若该属性由synthesize实现则编译器自动生成这两个方法。readonly只读仅拥有getter方法只有当该属性由synthesize实现时编译器才会为其合成setter方法。内存管理语义属性用于封装数据而数据则要有具体的所有权语义。assignsetter方法只会执行针对“纯量类型”例如CGFloat或NSInteger等的简单赋值操作。strong为这种属性设置新值时setter方法会先报留新值并释放旧值然后再将新值设置上去。weak为这种属性设置新值时setter方法既不报留新值也不释放旧值。在属性所指的对象遭到摧毁时属性值也会清空。unsafe_unretained此特质语义和assign相同但适用于“对象类型”。与weak不同的在于当目标对象遭到摧毁时属性值不会自动清空。copy其表达的所属关系与strong类似但setter方法不保留新值而是将其拷贝。当属性类型为NSString*时经常用此特质来保护其封装性因为传递给setter方法的新值有可能指向一个NSMutableString类的实例。此时若不是拷贝字符串那么设置完属性之后字符串的值就可能会在对象不知道的情况下遭人更改。因此这时就要拷贝一份不可变的字符串确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是可变的就应该在设置新属性值时拷贝一份。方法名可通过如下特质指定存取方法名gettername如果某属性时BOOL类型而你想为其getter方法加上is前缀。property(nonatomic,getterisOn)BOOL on;settername指定setter方法方法名不太常见。通过以上特质可以微调由编译器所合成的存取方法。值得注意的是若是自己实现这些存取方法那么应该保证其具备相关属性所声明的特质。例如如果某个属性声明为copy那么就应该在setter方法中拷贝相关对象。如果想在其他方法里设置属性值那么同样遵守属性定义中所宣称的语义。例如我们新增一个初始化方法用于设置名和姓的初始值。property(copy)NSString*firstName;property(copy)NSString*lastName;-(id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;-(id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName{if(self[superinit]){_firstName[firstName copy];_lastName[lastName copy];}returnself;}总结一下可以用property语法来定义对象中所封装的数据。通过“特质”来指定存储数据所需的正确语义。在设置属性所对应的实例变量时一定要遵从该属性所声明的语义。开发iOS程序时应该使用nonatomic属性因为atomic属性会严重影响性能。在对象内部尽量直接访问实例变量在对象之外访问实例变量时总是应该通过属性来做。然而在对象内部访问实例变量时笔者的建议是除了几种特殊情况之外在读取实例变量时采用直接访问的形式而在设置时通过属性来做。直接访问和属性访问的区别由于不经过Objective-C的“方法派发”步骤所以直接访问实例变量比属性访问的速度快。直接访问实例变量时不会调用其setter方法这就饶过了为相关属性所定义的“内存管理语义”。比如如果在ARC下直接访问一个声明为copy的属性那么并不会拷贝该属性只会保留新值并释放旧值。直接访问实例变量不会触发KVO通知。通过属性访问可给getter方法或setter方法中新值断点监控该属性的调用者及其访问时机有助于排查与之相关的错误。对究竟如何访问实例对象的讨论很多有一种合理的折中方案。即在写入实例变量时通过其setter方法来做而在读取实例变量时则直接访问。这样既能提高读取操作的速度又能控制对属性的写入操作。不过要注意在初始化方法中应该如何设置属性值这种情况下总是应该直接访问实例变量。在init方法中优先直接访问实例变量避免触发子类覆写的setter逻辑。某些情况下如果变量在超类中且子类无法直接访问就需要调用setter方法。惰性初始化即懒加载这种情况下必须通过getter方法来访问属性否则实例变量永远不会被初始化。一般用于一个属性不常用而且创建成本高的情况。-(ECOBrain*)brian{if(!_brain){_brain[[Brain alloc]init];}return_brain;}总结一下在对象内部读取数据时应该直接访问实例变量来读而写入数据时则应通过属性来写。在初始化方法及dealloc方法中总是应该直接通过实例变量来读写数据。有时使用惰性初始化配置某份数据时需要通过属性来读取数据。理解“对象等同性”这一概念对象等同性因为比较操作比较的是两个指针本身而不是其所指对象。所以应该使用NSObject协议中声明的isEqual:方法来判断两个对象的等同性。一般来说两个类型不同的对象总是不相等的。NSObject协议中有两个用于判断等同性的关键方法-(BOOL)isEqual:(id)object;-(NSUInteger)hash;NSObject类对这两个方法的默认实现是当且仅当其“指针值”完全相等时这两个对象才相等。如果isEqual:方法判定两个对象相等那么其hash方法也必须返回同一个值。但是如果两个对象的hash方法返回同一个值那么isEqual:方法未必会认为两者相等。例如我们认为如果两个EOCPerson的所有字段均相等那么这两个对象就相等。interfaceEOCPerson:NSObjectproperty(nonatomic,copy)NSString*firstName;property(nonatomic,copy)NSString*lastName;property(nonatomic,assign)NSUInteger age;end那么isEqual:”方法可以写成一(BOOL)isEqual:(id)object{if(selfobject)returnYES;if([selfclass]![object class])returnNO;EOCPerson*otherPerson(EOCPerson*)object;if(![_firstName isEqualToString:otherPerson.firstName])returnNO;if(![_lastNameisEqualToString:otherPerson.lastName])returnNO;if(_age!otherPerson.age)returnNO;returnYES;}首先直接判断两个指针是否相等。若相等则其均指向同一对象所以受测对象也必定相等。接下来比较两对象所属的类。若不属于同一类则两对象不相等。最后检测每个属性是否相等。只要其中有不相等的属性就判断两对象不等。接下来实现hash方法。根据同等性约定若两对象相等则其哈希码也相等但是两个哈希码相同的对象却未必相等。这是能否正确覆写“isEqual:“方法的关键-(NSUInteger)hash{NSUInteger fistNameHash[_firstName hash];NSUInteger lastNameHash[_lastName hash];NNSUInteger ageHash_age;returnfirstNameHash^lastNameHash^ageHash;}这样既能保持较高效率又能使生成的哈希码至少位于一定范围之内而不会过于频繁地重复。特定类所具有的等同性判定方法NSString类的等同性判定方法为isEqualToString:“NSArray类的的等同性判定方法为“isEqualToArray:”NSDictionary类的等同性判定方法为isEqualToDictionary:。调用此方法比调用isEqual:方法快因为后者不知道受测对象的类型要执行额外的步骤。如果经常需要判断等同性那么可能自己来创建等同性判定方法因为无须检测参数类型所以能大大提升检测速度。在编写判定方法时也应一并覆写“isEqual:”方法。后者的常见实现方式为如果受测的参数与接收该消息的对象都属于同一个类那么就调用自已编写的判定方法否则就交由超类来判断。-(BOOL)isEqualToPerson:(ECOPerson*)otherPerson{if(![_firstName isEqualToString:otherPerson.firstName]){returnNO;}if(![_lastName isEqualToString:otherPerson.lastName]){returnNO;}if(_age!otherPerson.age){returnNO;}returnYES;}-(BOOL)isEqual:(id)object{if([selfclass][object class]){return[selfisEqualToPerson:(ECOPerson*)object];}else{return[superisEqual:object];}}等同性判定的执行深度创建等同性判定方法时需要决定是根据整个对象来判断等同性还是仅根据几个字段。NSArray的检测方式为先看两个数组所含对象个数是否相同若相同再在每个对应位置的两个对象身上调用其isEqual:方法。如果对应位置上的对象均相等那么这两个数组就相等这叫做“深度等同性判定”。不过有时无须将所有数据逐个比较只根据其中部分数据即可判明二者是否等同。例如我们假设实例是根据数据库里的数据创建而来那么我们判定这个等同性时只用对比属性“唯一标识符”在数据库中作”主键“即可。容器中可变类的等同性在容器中放入可变类对象时放入collection之后就不应再改变其哈希码了。这是因为collection会把各个对象按照其哈希码分装到不同的“箱子数组”中。如果某对象在放入箱子之后哈希码又改变那么现在所处的这个箱子对它来说就是错误的。那么我们需要确保哈希码不是根据对象的可变部分计算出来的或是保证放入collection之后就不再改变对象内容了。举个例子提示大家要注意这么做的隐患现在set里包含一个数组对象数组中包含两个对象NSMutableSet*set[[NSMutableSet alloc]init];NSMutableArray*arrayA[[1,2]mutableCopy];[set addObject:arrayA];NSLog(set %,set);再向set中加入一个数组此数组与前一个数组所含对象相同顺序也相同。于是待加入的数组与set中已有的数组是相等的NSMutableArray*arrayB[[1,2]mutableCopy];[set addObject:arrayB];NSLog(set %,set);此时set里仍然只有一个对象因为刚才要加入的那个数组对象和set中已有的数组对象相等所以set并不会改变。我们添加一个和set中已有对象不同的数组NSMutableArray*arrayC[[1]mutableCopy];[set addObject:arrayC];NSLog(set %,set);由于加入的与原来已有的对象不相等所以现在set里有两个数组了一个是最早加入的另一个是刚才新添加的。最后我们改变arrayC的内容令其和最早加入set的那个数组相等[arrayC addObject:2];NSLog(set %,set);set中居然可以包含两个彼此相等的数组。根据set的语义是不允许出现这种情况的。若是拷贝此set更糟糕了:NSSet*setB[set copy];NSLog(setB %,setB);复制过的set中又只剩一个对象了此set好像是一个空set开始通过逐个向其中添加新对象而创建出来。总结一下若想检测对象的等同性请提供“isEqual”与hash方法。相同的对象必须具有相同的哈希码但是两个哈希码相同的对象却未必相同。不要盲目地逐个检测每条属性而是应该依照具体需求来制定检测方案。编写hash方法时应该使用计算速度快而且哈希码碰撞几率低的算法。以“类族模式”隐藏实现细节类族模式“类族”是一种很有用的模式可以隐藏“抽象基类”背后的实现细节。Objective-C的系统框架中普遍使用此模式。该模式可以灵活应对多个类将它们的实现细节隐藏在抽象基类后面以保持接口简洁。用户无须自己创建子类实例只需调用基类方法来创建即可。创建类族假设有一个处理雇员的类每个雇员都有“名字”和“薪水”两个属性管理者可以命令其执行日常工作。但是各种雇员的工作内容不同经理无须关心每个人如何完成其工作仅需指示开工即可。首先要定义抽象基类typedefNS_ENUM(NSUInteger,EOCEmployeeType){EOCEmployeeTypeDeveloper,EOCEmployeeTypeDesigner,EOCEmployeeTypeFinance,};interfaceEOCEmployee:NSObjectproperty(nonatomic,copy)NSString*name;property(nonatomic)NSUInteger salary;(EOCEmployee*)employeeWithType:(EOCEmployeeType)type;-(void)doADaysWork;end#importEOCEmployee.h#importEOCEmployeeDeveloper.h#importEOCEmployeeDesigner.h#importEOCEmployeeFinance.himplementationEOCEmployee(EOCEmployee*)employeeWithType:(EOCEmployeeType)type{switch(type){caseEOCEmployeeTypeDeveloper:return[[EOCEmployeeDeveloper alloc]init];break;caseEOCEmployeeTypeDesigner:return[[EOCEmployeeDesigner alloc]init];break;caseEOCEmployeeTypeFinance:return[[EOCEmployeeFinance alloc]init];break;}}-(void)doADaysWork{//子类实现这个方法}end每个“实体子类”都从基类继承而来#importEOCEmployeeDeveloper.himplementationEOCEmployeeDeveloper-(void)doADaysWork{NSLog(Develop);}end本例中基类实现了一个“类方法”该方法根据待创建的雇员类别分配好对应的雇员类实例。这种“工厂模式”是创建类族的办法之一。但是这会出现一个重要陷阱即类型信息的误导性。结合我们上面的例子当调用工厂方法时EOCEmployee*employee1[EOCEmployee employeeWithType:EOCEmployeeTypeDesigner];在语法和接口上看你是在创建EOCEmployee。但实际上因为employeeWithType:方法内是这样写的caseEOCEmployeeTypeDesigner:return[[EOCEmployeeDesigner alloc]init];所以employee1 这个指针的表面类型是 EOCEmployee但它实际指向的内存对象是 EOCEmployeeDesigner 的实例。基于上述原因如果你用 isMemberOfClass: 检查会返回NO。这是因为 isMemberOfClass: 检查的是是否为这个类的直系实例。因此判断一个对象是否属于这个类族应该使用 isKindOfClass: 方法。Cocoa里的类族系统框架中有许多类族大部分collection类都是类族。例如NSArray与其可变版本NSMutableArray。这样看来实际上有两个抽象基类一个用于不可变数组另一个用于可变数组。尽管具备公共接口的类有两个但仍然可以和起来算做一个类族。不可变的类定义了对所有数组都通用的方法而可变的类则定义了那些只适用于可变类型的方法。两个类属于同一个类族这意味着二者在实现各自类型的数组可以共用实现代码。此外还能把可变数组复制为不可变数组反之亦然。我们要明白很重要的一点像NSArray这样的类的背后其实是个类族对于大部分collection类而言都是这样的。// 错误示例试图通过精确的类对象比较来判断if([maybeAnArray class][NSArray class]){// 这里的代码永远不会执行}这段代码的本意是想检查变量 maybeAnArray 是否是一个 NSArray 类的实例。但是NSArray是一个类族。当你通过NSArray的工厂方法或其子类NSMutableArray的初始化方法创建一个数组对象时返回你的并不是NSArray类或NSMutableArray类的直系实例而是它们的某个私有子类的实例。这些私有子类继承自NSArray但对开发者完全隐藏。所以[maybeAnArray class]返回的是这个私有子类的Class对象它当然永远不等于[NSArray class]。同样使用 isKindOfClass: 可以检查该方法会沿着对象的继承链向上查询。我们常要向类族中新增实体子类需要遵守以下几条规则子类应该继承自类族中的抽象基类。子类应该定义自己的数据存储方式。子类应当覆写超类文档指明要覆写的方法。总结一下类族模式可以把实现细节隐藏在一套简单的公共接口后面。系统框架中经常使用类族。从类族的公共抽象基类中继承子类时要当心若有开发文档则应首先阅读。在既有类中使用关联对象存放自定义数据有时需要在对象中存放相关信息这时我们通常会从对象所属的类中继承一个子类然后改用这个子类对象。然而并非所有情况下都能这么做。Objective-C中有一项强大的特性可以解决此问题这就是“关联对象”。可以给某对象关联许多其他对象这些对象通过“键”区分。存储对象值的时候可以指明”存储策略“用来维护“内存管理语义”。下表给出了枚举的取值以及与之等效的property属性下列方法可以管理关联对象void objc_setAssociatedObject (id object, void *key, id value, objc_AssociationPolicy policy此方法以给定的键和策略为某对象设置关联对象值id objc_getAssociatedObject (id object, void *key)此方法根据给定的键从某对象中获取相应的关联对象值void objc_removeAssociatedObjects(id object)此方法移除指定对象的全部关联对象这类似NSDictionary但是两者有一个重要差别设置关联对象时用的键是一个不透明的指针。如果在两个键上调用isEqual:方法的返回值是YES那么NSDictionary就认为二者相等然而在设置关联对象值时若想令两个键匹配到同一个值则二者必须是完全相同的指针才行。因此这设置关联对象值时通常使用静态全局变量做键。不过值得注意的是只有在其他做法不可行时才选用关联对象因为这种做法通常会引入难以查找的bug。理解objc_msgSend的作用在对象上调用方法是Objective-C中经常使用的功能这叫做“传递信息”。消息有“名称”或“传递子”可以接受参数而且可能还有返回值。在Objective-C中如果向某对象传递消息那就会使用动态绑定机制来决定需要调用的方法。在底层所有方法都是普通的C语言函数然而对象收到消息之后究竟该调用哪个方法完全取决于运行期决定甚至可以在程序运行时改变。给对象发消息id returnValue[someObject messageName:parameter];someObject叫做接收者messageName叫做选择子selector。选择子与参数合起来称为消息。编译器看到此消息后将其转换为一条标准的C语言函数调用消息传递机制中的核心函数叫做objc_msgSendid returnValueobjc_msgSend(someObject,selector(messageName:),parameter);其原型为voidobjc_msgSend(idself,SEL cmd,...)这是一个参数个数可变的函数能接受两个及以上的参数p1接收者。p2选择子类型是SEL指的就是方法的名字。后续参数消息中的那些参数顺序不变。objc_msgSend函数会依据接收者与选择子的类型来调用适当的方法。为了完成此操作该方法需要在接收者所属的类中搜寻其方法列表如果能找到与选择子名称相符的方法跳转其代码。如果找不到沿着继承体系继续向上查找等找到合适的方法之后再跳转。如果最终还是找不到执行“消息转发”操作。objc_msgSend将匹配结果缓存在“快速映射表”里面解决了调用一个方法需要很多步骤的问题。但是这种快速执行路径依旧不如静态绑定的函数调用操作迅速。以上描述了部分消息的调用过程其他“边界情况”则需交由另一些函数来处理objc_msgSend_stret待发送的消息返回结构体。objc_msgSend_fpret消息返回的是浮点数。objc_msgSendSuper要给超类发消息。原型如下return_typeClass_selector(idself,SEL _cmd,...)每个类里都有一张表格其中的指针都会指向这种函数而选择子的名称是查找表时所用的键。objc_msgSend等函数正是通过这张表来寻找应该执行的方法并跳转实现的。如果某函数的最后一项操作是调用另一个函数那么就可以运用“尾调用优化”技术。在没有优化的情况下每次函数调用都会在内存中形成一个“栈帧”记录它的执行状态。如果函数A调用了BA的栈帧就会一直保留在内存中等待B返回。如果这种调用链很长比如递归就会消耗大量内存甚至导致栈溢出。尾调用优化的核心就是 当编译器或运行环境发现一个调用是“尾调用”时它不会为这个新调用创建新的栈帧而是直接复用当前函数的栈帧并“跳转”到新函数的位置。理解消息转发机制在编译器向类发送了其无法解读的消息并不会报错而是启动“消息转发”机制。因为在运行期可以继续向类中添加方法所以编译器在编译时无法确知类中到底会不会有某个方法实现。消息转发分为两大阶段第一阶段先征询接收者所属的类看是否能动态添加方法以处理当前这个“未知的选择子”这叫做“动态方法解析”。第二阶段涉及完整的消息转发机制。这两个阶段之间又细分为两小步首先请接收者看看有没有其他对象能处理这条消息。若有则运行期系统会把消息转给那个对象于是消息转发过程结束一切如常。若没有“备援的接收者”则启动完整的消息转发机制运行期系统会把与消息有关的全部细节都封装到NSInvocation对象中再给接收者最后一次机会令其设法解决当前还未处理的这条消息。下面解释上面提到的几个概念动态方法解析对象在收到无法解读的消息后首先调用所属类的下列方法(BOOL)resolveInstanceMethod:(SEL)selector;该方法的返回值表示这个类能否新增一个实例方法用于处理该选择子使用前提是相关方法的代码已经写好只需要在运行时动态插在类中内部绑定的是C函数因为Runtime只能绑定C函数。此方案常用来实现dynamic属性。备援接收者当接收者还有第二次机会响应未知的选择子运行期系统会判断是否将消息转给其他接收者来处理调用方法如下-(id)forwardTargetForSelector:(SEL)selector;如果能找到备援对象就将其返回否则返回nil。请注意我们无法操作经由这一步所转发的消息。若是想在发送给备援接收者之前先修改消息内容那就得通过完整的消息转发机制了。完整的消息转发如果转发算法已经来到这一步的话那么就只能启用完整的消息转发机制了。此步骤调用-(void)forwardInvocation:(NSInvocation*)invocation;消息转发全流程接收者在其中的每一步都有机会处理消息步骤越往后消息处理的代价就越大。用“方法调配技术”调试“黑盒方法”通过方法调配技术我们既不需要源代码也不需要通过继承自类来覆写方法就能改变这个类本身的功能。新功能将在本类的所有实例中生效。通过下面方法可以实现两个方法实现的交换此函数的两个参数表示待交换的两个方法实现而方法实现则可通过下列函数获得此函数根据给定的选择从类中取出与之相关的方法。通过此方案开发者可以为不知道具体实现的黑盒方法增加日志记录功能有助于程序调试。理解类对象的用意描述Object-C对象所用的数据结构定义在运行期程序库的头文件里id类型本身也定义在这里由此可见每个对象结构体的首个成员是Class类的变量。该变量定义了对象所属的类通常称为isa指针。Class对象也定义在运行期程序库的头文件中此结构体存放类的元数据。Class本身也是OC对象其中的super_class定义了本类的超类类对象所属的类型是元类用来表述类对象本身所具有的元数据例如类方法。每个类仅有一个类对象而每个类对象仅有一个与之相关的元类。可以用类型信息查询方法来检视类继承体系。isMemberOfClass:能够判断对象是否为某个特定类的实例isKindOfClass:能够判断出对象是否为某类或其派生类的实例。像这样的类型信息查询方法使用isa指针获取对象所属的类然后通过super_class指针在继承体系中游走。由于OC使用“动态类型系统”所以用于查询对象所属类的类型信息查询功能非常有用。此外也可以用比较类对象是否等同的办法来做。若是如此那就要使用操作符而不要用“isEqual:”方法。原因在于类对象是“单例”在应用程序的范围内每个类的Class仅有一个实例。如if([object class][SomeClass class]){}即使可以这样做我们也仅应该尽量使用类型信息查询方法而不应该直接比较两个类对象是否等同因为前者可以正确处理那些使用了消息传递机制的对象。注意对于存在代理的对象class方法所返回的类表示发起代理的对象而非接受代理的对象。