《Effective Objective-C 2.0》读书笔记---第四章

前言:


OC不支持多重继承,因而我们把某个类应该实现的一系列方法定义在协议里面,协议除了实现委托模式,还能很好地描述接口。


利用“分类”(Category),我们无需继承子类即可直接为当前类添加方法。


第23条:通过委托与数据源协议进行对象间通信


如果要向外界公布此类实现了某协议,那么就在接口中声明,而如果这个协议是个委托协议的话,那么通常只会在类的内部使用,因此在类扩展中声明。


如果要在委托对象上调用可选方法,那么必须提前使用类型细信息查询方法来判断这个委托对象能否响应相关选择器。


在调用delegate对象中的方法时,总是应该把发起委托的实例也一并传入方法中,比如说我们的使用多个alert的时候,就可以根据传回的alert参数判断当前的alert是哪个了。


在实现委托模式和数据源模式的时候,如果协议中的方法是可选的,那么就会写出一大批类似下面这样的代码来:

if ([_delegate respondsToSelector:          @selector(someClassDidSomething:)])

{    [_delegate someClassDidSomething];}

这样不仅多余,还影响效率,因此我们可以把委托对象是否响应某个协议方法这一信息缓存起来。


例如返回进度的回调会很频繁的被调用,每次去判断委托有没有实现这个方法就会很多余,这样就可以用c语言中的位域来解决,如下:

@interface EOCNetworkFetcher ()

 {

    struct {

        unsigned int didReceiveData      : 1;

        unsigned int didFailWithError    : 1;

        unsigned int didUpdateProgressTo : 1;

    } _delegateFlags;}

@end

_delegateFlags有单个位域,每一个位域都与委托协议中的某个可选的方法相对应,使用如下:

// Set flag

_delegateFlags.didReceiveData = 1;

// Check flag

if (_delegateFlags.didReceiveData)

 {    // Yes, flag set}


- (void)setDelegate:(id<EOCNetworkFetcher>)delegate 

{

    _delegate = delegate;

    _delegateFlags.didReceiveData =      [delegate respondsToSelector:                @selector(networkFetcher:didReceiveData:)];

    _delegateFlags.didFailWithError =      [delegate respondsToSelector:                @selector(networkFetcher:didFailWithError:)];

    _delegateFlags.didUpdateProgressTo =      [delegate respondsToSelector:                @selector(networkFetcher:didUpdateProgressTo:)];

}


第24条:将类的实现代码分散到便于管理的数个分类之中


通过OC的分类机制,把类代码按逻辑划入几个分区中,对开发和调试都有好处。如下:


#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;

@property (nonatomic, copy, readonly) NSString *lastName;

@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString*)firstName

            andLastName:(NSString*)lastName;

/* Friendship methods */

- (void)addFriend:(EOCPerson*)person;

- (void)removeFriend:(EOCPerson*)person;

- (BOOL)isFriendsWith:(EOCPerson*)person;

/* Work methods */

- (void)performDaysWork;

- (void)takeVacationFromWork;

/* Play methods */

- (void)goToTheCinema;

- (void)goToSportsGame;

@end


通过分类机制修改后如下:

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;

@property (nonatomic, copy, readonly) NSString *lastName;

@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString*)firstName

            andLastName:(NSString*)lastName;

@end

@interface EOCPerson (Friendship)

- (void)addFriend:(EOCPerson*)person;

- (void)removeFriend:(EOCPerson*)person;

- (BOOL)isFriendsWith:(EOCPerson*)person;

@end

@interface EOCPerson (Work)

- (void)performDaysWork;

- (void)takeVacationFromWork;

@end

@interface EOCPerson (Play)

- (void)goToTheCinema;

- (void)goToSportsGame;

@end


使用分类机制后,依然可以把整个类都定义在一个接口文件中,并将其代码写在一个实现文件里,可是,随着分类数量增加,当前这份实现文件很快就膨胀得无法管理了。此时,可以把每个分类提取到各自的文件中去,如下:

EOCPerson+Friendship(.h/.m)

EOCPerson+Work(.h/.m)

EOCPerson+Play(.h/.m)


NSURLRequest及其可变版本NSMutableURLRequest就是这么做的,与标准的URL请求相比,执行HTTP请求时还需要另外一些信息,例如“HTTP方法”,(HTTP method,GET、POST等)或HTTP头(HTTP header)。


然而却不便从NSURLRequest中继承子类以实现HTTP协议的特殊需求,因为本类包裹了一套操作CFURLRequest数据结构所需的c函数,所有“HTTP方法”都包含在这个结构里。于是,为了扩展NSURLRequest类,把与HTTP有关的方法归入名为NSHTTPURLRequest的分类中,而把与可变版本有关的方法归入名为NSMutableHTTPURLRequest的分类中。这样也就没有使用FTP协议请求的时候使用HTTP方法的疑问了。


最后,将应该视为私有的方法归入名叫Private的分类中,以隐藏实现细节。这个分类的头文件不会随程序库一并公开。


第25条:总是为第三方类的分类名称加前缀


分类机制通常用于向无源码的既有类中新增功能。运行期系统会把分类中所实现的每个方法都加入类的方法列表中,如果类中本来就有此方法,而分类又实现了一次,那么分类中的方法会覆盖原来那一份实现代码,甚至可能发生多次覆盖。


解决方法是:以命名空间来区别各个分类的名称与其中所定义的方法,即给分类和分类方法加前缀。


第26条:勿在分类中声明属性


尽管从技术上说,分类中也可以声明属性,但是除了类扩展,其他分类都无法向类中新增实例变量,因此,它们无法把实现属性所需的实例变量合成出来。(这句话的意思是你即使在分类中添加了属性,在类里面也没有相应的实例变量给你访问,而且你需要自己实现点方法(get,set),例如添加title属性,并没有合成title实例变量给你访问,你只能通过点方法来调用get和set方法,而且这两个方法需要你自己来实现)


在分类中不允许声明为@synthesize,编译器报错。可以声明为@dynamic。


类所封装的全部数据都应该定义在主接口中,这里是唯一能够定义实例变量的地方。分类应该理解成扩展类功能的一种手段,而非封装数据。


第27条:使用“class-continuation分类”隐藏实现细节


class-continuation分类就是类扩展。


OC动态消息系统的工作方式决定了其不可能实现真正的私有方法或私有实例变量。


用法一:


类扩展是唯一能声明实例变量的分类,而且此分类没有特定的实现文件,其中的方法都应该定义在类的主实现文件里。而且类扩展没有名字。


这样做的好处是隐藏实现细节,因为放在公共接口中,即使标识为private也还是会泄漏实现细节。


比如说你有个绝密的类EOCSuperSecretClass,如下:

#import <Foundation/Foundation.h>@class EOCSuperSecretClass;@interface EOCClass : NSObject {@private    EOCSuperSecretClass *_secretInstance;}@end


那么,信息就泄露了。


OC++是OC和C++的混合体,其代码可以用这两种语言来编写。编写OC++代码时,实现文件是.mm扩展名,这个扩展名表示编译器应该将此文件按照OC++来编译。否则就无法引入c++的头文件了,可是只要包含c++头文件的类,都必须以.mm为扩展名,因此,解决方法就是把c++引入到类扩展中,如下:

// EOCClass.h

#import <Foundation/Foundation.h>

@interface EOCClass : NSObject

@end

// EOCClass.mm

#import "EOCClass.h"

#include "SomeCppClass.h"

@interface EOCClass () 

{

    SomeCppClass _cppClass;

}

@end

@implementation EOCClass

@end


用法二:


将公共接口中声明为“只读”的属性扩展为“可读写”,以便在类的内部设置其值。


在类头文件中声明属性为只读的话,在类扩展中声明同名的属性就必须是可读可写,反之,在类扩展中如果声明了属性是可读可写的,在类头文件中同名的属性就必须是只读的,否则编译器报错,也就是如果类头文件和类扩展都定义了同一个属性,那么,这个属性在类头文件中必须是只读,类扩展中必须是可读可写的,这个第18条也讲过。


用法三:


若对象所遵从的协议只应视为私有,则可在类扩展中声明。只会在类的实现中用到的私有方法也可以声明在类扩展中。


第28条:通过协议提供匿名对象


类似如下使用:delegate可以是任意对象,只要遵循EOCDelegate协议即可。只要,具体的类型就隐藏了。因为对象类型并不重要,只要的是它有木有实现某些方法。

@property (nonatomic, weak) id<EOCDelegate> delegate;


NSMutableDictionary的以下方法也是,key可以是任意对象,只要实现了NSCopying协议就行。

- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;


猜你喜欢

转载自blog.csdn.net/junjun150013652/article/details/53790393