Effective Objective

Stella981
• 阅读 627

对象的类型并非在编译期就绑定好了,而是要在运行期查找。而且还有个特殊的类型叫做id,它能指代任意的Objective-C对象类型。一般情况下,应指明消息接收者的具体类型,这样的话,如果向其发送了无法解读的消息,那么编译器就会产生警告信息。而类型为id的对象则不然,编译器假定它能响应所有消息

如下面代码所示:

Effective Objective

对于str1,我指明了其具体类型是NSString *,所以当我给str1发送了一个NSString无法解读的消息后,编译器就产生了警告信息。对于str2,其类型为id,虽然NSString无法解读该消息,但是Foundation框架中有其他的类可以解读该消息,而编译器又假定id类型的str2能响应所有消息,所以就没有报错。

这个时候我们可能就会有个疑惑:_之前的文章中说,编译器无法确定某类型对象到底能解读多少种选择子,因为在运行期还可向其中动态新增;可是我们这里又说,当给一个指明了其具体类型的消息接收者发送了其无法解读的消息的时候,编译器就会产生警告信息,这不是矛盾了吗?_其实不然。即便是使用了动态新增技术,编译器也觉得应该能在某个头文件中找到方法原型的定义

Objective-C对象的本质

每个Objective-C对象实例都是指向某块内存数据的指针。所以在声明变量时,类型后面要跟一个“ * ”字符:

NSString *str1 = @"Norman";

str1是一个指针变量,我们可以将其理解成是一个存放内存地址的变量,而NSString自身的数据就存在于那个地址中。因此我们可以说,str1指向了NSString实例。所有的Objective-C对象都是如此。如果我们将对象所需的内存分配在栈上,那么编译器就会报错:

Effective Objective

对于通用的对象类型id,其数据结构的定义是这样的(关于Objective-C中类、实例对象等数据结构的详细说明,请参考我之前写的文章:Runtime——相关数据结构的说明):

typedef struct objc_object *id;

所以id本身已经是指针了,因此我们能够这样写:

id str2 = @"Lee";

上面这种使用id来定义字符串的方式,与使用NSString *来定义相比,其语法意义是相同的。唯一的区别是:如果声明时指定了具体类型,那么在该类实例上调用其所没有的方法时,编译器会探知此情况,并发出警告信息

刚才提到,id的数据结构定义是这样的:

typedef struct objc_object *id;

那么实例对象的数据结构是如何的呢?如下:

struct objc_object {

Class isa  OBJC_ISA_AVAILABILITY;

};

由此可见,每个对象结构体的首个成员是Class类的变量。该变量定义了对象所属的类,通常称为“is a”指针。例如,刚才的例子中所用的对象“是一个”(is a)NSString,所以其“is a”指针就指向NSString。

Class的定义如下:

typedef struct objc_class *Class;

struct objc_class {

Class isa;

Class super_class;

const char *name;

long version;

long info;

long instance_size;

struct objc_ivar_list *ivars;

struct objc_method_list **methodLists;

struct objc_cache *cache;

struct objc_protocol_list *protocols;

}

此结构体的首个变量也是isa指针,这说明Class本身也是Objective-C对象。类对象所属的类型(也就是isa所指向的类型)是另外一个类,叫做“元类”(metaclass),用来表述类对象本身所具备的元数据。****“类方法”就定义在此处,因为这些方法可以理解成类对象的实例方法

结构体里还有个变量叫做super_class,它定义了本类的父类。

关于这一块内容我之前也专门写过一篇文章,isa指针,实例对象、类对象、元类等之间的关系在那篇文章里都有详细说明。

在类继承体系里查询类型信息

isMemberOfClass能够判断出对象是否为某个特定类的实例,而isKindOfClass则能判断出对象是否为某类或其派生类的实例。例如:

NSMutableArray *arr = [NSMutableArray array];

BOOL a = [arr isMemberOfClass:[NSMutableArray class]];

BOOL b = [arr isMemberOfClass:[NSArray class]];

BOOL c = [arr isKindOfClass:[NSMutableArray class]];

BOOL d = [arr isKindOfClass:[NSArray class]];

BOOL e = [arr isKindOfClass:[NSMutableDictionary class]];

NSLog(@"%d, %d, %d, %d, %d", a, b, c, d, e);

最后打印出来的结果是:

2017-07-02 11:31:38.427 删掉****[20548:2488416] 0, 0, 1, 1, 0

其实b、 c、 d、 e都没啥毛病,可是按道理 a 难道不应该是1吗?其实不然。arr看似是NSMutableArray的实例对象,其实是NSMutableArray的某一个子类的实例对象,NSMutableArray实际上是一个类簇。关于类簇,我前面的文章Effective Objective-C 2.0——以“类族模式”隐藏实现细节有专门写过。

我们如果将对象a所属的类打印出来的话:

NSMutableArray *arr = [NSMutableArray array];

BOOL a = [arr isMemberOfClass:[NSMutableArray class]];

NSLog(@"%d", a);

NSLog(@"%@, %@", [arr class], [NSMutableArray class]);

其结果如下:

2017-07-02 11:50:21.774 删掉****[20606:2500604] 0

2017-07-02 11:50:21.774 删掉****[20606:2500604] __NSArrayM, NSMutableArray

我们发现,arr所属的类其实是__NSArrayM,所以[arr isMemberOfClass:[NSMutableArray class]]肯定就是NO了。

如上是介绍了Objective-C中的类型信息查询方法,类型信息的查询都是在运行期进行的,首先使用isa指针获取对象所属的类,然后通过super_class指针在继承体系中游走

前面我讲到,可以通过isMemberOfClass和isKindOfClass来判断某实例对象是否为某类或其派生类的实例。如果我们要比较类对象的等同性,那么可以使用 == 操作符,而不要使用比较Objective-C对象时常用的“isEqual:”方法。至于为啥不使用“isEqual:”方法,可以参考这篇文章Effective Objective-C 2.0——理解“对象等同性”这一概念;可以使用 == 操作符,是因为类对象是“单例”(singleton),在应用程序范围内,每个类的Class都仅有一个实例。也就是说,另外可以精确判断出对象是否为某类实例的办法是:

BOOL b = [arr class] == [NSMutableArray class];

但是,即便能这么做,我们也应该尽量使用类型信息查询方法,而不应该直接通过 == 操作符来直接比较两个类对象是否等同,因为使用类型信息查询方法可以正确处理那些使用了消息传递机制的对象

比如,某个对象A可能会把其收到的所有选择子都转发给另外一个对象B。这样的对象A叫做proxy,此种对象均以NSProxy为基类(NSProxy这个类我之前也没怎么接触过,下一篇文章将重点介绍一下这个基类)。通常情况下,如果在此种proxy对象(即对象A)上调用class方法,那么返回的是proxy对象本身(此类是NSProxy的子类),而非接收proxy的对象(即对象B)所属的类。然而,若是改用isKindOfClass这样的类型信息查询方法,那么proxy对象(即对象A)就会把这条消息转给“接收peoxy的对象”(proxied object,即对象B)。也就是说,这条消息的返回值与直接在接收proxy的对象(即对象B)上面查询其类型所得的结果相同。因此,对于对象A,通过isKindOfClass方法查出来的类对象与通过class方法所返回的那个类对象不同,class方法所返回的类表示NSProxy的某个子类,而非对象B所属的类。

总结

1,每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。

2,如果对象的类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。

3,尽量使用类型信息查询方法来确定对象类型,而不要直接比较对象,因为某些对象可能实现了消息转发功能。

本文分享自微信公众号 - iOS小生活(iOSHappyLife)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这