《Effective Objective-C 2.0 》 阅读笔记 item8

Posted 凉亭下

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Effective Objective-C 2.0 》 阅读笔记 item8相关的知识,希望对你有一定的参考价值。

第8条:理解“对象等同性”这一概念

1. 对象等同性

“==”操作比较的是两个指针本身,而不是其所指的对象。
应该使用NSObject协议中声明的“isEqual:”方法来判断两个对象的等同性。其中,某些对象提供了特殊的“等同性判定方法”,如判断NSString类对象的“isEqualToString:”方法。

2. 判断等同性的关键方法

NSObject协议中有两个用于判断等同性的关键方法:

- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

NSObject类对这两个方法的默认实现是:当且仅当其“指针值”(pointer value)完全相等时,这两个对象才相等。
若想在自定义的对象中正确覆写这些方法,就必须先理解其约定。如果“isEqual:”方法判定两个对象相等,那么其hash方法也必须返回同一个值。但是,如果两个对象的hash方法返回同一个值,那么“isEqual:”方法未必会认为两者相等。

*** isEqual:方法的实现 ***

/* 某个类  */
@interface EOCPerson : NSObject
@property (nooatomic, copy) NSString *firstName;
@property (nooatomic, copy) NSString *lastName;
@property (nooatomic, assign) NSUInteger age;
@end

/* isEqual:方法的实现  */
- (BOOL)isEqual:(id)object
    // 判断两个对象的指针
    if (self == object) return YES;
    // 判断两个对象所属的类
    if ([self class] != [object class]) return NO;
    
    // 检查两个对象的属性是否都相等
    EOCPerson *otherPerson = (EOCPerson*)object;
    if (![_firstName isEqualToString:otherPerson.firstName])
        return NO;
    if (![_lastName isEqualToString:otherPerson.lastName])
        return NO;
    if (_age != otherPerson.age)
        return NO;
    return YES;

*** hash方法的实现 ***
根据等同性约定:若两对象相等,则其hash值也相等,但是两个hash值相同的对象却未必相等。 

/* 第一种实现方式  */
- (NSUInteger)hash
    return 1337;

/*
  评价:在collection(集合类)中使用这种对象将产生性能问题,因为collection在检索哈希表(hash table)时,会用对象的哈希码做索引。
 */
  
/* 第二种实现方式  */
- (NSUInteger)hash
    NSString *stringToHash = [NSString stringWithFormat:@"%@:%@:%i", _firstName, _lastName, age];
    return [stringToHash hash];

/*
  评价:将NSString对象中的属性都塞入另外一个字符串中,然后令hash方法返回该字符串的hash值。
  这样做,符合“两个相等的对象返回相同的hash值”的约定,但是还需要负担创建字符串的开销,所以比返回单一值要慢。而且,把这种对象添加到collection中时,也会产生性能问题,因为要想添加,必须先计算其hash值。
 */
 
 /* 第三种实现方式  */
- (NSUInteger)hash
    NSUInteger firstNameHash = [_firstName hash];
    NSUInteger lastNameHash = [_lastName hash];
    NSUInteger ageHash = _age;
    return firstNameHash ^ lastNameHash ^ ageHash;

/*
  评价:这种做法,既能保持较高效率,又能使生成的hash值至少位于一定范围之内,而不会过于频繁地重复。
  当然,此算法生成的hash值还是会碰撞(collision),不过至少可以保证hash值有多种可能的取值。
 */

 

总结:编写hash方法时,应该用当前的对象做做实验,以便在减少碰撞频度与降低运算复杂程度之间取舍。

3. 特定类所具有的等同性判定方法

除了NSString之外,NSArray与NSDictionary类也具有特殊的等同性判定方法(分别为“isEqualToArray:”和“isEqualToDictionary:”方法)。
在编写特定类的判定方法时,也应一并覆写“isEqual:”方法。

/* EOCPerson类  */
// 在自己编写的判定方法中不用检测参数类型
- (BOOL)isEqualToPerson:(EOCPerson*)otherPerson
    // 判断两个对象的指针
    if (self == object) return YES;
    
    // 检查两个对象的属性是否都相等
    EOCPerson *otherPerson = (EOCPerson*)object;
    if (![_firstName isEqualToString:otherPerson.firstName])
        return NO;
    if (![_lastName isEqualToString:otherPerson.lastName])
        return NO;
    if (_age != otherPerson.age)
        return NO;
    return YES;


// 覆写isEqual:方法
// 如果是两对象所属的类就调用自己编写的判定方法,否则交由超类来判断。
- (BOOL)isEqual:(id)object
    if ([self class] == [object class])
        return [self isEqualToPerson:(EOCPerson*)object];
    else
        return [super isEqual:object];
    

 

4. 等同性判定的执行深度

创建等同性判定方法时,需要决定是根据整个对象来判断等同性,还是仅仅根据其中几个字段来判断。前者叫做“深度等同性判定”,后者由于有名为“唯一标识符”的属性,可以根据标识符来判断等同性,尤其是该属性声明为readonly的时候。
是否需要在等同性判定方法中检测全部字段取决于受测对象。只有类的编写者才可以判定两个对象实例在何种情况下应判定为相等。

5. 容器中可变类的等同性

在collection中放入可变类对象的时候,需要确保hash值不是根据对象的“可变部分”计算出来的,或是保证放入collection之后就不再改变可变类对象的内容了。

要点

  • 若想检测对象的等同性,请提供“isEqual:”和“hash”方法。
  • 相同的对象必须具有相同的hash值,但是两个hash值相同的对象却未必相同
  • 不要盲目地逐个检测每条属性,而是应该依照具体需求来制定检测方案。
  • 编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。

《Effective Objective-C 2.0》读后总结 之四

写在前面的话:

1、从现在,这一刻,开始努力,动起来!

2、《Effective Objective-C 2.0》这是一本非常实在的书,各个章节用具体的例子告知读者如此这般的缘由!

3、书已经买了很久,也隔三差五的看一下,本次是按照本书章节,总结本书内容以及自己的体会!

4、愿共勉之!

第37条:理解“块”这一概念

37.1:块是C、C++、Objectivve-C中的语法闭包;

37.2:块可以接受参数,也可以返回值;

37.3:块可以分配在栈或堆上,也可以是全局的。分配在栈上的块可拷贝到堆里,这样的话,就和标准deObjective-C对象一样,具备引用计数了!

第38条:为常用的块类型创建typedef

38.1:以typedef重新定义块类型,可另块变量用起来更加简单;

38.2:定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型相冲突;

38.3:不妨为同一个块签名定义多个类型别名。如果要重构的代码使用了块类型的某个别名,那么只需要修改响应typedef中的块签名即可,无须改动其他rypedef。

第39条:用handler块降低代码分散程度

39.1:在创建对象时,可以使用内联的handler块将相关业务逻辑一并声明;

39.1:在有多个实例对象需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若改用handler块来实现,则可直接将块与相关对象放在一起;

39.2:涉及API时如果用到handler块,那么可以增加一个参数,使调用者可通过此参数来决定应该把块安排在哪个队列上执行。

第40条:用块引用其所属对象时不要出现保留环 

40.1:如果块所捕获的对象直接或间接地保留了块本身,那么就得当心保留还的问题;

40.2:一定要找个适当的时机解除保留环,而不能把责任推给API的的调用者。

第41条:多用派发队列,少用同步锁

41.1:派发队列可用来表述同步语义(synchronization semantic),这种做法要比使用@sychornized块或NSLock对象简单;

41.2:将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程;

41.3:使用同步队列及栅栏块,可以令同步行为更高效!

第42条:多用GCD,少用performSelector系列方法 

42.1:performSelector系列方法在内存管理方面容易有疏失。它无法确定将要执行的选择子具体是什么,因而ARC编译器也就无法插入使用的内存管理方法;

42.2:performSelector系列方法所能处理的选择子太过局限了,选择子的返回值类型以及发给方法的参数个数都收到限制;

42.3:如果想把任务放在另一个线程上执行,那么最好不要用performSelector系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现。

第43条:掌握GCD及操作队列的使用时机

43.1:在解决多线程和任务管理问题时,派发队列并非唯一解决方案;

43.2:操作队列提供了一套高层的Objective-C API,能实现纯GCD所具备的绝大部分功能,而且还能完成一些更为复杂的操作,那些操作若改用GCD来实现,则需另外编写代码!

第44条:通过Dispatch Group机制,根据系统资源状况来执行任务

44.1:一系列任务可归入一个dispatch group之中。开发者可以在这组任务执行完毕时获得通知。

44.2:通过disaptch group,可以在并发式派发队列里同时执行多项任务。此时GCD会根据系统资源状况来调度这些并发执行的任务。开发者若自己来实现此功能,则需编写大量代码!

第45条:使用dispatc_once来执行秩序运行一次的线程安全代码

45.1:经常需要编写“只需执行一次的线程安全代码”(thread-safe single-code execution)。通过GCD所提供的diapstch_once函数,很容易就能实现此功能;

45.2:标记应该声明在static或gloabal作用域中,这样的话,在把只需执行一次的块传给dispatch_once函数时,传进去的标记也是相同的。

第46条:不要使用dispatch_get_current_queue

46.1:dispatch_get_current_queue函数的行为常常与开发者所预期的不同。此函数已经废弃,只能做调试之用。

46.2:由于派发队列是按照层级来组织的,所以无法单用某个队列对象来描述“当前队列”这一概念。

46.3:dispatch_get_current_queue函数用于解决由不可重入的代码所引发的死锁,然而能用此函数解决的问题,通常也能改用“队列特定数据”来解决!

 

本章主要是块和大中枢派发,可以说是目前OC变成的基础和核心!这部分相关的实例使用在项目开发中进一步完善和提升!!!

 

 

 

写在结尾处的话:每天努力一点点,也许不明显,但终究会有所帮助!

非常汗颜,整个九月份没有更新!愿继续努力!

以上是关于《Effective Objective-C 2.0 》 阅读笔记 item8的主要内容,如果未能解决你的问题,请参考以下文章

Effective Objective-C 2.0 — 第14条:理解“类对象“的用意

《Effective Objective-C 2.0》读后总结 之五

《Effective Objective-C 2.0》读后总结 之四

《Effective Objective-C 2.0》读后总结 之三

《Effective Objective-C 2.0 》 阅读笔记 item8

EFFECTIVE OBJECTIVE-C 2.0 TIPS 总结 CHAPTER 1 & CHAPTER 2