观“编写高质量iOS与OC 代码的52个有效方法”有感(三)· 接口与API设计

iPhone.jpg

我们在写程序时,都希望其中一部分代码用于后续项目,又或者把某些代码发布出来,供他人使用。因此,我们构建项目的要注意:

15、用前缀避免命名空间冲突

  • 因为Objective-C没有其他语言那种内置的命名空间机制(namespace)。
    所以我们在起名时要设法避免潜在的命名冲突,否则就容易重名,发生命名冲突,程序链接过程就会出错。比如下面:
命名冲突.png
  • 避免问题:
    变相实现命名空间:为所有名称都加上适当前缀(一般是与公司或者应用程序有关联的名字)。命名时要注意:因为Apple宣传其保留使用所有“两字母前缀”的权利,那我们自己选的前缀应该是三个字母的。

16、提供“全能初始化方法”

  • 所有对象都要初始化。但是创建类实例的方式有的时候不止一种。
    例如:UITableViewCell,初始化时,需要指明其样式以及标识符,标识符区分不同类型的单元格。
    这种对象的创建成本比较高,绘制表格的时候可依照标识符复用,提升程序效率。我们把这种可为对象提供必要信息以便能完成工作的初始化方法叫做“全能初始化方法”
  • 试一试
    首先创建一个类“ZSCManager”然后给其添加2个只读的属性,这样外界就无法更改了,再提供初始化方法设置这两个属性:
 #import <Foundation/Foundation.h>

@interface ZSCManager : NSObject

@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *phone;

- (id)initWithManageName:(NSString *)name
                   phone:(NSString *)phone;

@end

//.m的实现
#import "ZSCManager.h"

@implementation ZSCManager


- (id)init {
    return [self initWithManageName:@"未知名字" phone:@"110"];
}


- (id)initWithManageName:(NSString *)name
                   phone:(NSString *)phone {
    if (self = [super init]) {
        _name = name;
        _phone = phone;
    }
    return self;
}

@end
  • 注意:
    设置默认值的那个init方法调用了“全能初始化方法”。
    若是存储方式变了(比如名字和手机号放在某个结构中),则init与全能初始化方法设置数据的代码就要修改。
    简单的情况没有太大问题,如果类的初始化方法有很多种,而且初始化的数据较为复杂,那么这样做就麻烦的多。很容易忘记修改某个初始化方法,造成初始化方法之间相互不一致。

  • 假如现在创建个名叫 ZSCLeader 的类,让其成为 ZSCManager 的子类。那么新类的初始化方法要怎么写呢?

#import "ZSCManager.h"

@interface ZSCLeader : ZSCManager

- (id)initWithLeaderName:(NSString *)name;

@end

//.m的实现
#import "ZSCLeader.h"

@implementation ZSCLeader

- (id)initWithManageName:(NSString *)name
                   phone:(NSString *)phone {
    return [self initWithLeaderName:name];
}

- (id)initWithLeaderName:(NSString *)name {
    return [super initWithManageName:name phone:nil];
}

@end
  • ZSCLeader类的全能初始化方法调用了超类的全能初始化方法,
    再看 ZSCManager 类的实现,也会发现 ZSCManager 也调用了超类的全能初始化方法,因此说明:全能初始化的方法调用链一点要维系。
    调用者创建 ZSCLeader 会通过 initWithLeaderName 或者 init的方法。所以要覆写超类的全能初始化方法,否则init方法创建就会有问题。

  • 有时候我们不想覆写超类的全能初始化方法,可以理解为:我们认为这是调用者自己犯错了。这样子,我们就需要覆写超类的全能初始化方法并抛出异常:

- (id)initWithManageName:(NSString *)name
                   phone:(NSString *)phone {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"不让你调用此方法,方法名字:initWithManageName:(NSString *)name phone:(NSString *)phone 或者 init" userInfo:nil];
}
  • 然后调用init 或者超类的方法创建时,程序就会崩溃报错
    *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '不让你调用此方法,方法名字:initWithManageName:(NSString *)name phone:(NSString *)phone 或者 init'

  • 我们不想程序崩溃也想知道崩溃的原因,可以这样办:

    @try {
        // 可能会出现崩溃的代码
        ZSCLeader *leader = [[ZSCLeader alloc] init];
    }
    @catch (NSException *exception) {
        // 捕获到的异常exception
        NSLog(@"%@",exception);
        //控制台的输出
        //不让你调用此方法,方法名字:initWithManageName:(NSString *)name phone:(NSString *)phone 或者 init
    }
    @finally {
        // 结果处理
    }
  • 总结:
    • 如果在类中提供一个全能初始化方法,在文档中指明。其他初始化方法均应调用此方法。
    • 若全能初始化方法与超类不同,则需覆写超类中的对应方法。
    • 如果超类的初始化方法不适用子类,那么应该覆写这个超类方法,并在其中抛出异常。

17、实现 description 方法

  • 调试程序时,经常需要打印并查看对象信息。一般都是这样做
    ZSCManager *manager = [[ZSCManager alloc] init];
    NSLog(@"%@",manager);
    可以看到输出为:
    <ZSCManager: 0x610000026b40>
    这样的信息不太有用。只有类的名字和指针地址。

  • 我们在类中覆写 description 方法。

    - (NSString *)description {
        return [NSString stringWithFormat:@"<%@:%p ,\"name : %@  phone : %@\">",[self class],self,_name,_phone];
    

}

这次的输出变得不一样了,对我们也更有用:
      <ZSCManager:0x6080000354e0 ,"name : 未知名字  phone : 110">

- 注意:
  - 实现 description 方法返回一个有意义的字符串,用以描述该实例。
  - 若想在调试时打印出更详尽的对象描述信息,则应该实现 debugDescription 方法。

#18、尽量使用不可变对象
- 设计类的时候,要充分运用属性来封装数据。
属性默认情况是“既可读又可写的”(readWrite)
一般来说,我们有些建模的数据未必需要改变。这样我们最好把其属性设置为readonly。
      @property (nonatomic, readonly, copy) NSString *name;
如果想“暴力”修改,那就通过KVC:
      [manager setValue:@"哈哈" forKey:@"name"];

#19、使用清晰而协调的命名方式
 - 方法和变量名使用“驼峰式大小写命名法”
小写字母开头,其后每个单词首字母大写。
类名也用驼峰命名法,不过其字母要大写,而且前面通常有两三个前缀字母。

- 方法名要言简意赅,从左至右读起来要像个日常用语中的句子才好。
- 方法名里不要使用缩略后的类型名称。
- 给方法起名时的第一要务就是确保其风格与你自己的代码或所要集成的框架相符。

#20、为私有方法名加前缀
- 一个类所要做的事情通常都要比外面看到的更多。
编写类的实现代码时,经常要写一些只在内部使用的方法。
建议应该为这种方法的名称加上某些前缀。
1、有助于调试。
2、容易把公共方法和私有方法区别开。
- 不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的,建议用p_XXXX这样。p代表 “private”(私有的)。

#21、理解Objective-C错误模型
- 很多编程语言都有“异常”(exception)机制,Objective-C 也不例外。
       @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"XXX" userInfo:nil];
OC语言现在采用的方法:只在极其罕见的情况下抛出异常,异常抛出之后,无须考虑恢复问题,应用会立刻退出。不需要编写“异常安全”代码。
-  Objective-C在出现“非致命错误”时。这样处理的:
令方法返回 nil/0,或者是使用NSError。
 - 假如调用者发现方法的返回值是nil,就可以确定其中发生了错误。就可以采取相对应的措施。
 - NSError 用法灵活。我们可以把导致错误的原因汇报给调用者。
NSError domain(错误范围,类型为字符串)
产生错误的根源,比如从URL中解析或取得数据时出错了,就会使用NSURLErrorDomain来表示错误范围。
Error code(错误码,类型为整数)
独有的错误代码,错误情况通常采用 enum 来定义。比如 HTTP 请求出错时,可能会把HTTP状态码设为错误码。
User info (用户信息,类型为字典)
关于错误的额外信息。
- 参考 AFNetworking 中对NSError的使用,直接把错误信息放在 NSError 对象里,经由“输出参数”返回给调用者。

#22、理解 NSCopying 协议
- 使用对象时经常需要拷贝它。
在Objective-C中,如果想令自己的类支持拷贝操作,就要实现 NSCopying 协议。协议里只有一个方法:
      - (id)copyWithZone:(nullable NSZone *)zone
NSZone是什么呢?
以前开发程序时,会把内存分为不同的“区”(zone),对象会创建在某个区里面。
现在不用了,每个程序只有一个“默认区”,不用担心其中的 zone 参数。
  • (id)copyWithZone:(nullable NSZone *)zone {
    ZSCManager *copy = [[[self class] allocWithZone:zone] initWithManageName:_name phone:_phone];
    return copy;
    }
直接把待拷贝的对象交给“全能初始化方法”,令其执行所有初始化工作。

- 若是自定义的对象分为可变版本和不可变版本,那么就要同时实现 NSCopying 和 NSMutableCopying 协议。
- 复制对象时需要决定采用浅拷贝还是深拷贝,一般情况下执行浅拷贝。
深拷贝:在拷贝自身时,将其底层数据也一并复制过来。
浅拷贝:只拷贝容器对象本身,而不复制其中数据。
注意:容器内的对象未必都能拷贝,调用者也不一定想在拷贝容器的时候一并拷贝其中的每个对象。
因为:没有专门定义深拷贝的协议。
所以:具体执行方式由每个类来确定,只需要你自己决定自己写的类是否要提供深拷贝的方法。如果你所写的对象需要深拷贝,那么可以考虑新增一个专门执行深拷贝的方法。

### 接下来也将会继续整理。如果觉得有用请点个喜欢!
### 您的支持将是我继续写作的动力!谢谢。

[观“编写高质量iOS与OC X代码的52个有效方法”有感(一)· 熟悉Objective-C](//www.greatytc.com/p/0876e60d3160)
[观“编写高质量iOS与OC X代码的52个有效方法”有感(二)· 对象、消息、运行时](//www.greatytc.com/p/1aac4fb98888)
[观“编写高质量iOS与OC X代码的52个有效方法”有感(三)· 接口与API设计](//www.greatytc.com/p/5d9e61db2a01)
[观“编写高质量iOS与OC X代码的52个有效方法”有感(四)· 协议与分类](//www.greatytc.com/p/0944e1e276ff)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容