美文网首页
接口与API设计

接口与API设计

作者: 飞行员suke | 来源:发表于2017-03-21 15:50 被阅读0次

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

    Objective-C没有其他语言那种内置的命名空间(name space)机制。鉴于此,我们在起名时要设法避免潜在的命名冲突,否则很容易就重名了。应用程序的链接过程会出错,类似如下:

    duplicate symbol _OBJC_METACLASS_$_EOCTheClass in:
        build/something.o
        build/something_else.o
    duplite symbol_OBJC_$_EOCTheClass in :
        build/something.o
        build/something_else.o
    

    唯一避免此问题的唯一办法就是变相实现命名空间:为所有名称加上适当前缀

    1. Apple宣称其保留使用所有"两字母前缀"的权利,所有我们选用的前缀应该是三个字母的。选择与公司名、应用程序相关的前缀。
    2. 我们总是应该给纯C函数和全局变量加上前缀。
    3. 若自己所开发的程序库中用到了第三方库,则应为其中的名称加上前缀

    十六、提供"全能初始化方法"

    可为对象提供必要信息以便其能完成工作的初始化方法叫做"全能初始化方法",也叫指定初始化方法。

    1. 在类中提供一个全能初始化方法,并于文档中指明。其他初始化方法,都应调用此全能初始化方法!

    2. 若子类的全能初始化方法与超类不同,则需覆写超类中的对应方法。如矩形类和正方形类的全能初始化方法不同

       -initWithWidth:(float)width andHeight:(float)height; //矩形的全能初始化方法
       -initWithDimension:(float)dimension;//正方形的全能初始化方法
       在正方形类中,需要覆写掉矩形父类的全能初始化方法,因为该方法不适用于正方形类。
      
    3. 如果超类的初始化方法不适用于子类,那么应该覆写这个方法,并在其中抛出异常

    4. 有时候需要编写多个全能初始化方法。如果某对象的实例有两种完全不同的创建方式,必须分开处理,那么就会出现这种情况。如UI布局中使用nib文件的类。

       -(id)initWithCoder:(NSCoder *)decoder;
      

    十七、实现description方法

    若调试时需要打印出自定义类的全部属性,需要在自定义的类上覆写description方法,也应该像默认的实现那样打印出类的名字和指针地址。 如下代码:

    -(NSString *)description{
        return [NSString stringWithFormat:@"<%@: %p,%@>",
        [self class],
        self,
        @{@"title":_title,
          @"latitude":@(_latitude),
          @"longitude":@(_longitude)}
        ];
    }
    

    如上代码中:用NSDictionary来实现此功能可以令代码更易维护;如果以后还要向类中新增属性,并且要在description方法中打印,那么只需要修改字典内容即可。

    NSObject协议中还有一个方法需要注意,那就是debugDescription。此方法是开发者在调试器中以控制台命令打印对象时才调用的。 使用lldb的po命令, 如 po anObject,就会调用debugDescription方法。

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

    十八、尽量使用不可变对象

    编程实践中,应该尽量把对外公布出来的属性设为只读,而且只在确有必要是才将属性对外公布。

    1. 尽量创建不可变对象
    2. 若某属性仅可于对象内部修改,则在"class-continuation分类"中将其由readonly属性扩展为readwrite属性。
    3. 不要把可变的collection作为属性公开,而应提供相关方法,以此修改对象中的可变collection。

    十九、使用清晰而协调的命名方式

    Objecti-C中,方法和变量名使用驼峰式大小写命名法。

    方法命名:

    1. 如果方法的返回值是新创建的,那么方法名的首个词应是返回值得类型,除非签名还有修饰词,如localizedString。 属性的存取方法则不遵循这种命名方式。
    2. 应该把表示参数类型的名称放在参数前面
    3. 如果方法要在当前对象上执行操作,那么就该包含动词,若执行操作时还需要参数,则应该在动词后面加上一个或多个名词
    4. 不要使用str这种简称,应该用string这样的全称
    5. Boolean属性应该加is前缀。如果某方法返回非属性的Boolean值,那么应该根据其功能,选用has或is当前缀
    6. 将get这个前缀留给哪些借由"输出参数"来保存返回值的方法,比如说,把返回值填充到"C语言式数组"里的那种方法就可以使用get做前缀

    类与协议的命名

    1. 命名方式应该协调一致,如果从其他框架中继承子类,那么务必遵循其命名惯例
    2. 若创建自定义的委托协议,则其名称应该包含委托发起方的名称,后面再跟上Delegate一词

    二十、为私有方法名加前缀

    1. 给私有方法的名称加上前缀,这样可以很容易地将其同公共方法区分开
    2. 不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的。一般可用 "p_" 前缀 或者 "类名_"作为前缀,来有效避免重名的问题

    二十一、理解Objective-C错误模型

    Objective-C语言现在采用的办法是:

    1. 在极其罕见的情况下抛出异常,异常抛出之后,无需考虑恢复问题,而且应用程序此时也应该退出。
    2. 在出现非致命错误时,Objective-C语言所用的编程范式为:令方法返回nil/0,或是使用NSError,以表明其中有错误发生

    NSError的用法更加灵活,因为经由此对象,我们可以把导致错误的原因汇报给调用者。NSError对象里封装了三条信息:

    1. Error domain(错误范围,其类型为字符串)
    2. Error code(错误码,其类型为整数)
    3. User info(用户消息,其类型为字典)

    在设计API时
    NSError的第一种常见用法是 通过委托协议来传递错误。
    有错误发生时,当前对象会把错误信息经由协议中的某个方法传递给其委托对象(delegate)。 如下:

    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
    

    NSError的另一种常见方法是:经由方法的"输出参数"返回给调用者。如下:

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

    实际上,在使用ARC时,编译器会把方法签名中的NSError**转换成NSError *__autoreleasing*,也就是说,指针所指的对象会在方法执行完毕后自动释放。

    //EOCErrors.h
    extern NSString *const EOCErrorDomain;
    
    typedef NS_ENUM(NSUInteger,EOCError){
        EOCErrorUnknown                     = -1,
        EOCErrorInternalInconsistency       = 100,
        EOCErrorGeneralFault                = 105,
        EOCErrorBadInput                    = 500,
    };
    //EOCErrors.m
    NSString *const EOCErrorDomain = @"EOCErrorDomain";
    

    最好能为自己的程序库中所发生的错误指定一个专用的"错误范围"字符串,使用此字符串创建NSError对象,并将其返回给库的使用者。使用枚举类型来表示错误码。

    二十二、理解NSCopying协议

    NSCopying协议只有一个方法:

    -(id)copyWithZone:(NSZone *)zone;
    

    以前开发程序时,会根据NSZone把内存分成不同的"区"(Zone),而对象会创建在某个区里面。现在不用了,每个程序只有一个区,"默认区"。所以尽管必须实现这个方法,但可以不必担心其中的zone参数了

    #import <Foundation/Foundation.h>
    NS_ASSUME_NONNULL_BEGIN
    
    @interface EOCPerson : NSObject<NSCopying>
    @property(nonatomic,copy,readonly) NSString *firstName;
    @property(nonatomic,copy,readonly) NSString *lastName;
    
    -(id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
    -(void)addFriend:(EOCPerson *)person;
    -(void)removeFriend:(EOCPerson *)person;
    
    @end
    NS_ASSUME_NONNULL_END
    

    实现

    #import "EOCPerson.h"
    
    
    @implementation EOCPerson{
        NSMutableSet *_friends;
    }
    
    -(id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName{
        if(self = [super init]){
            _firstName = [firstName copy];
            _lastName = [lastName copy];
            _friends = [NSMutableSet new];
        }
        return self;
    }
    
    -(void)addFriend:(EOCPerson *)person{
        [_friends addObject:person];
    }
    
    -(void)removeFriend:(EOCPerson *)person{
        [_friends removeObject:person];
    }
    
    -(id)copyWithZone:(NSZone *)zone{
        EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];
        copy->_friends = [_friends mutableCopy];
        //使用->语法,因为friends并非属性,只是个内部使用的实例变量
        return copy;
    }
    
    @end
    

    上面代码中,[_friends mutableCopy];是因为NSMutableSet实现了NSMuatbleCopying协议。它也只有一个方法、

    -(id)mutableCopyWithZone:(NSZone*)zone;
    

    对于不可变的NSArray和可变的NSMutableArray来说,下列关系总是成立的:
    -[NSMutableArray copy] ==> NSArray
    -[NSArray mutableArray] ==> NSMutableArray

    要点:
    1. 若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议
    2. 如虹自定义的对象可分为可变版本与不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议
    3. 赋值对象是需决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝
    4. 如果你所写的对象需要深拷贝,那么可以考虑新增一个专门执行深拷贝的方法

    相关文章

      网友评论

          本文标题:接口与API设计

          本文链接:https://www.haomeiwen.com/subject/dcjgnttx.html