iOS 探究 | 第六篇 Equality(即 ==,isEqual,isEqualToString)详细探究

探究系列已发布文章列表,有兴趣的同学可以翻阅一下:

第一篇 | iOS 属性 @property 详细探究

第二篇 | iOS 深入理解 Block 使用及原理

第三篇 | iOS 类别 Category 和扩展 Extension 及关联对象详解

第四篇 | iOS 常用锁 NSLock ,@synchronized 等的底层实现详解

第五篇 | iOS 全面理解 Nullability

------- 正文开始 -------

引言

开发过程中,我们经常需要判断一个对象是不是我们需要处理的对象,以及是否满足我们进一步执行程序的条件。这个时候我们就需要通过一些方法进行判断。==isEqual: 及其他相等性(Equality)判断方法应运而生。


常用介绍

isEqual:isEqualToString:== 的区别

  • ==
  1. 对于基本数据类型,比较的是值;
  2. 对于对象类型,判断两个对象的内存地址是否相等,相等则返回 YES,不相等则返回 NO;
  • isEqual: NSObject 及其子类中指定 isEqual: 方法来确定两个对象是否相等。在它的基本实现中,相等检查只是简单地判断相等标识,如下:

    - (BOOL)isEqual: (id)other {
        return self == other;
    }
    

    然而,一些 NSObject 的子类重写了 isEqual:,因此它们各自重新定义了相等的标准:

    • 如果一个对象最重要的事情是它的状态,那么它被称为值类型,它的 observable 属性被用来确定是否相等。

    • 如果一个对象最重要的事情是它的标识,那么它被称为引用类型,它的内存地址被用来确定是否相等。

    在 Foundation 框架中,下面这些 NSObject 的子类都有自己的相等性检查实现,只要看看它们的 isEqualToClassName: 方法就知道了。它们在 isEqualToClassName: 中确定是否相等时,相应类型的对象都遵循值语义,当需要对它们的两个实例进行比较时,推荐使用这些高级方法而不是直接使用 isEqual: 进行比较。具体类及方法如下:

    • NSValue -isEqualToValue:
    • NSArray -isEqualToArray:
    • NSAttributedString -isEqualToAttributedString:
    • NSData -isEqualToData:
    • NSDate -isEqualToDate:
    • NSDictionary -isEqualToDictionary:
    • NSHashTable -isEqualToHashTable:
    • NSIndexSet -isEqualToIndexSet:
    • NSNumber -isEqualToNumber:
    • NSOrderedSet -isEqualToOrderedSet:
    • NSSet -isEqualToSet:
    • NSString -isEqualToString:
    • NSTimeZone -isEqualToTimeZone:

    注意: isEqualToClassName: 方法不接受 nil 作为参数,如传 nil 编译器会给出警告,而 isEqual: 接受(如果传入 nil 则返回 NO )。

  • isEqualToString: NSString 是一个很特殊的类型,先看下面代码:

NSString *a = @"Hello";
NSString *b = @"Hello";

// YES
if (a == b) {
    NSLog(@"a == b is Yes"); 
}

// YES
if ([a isEqual:b]) {
    NSLog(@"a isEqual b is Yes");
}

// YES
if ([a isEqualToString:b]) {
    NSLog(@"a isEqualToString b is Yes"); 
}

会发现上面的三种判断都是 YES ,为什么 == 判断也是 YES

这是因为苹果采用了 字符串驻留(String Interning) 的优化技术。在这种情况下,创建的字符串在内部被视为字符串字面量。运行时不会为这些字符串分配不同的内存空间。

注意: 所有这些针对的都是静态定义的不可变字符串。

另外, Objective-C 选择器的名字也是作为驻留字符串储存在一个共享的字符串池当中。对于通过来回传递消息来操作的语言来说,这是一个重要的优化。能够通过指针是否相等来快速检查字符串,这对运行时性能有很大的影响。

  • Hashing

对于面向对象编程来说,对象相等性检查的主要用例,就是确定一个对象是不是一个集合的成员。 为了确保在 NSDictionaryNSSet 集合中的检查速度,具有自定义相等实现的子类应该以满足以下条件的方式实现 hash 方法:

  1. 对象相等具有交换性:([a isEqual:b] ⇒ [b isEqual:a])
  2. 如果对象相等,那么它们的哈希值也必须相等:([a isEqual:b] ⇒ [a hash] == [b hash])
  3. 但是,反过来则不成立:即两个对象可以具有相同的哈希值,但彼此不相等:([a hash] == [b hash] ¬⇒ [a isEqual:b])
  • Tagged Pointers

Tagged Pointer 功能主要有如下三点:

  1. Tagged Pointer 用于存储小对象,例如 NSNumber ,NSString 和 NSDate 等;
  2. Tagged Pointer 值不再是地址,而是实际值。因此,它不再是真正的对象,它只是一个伪指针,一个 64 位的二进制。因此,它的内存不存储在堆中,不需要 malloc 和 free ;
  3. 内存读取效率提高 3 倍,创建速度提高 106 倍;

OS X 和 iOS 都在 64 位代码中使用 Tagged Pointer 对象。在 32 位代码中没有使用 Tagged Pointer 对象,尽管在原则上这并不是不可能。开源的 objc4-818.2/runtime/objc-internal.h 有详细的定义及介绍:

{
    // 60-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,

    // 60-bit reserved
    OBJC_TAG_RESERVED_7        = 7, 

    // 52-bit payloads
    OBJC_TAG_Photos_1          = 8,
    OBJC_TAG_Photos_2          = 9,
    OBJC_TAG_Photos_3          = 10,
    OBJC_TAG_Photos_4          = 11,
    OBJC_TAG_XPC_1             = 12,
    OBJC_TAG_XPC_2             = 13,
    OBJC_TAG_XPC_3             = 14,
    OBJC_TAG_XPC_4             = 15,
    OBJC_TAG_NSColor           = 16,
    OBJC_TAG_UIColor           = 17,
    OBJC_TAG_CGColor           = 18,
    OBJC_TAG_NSIndexSet        = 19,
    OBJC_TAG_NSMethodSignature = 20,
    OBJC_TAG_UTTypeRecord      = 21,

    // When using the split tagged pointer representation
    // (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where
    // the tag and payload are unobfuscated. All tags from here to
    // OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache
    // builder is able to construct these as long as the low bit is
    // not set (i.e. even-numbered tags).
    OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set

    OBJC_TAG_Constant_CFString = 136,

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263,

    OBJC_TAG_RESERVED_264      = 264
}

本质上来说 Tagged Pointer 就是 Tag + Data 组合的一个内存占用 8 个字节 64 位的伪指针:

  • Tag 为特殊标记,用于区分是否是 Tagged Pointer 指针以及区分 NSNumber、NSDate、NSString 等对象类型;
  • Data 为对象对应存储的值。

在运行效率上,很多涉及 Tagged Pointer 类型相关功能,苹果都有针对性的进行了优化,因此执行起来效率特别高,具体可在源码中搜索 isTaggedPointer 进一步查看。

另外,在源码 objc-runtime-new.mm 中有一段注释对 Tagged pointer objects 进行了解释,具体如下:

/***********************************************************************
* Tagged pointer objects.
*
* Tagged pointer objects store the class and the object value in the 
* object pointer; the "pointer" does not actually point to anything.
* 
* Tagged pointer objects currently use this representation:
* (LSB)
*  1 bit   set if tagged, clear if ordinary object pointer
*  3 bits  tag index
* 60 bits  payload
* (MSB)
* The tag index defines the object's class. 
* The payload format is defined by the object's class.
*
* If the tag index is 0b111, the tagged pointer object uses an 
* "extended" representation, allowing more classes but with smaller payloads:
* (LSB)
*  1 bit   set if tagged, clear if ordinary object pointer
*  3 bits  0b111
*  8 bits  extended tag index
* 52 bits  payload
* (MSB)
*
* Some architectures reverse the MSB and LSB in these representations.
*
* This representation is subject to change. Representation-agnostic SPI is:
* objc-internal.h for class implementers.
* objc-gdb.h for debuggers.
**********************************************************************/

详细介绍此处就不再翻译,重点说一下:

  • 1 bit 用来标识是否是 Tagged Pointer;
  • 3 bits 用来标识类型;
  • 60 bits 负载数据容量 即存储对象数据;

注意: 此处不对 Tagged Pointer 做深入详细介绍,有兴趣的同学可以 Google 一下 Tagged Pointer,有很多优秀的文章介绍的非常详尽。

由于 Tagged Pointer 是一个伪指针,而不是一个真正的对象,因此它并没有 isa 指针。所以当我们通过 LLDB 打印 Tagged Pointer 对应的 isa 指针时,程序会报错误提示:

error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory

而当针对 Tagged Pointer 需要使用到类似 Objective-C 对象的 isa 指针功能时,可以通过调用 isKindOfClassobject_getClass 实现判断及其他操作。


拓展知识

  • 理解“指针”和“指针值”,以及“直接引用”和“间接引用”的区别

存放变量地址的变量我们称之为“指针变量”,简单的说变量 p 中存储的是变量 a 的地址,那么 p 就可以称为是指针变量,或者说 p 指向 a 。当我们访问 a 变量的时候其实是程序先根据 a 取得 a 对应的地址,再到这个地址对应的存储空间中拿到 a 的值,这种方式我们称为“直接引用”。

而当我们通过 p 取得 a 的时候首先要先根据 p 转换成 p 对应的存储地址,再根据这个地址到其对应的存储空间中拿到存储内容,它的内容其实就是 a 的地址,然后根据这个地址到对应的存储空间中取得对应的内容,这个内容就是 a 的值,这种通过 p 找到 a 对应地址再取值的方式称为“间接引用”。

  • iOS 内存分区:

堆:存放对象,Objective-C 通过 newalloc 创建的对象,C 通过 malloc 创建的对象。由开发者进行管理,动态分配和释放,内存不连续,速度相对较慢。

栈:存放局部变量,函数的参数,由系统分配和释放,内存连续,速度相对较快。

BSS 段:存放未初始化的全局变量和静态变量,内存一直存在,程序结束后由系统释放。

Data 段:存放已经初始化的全局变量及静态变量,常量。其属于静态内存分配,分为只读数据段(常量区)和读写数据段,程序结束后由系统释放。

代码区:用于存放程序运行时的代码,代码会被编译成二进制存进内存的程序代码区。


总结

Equality 是日常开发使用非常频繁,用以判断并决定程序的运行流程,如果处理不够准确,可能会出现一些意想不到的 bug。因此熟练掌握它的使用可以让我们规避一些非常低级的错误。以上就是本文对 iOS Equality 相关知识点的介绍,希望这篇文章对你有所帮助,感谢阅读。


参考资料:

Implementing Equality and Hashing

Equality


关于技术组

iOS 技术组主要用来学习、分享日常开发中使用到的技术,一起保持学习,保持进步。文章仓库在这里:https://github.com/minhechen/iOSTechTeam
微信公众号:iOS技术组,欢迎联系进群学习交流,感谢阅读。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容