[编写高质量iOS代码的52个有效方法](五)接口与API设计(下)

[编写高质量iOS代码的52个有效方法](五)接口与API设计(下)

参考书籍:《Effective Objective-C 2.0》 【英】 Matt Galloway

先睹为快

19.使用清晰而协调的命名方式

20.为私有方法名加前缀

21.理解Objective-C错误模型

22.理解NSCopying协议

目录

第19条:使用清晰而协调的命名方式

类、方法及变量的命名是Objective-C编程的重要环节。其语法结构使得代码读起来和句子一样。名称中一般都带有in、for、with等介词,而其他编程语言则很少使用这些它们认为多余的字眼:

// C++
string text = "The quick brown fox jumped over the lazy dog";
string newText = text.replace("fox","cat");

// Objective-C
NSString *text = @"The quick brown fox jumped over the lazy dog";
NSString *newText = [text stringByReplacingOccurrencesOfString:@"fox" withString:@"cat"];

text.replace(“fox”,”cat”)从字面上无法判断是用fox替换cat还是用cat替换fox,而Objective-C的命名方式虽然长一点,但却非常清晰。

C++或Java更习惯简省的函数名,在这种命名方式下,若想知道每个参数的用途,就得查看函数原型,这会令代码难于读懂,以一个矩形的类为例:

// C++
class Rectangle{
    public:
        Rectangle(float width, float height);
        float getWidth();
        float getHeight();
    private:
        float width;
        float height;
};

// Objective-C
@interface EOCRectangle : NSObject

@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;

- (id)initWithWidth:(float)width andHeight:(float)height;
@end

当创建该类实例时:

// C++
Rectangle aRectangle = new Rectangle(5.0f,10.0f);
// Objective-C
Rectangle *aRectangle = [[Rectangle alloc]initWithWidth:5.0f andHeight:10.0f];

C++代码无法判断5.0f和10.0f分别表示什么,就算能推测出来表示矩形的尺寸,也不知道宽度在先还是高度在先,需要查函数定义才能确定。而Objective-C的代码却很清晰。

虽说长名字可伶代码更为易读,但也不能长得太过分了,应尽量言简意赅:

// 好的方法名
- (EOCRectangle*)unionRectangle:(EOCRectangle*)rectangle
- (float)area

// 不好的方法名
- (EOCRectangle*)union:(EOCRectangle*)rectangle // 不清晰
- (float)calculateTheArea  // 太冗余

给方法命名时的注意事项可以总结为:

  1. 如果方法的返回值是新创建的,那么方法名的首个词应是返回值类型,除非前面还有修饰语。属性的存取方式不遵循这种命名方式。
  2. 应该把表示参数类型的名词放在参数前面。
  3. 如果方法要在当前对象上执行操作,那么就应该包含动词;若执行操作时还需要参数,则应该在动词后面加上一个或多个名词。
  4. 不要使用str这种简称,应该使用string这样的全称。
  5. 返回Bool值的方法应加上has或is前缀。
  6. get这个前缀留给那些借由输出参数来保存返回值的方法。

第20条:为私有方法名加前缀

Objective-C语言没办法将方法标为私有,每个对象都可以响应任意消息。需要开发者在命名惯例中体现私有方法等语义。笔者喜欢用p_作为前缀,p表示private,而下划线可以把这个字母和真正的方法名区隔开。

#import <Foundation/Foundation.h>

@interface EOCObject : NSObject
- (void)publicMethod;
@end

@implementation EOCObject

// 公有方法
- (void)publicMethod{
    // code
}

// 私有方法
- (void)p_privateMethod{
    // code
}
@end

苹果公司喜欢单用一个下划线作私有方法的前缀,所以请不要照苹果公司的办法来做,不然有可能无意间重写父类的同名方法。

#import <UIKit/UIKit.h>

@interface EOCViewController : UIViewController
@end

@implementation EOCViewController
- (void)_resetViewController{
    // code
}
@end

以上代码看起来没有问题,但UIViewController类本身已经实现了一个名叫_resetViewController的方法。这样写的话所有调用都将执行子类中的这个方法。由于超类中的同名方法并未对外公布,除非深入研究这个库,否则根本不会察觉无意间重写了这个方法。

第21条:理解Objective-C错误模型

很多编程语言都有异常(exception)机制,Objective-C也不例外,但需要注意的是,ARC在默认情况下不是异常安全的。这意味着:如果抛出异常,那么本应在作用域末尾释放的对象现在却不会自动释放了。想要生成异常安全的代码,需要打开编译器标志-fobjc-arc-exceptions。

Objecti-C现在所采用的方法是:只在极其罕见的情况下抛出异常,异常抛出之后,无需考虑恢复问题,应用程序直接退出。异常只用于处理严重错误,出现一般错误时,令方法返回nil/0,或使用NSError。

比如初始化无法根据传入的参数来初始化当前实例,那么就令其返回nil/0。

- (id)initWithValue:(id)value{
    if((self = [super init])){
        if(/* 无法用value初始化实例 */){
            self = nil;
        }else{
            // 初始化
        }
    }
    return self;
}

而NSError的用法更加灵活,可以把导致错误的原因回报给调用者。NSError对象里封装了三条信息:Error domain(错误范围,类型为字符串)、Error code(错误码,类型为整数)、User info(用户信息,类型为字典)。

// EOCErrors.h
#import <Foundation/Foundation.h>

// 声明全局变量错误范围
extern NSString *const EOCErrorDomain;

// 错误码
typedef NS_ENUM(NSUInteger, EOCError){
    EOCErrorUnkown = -1,
    EOCErrorInteralInconsistency = 100,
    EOCErrorGeneralFault = 105,
    EOCErrorBadInput = 500,
};

@interface EOCErrors : NSObject

- (BOOL)doSomething:(NSError**)error;

@end

// EOCErrors.m
#import "EOCErrors.h"

NSString *const EOCErrorDomain = @"EOCErrorDomain";

@implementation EOCErrors

- (BOOL)doSomething:(NSError *__autoreleasing *)error{
    if(/* error发生 */){
        if (error) {
        *error = [NSError errorWithDomain:EOCErrorDomain code:EOCErrorGeneralFault userInfo:@{@"EOCErrorUserInfo":@"General fault occurs!"}];
        }
        return NO;
    }
    return YES;
}

@end

这样就能经由输出参数把NSError对象回传给调用者:

NSError *error = nil;
BOOL ret = [object doSomething:&error];
if (error){
    // code
}

第22条:理解NSCopying协议

使用对象时,经常需要拷贝它。在Objective-C中,此操作通过copy方法完成。如果想令自己的类支持拷贝操作,那就要实现NSCopying协议,该协议只有一个方法:

- (id)copyWithZone:(NSZone*)zone

现在每个程序只有一个区了(默认区),所以实现这个方法时不用担心其中的zone参数。copy方法由NSObject实现,该方法只是以默认区为参数来调用copyWithZone:方法。所以我们想的是重写copy方法,真正需要实现的是copyWithZone:方法。

下面是一个实现NSCopying协议的例子:

// EOCPerson.h
#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject<NSCopying>

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

- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
@end

// EOCPerson.m
#import "EOCPerson.h"

@interface EOCPerson()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end

@implementation EOCPerson{
    NSMutableSet *_friends;
}

- (void)addFriend:(EOCPerson *)person{
    [_friends addObject:person];
}

- (void)removeFriend:(EOCPerson *)person{
    [_friends removeObject:person];
}

- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName{
    if ((self = [super init])) {
        _firstName = [firstName copy];
        _lastName = [lastName copy];
        _friends = [NSMutableSet new];
    }
    return self;
}

// 实现NSCopying协议
- (id)copyWithZone:(NSZone *)zone{
    // 以全能初始化方法初始化拷贝对象
    EOCPerson *copy = [[[self class]allocWithZone:zone]initWithFirstName:_firstName lastName:_lastName];
    // 将实例变量拷贝到拷贝对象
    copy->_friends = [_friends mutableCopy];
    return copy;
}

@end

本例中,_friends是使用mutableCopy方法来复制的,此方法来自另一个叫做NSMutableCopying的协议,与NSCopying类似,也只定义了一个方法:

- (id)mutableCopyWithZone:(NSZone*)zone

如果需要返回不可变的拷贝,则应该实现NSCopying协议,而若需要返回可变的拷贝,则应实现NSMutableCopying协议:

[NSMutableArray copy] // 返回NSArray
[NSArray mutableCopy] // 返回NSMutableArray

编写拷贝方法时,还要决定一个问题,就是应该执行深拷贝还是浅拷贝。深拷贝的意思就是:在拷贝对象自身时,将底层数据也一并复制过去。而浅拷贝只拷贝容器本身,而不复制其中数据。Foundation框架中的所有容器在默认情况下都执行浅拷贝。

在EOCPerson那个例子中,若需要深拷贝的话,可以编写一个专供深拷贝的方法:

- (id)deepCopy{
    EOCPerson *copy = [[[self class]alloc]initWithFirstName:_firstName lastName:_lastName];
    // copyItem参数设为YES,则会向容器中每个元素都发送copy消息,用拷贝好的元素创建新set返回给调用者。
    copy->_friends = [[NSMutableSet alloc]initWithSet:_friends copyItem:YES];
    return copy;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值