美文网首页iOS学习iOS菜鸟级开发
编写高质量的iOS代码(四)

编写高质量的iOS代码(四)

作者: Mustard_Buli | 来源:发表于2016-07-06 13:56 被阅读44次

    用前缀避免命名空间冲突

    Objective-C没有其他语言的内置的命名空间(namespace),所以在起名时就要设法避免 潜在的命名冲突。如果发生了naming clash(命名冲突),那么程序的链接过程就会报错。

    • 选择与你公司、应用程序或二者皆有关联的名称作为类名的前缀,并在所有代码中均使用这一前缀。

    Apple宣称其保留使用所有“两字母前缀”(two-letter prefix)的权利,也就是说自己选用的前缀应该是三个字母。

    • 若自己所开发的程序库中用到了第三方库,则应为其中的名称加上前缀。

    提供“指定初始化方法”

    什么是“指定初始化方法”,就是可谓对象提供必要信息以便其能完成工作的初始化方法。
    🌰:在初始化UITabableViewCell的时候,需要指明style以及identifier,用来重复利用。

    • 在类中提供一个指定初始化方法,并于文档里指明。其他初始化方法均应调用此方法。

    如果一个类有多个初始化方法,不过仍然要在其中选定一个作为指定初始化方法,令其他初始化方法都来调用它。NSDate就是个🌰:

     - (id)init;
     - (id)initWithString:(NSString *)string;
     - (id)initWithTimeIntervalSinceNow:(NSTimeInterval)seconds;
     - (id)initWithTimeInterval:(NSTimeInterval)seconds sinceDate:(NSDate *)refDate;
     - (id)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)seconds;
     - (id)initWithTimeIntervalSince1970:(NSTimeInterval)seconds;
    

    在上面几个方法中,“initWithTimeIntervalSinceReferenceDate:”是指定初始化方法,换而言之,其余的方法都要调用它,并且也只有这个方法中才会存储内部数据。这样做的好处就是,当需要改变底层数据存储机制时,只需要修改此方法的代码就好了,无须改动其他初始化方法。

    • 若指定初始化方法和父类不同,则需覆写父类中对应的方法。
    • 如果父类的初始化方法不适用于子类,那么应该覆写这个父类方法,并在其中抛出异常。

    实现description方法

    • 实现description方法返回一个有意义的字符串,用以描述该实例。

    在调试程序时,经常需要打印并查看对象信息,最常用的做法就是:

    NSLog(@"object = %@", object);
    

    在构建需要打印到日志的字符串时,object对象会收到desciption消息,该方法所返回的描述信息将取代“格式字符串”(format string)里的"%@"。🌰:

    NSArray *object = @[@"A string", @(123)];
    NSLog(@"object = %@", object);
    //output:object = ( "A string", 123 )
    

    但是,如果在自定义类上这么做,输出的信息却是这样的:

    object = <MSTPerson: 0x3fd8a1377299>
    

    这个时候就可以通过覆写description方法来获取更多的信息,🌰:


    MSTPerson.h
    MSTPerson.m
    main.m

    按照上面的代码来写,那么MSTPerson就会出书如下格式的信息:


    output
    但是这里建议,在新实现的description方法中,也应该像默认的实现那样,打印出类的名字和地址。
    有个简单的方法,可以在description中输出很多互不相同的信息,那就是借助NSDictionary类的description方法。
    🌰,下面这个类表示某个地点的名称和地理坐标:
    MSTLocation.h
    MSTLocation.m
    main.m
    output
    • 若想在调试时打印出更详尽的对象描述信息,则应实现debugDescription方法。

    都知道在打断点的地方LLDB的"po"命令可以完成打印工作,而打印出来的内容就是所打印的类的debugDescription方法返回的字符串。如果想打印出更具体的指令,那么只需要像上面的做法一样,更新debugDescription方法。
    以MSTLocation为🌰:


    output

    尽量使用不可变对象

    在使用属性时,可以将其声明为readonly,但是默认的情况下,属性是readwrite,这样都是可变的。但是一般的情况下,我们要建模的数据未必需要改变。
    在具体的编程中,应该尽量把对外公布出来的属性设为只读,而且只在确有必要时才将属性对外公布。🌰,要编写一个类来处理地图上的景点,这些点的数据通过某个网络服务来获取。一开始写出来的代码也许就是:


    MSTPointOfInterest.h

    但是这些属性都是在外部不需要更改的,所以应该这样写:


    MSTPointOfInterest.h

    虽然没有setter方法,但是仍要指定内存管理语义(strong, weak之类)的原因就是这样做以后更改为readwrite更方便。

    • 若某属性仅可用于对象内部修改,则在“class-continuation分类”中将其由readonly属性扩展为readwrite属性。

    如果该属性是nonatomic的,这样做可能会产生“竞争条件”(race condition)。在对象内部写入某属性时,对象外的observer也许正在读取该属性。想要避免这个问题,可以在必要时通过“派发队列”(dispatch queue)等手段,将(包括对象内部的)所有数据存取操作都设为同步操作。
    🌰:


    MSTPointOfInterest.m

    现在,只能在内部设置属性值了,但是在外部还是可以通过KVC的方式来设置这些属性值。

    • 不要把可变的collection作为属性公开,而应提供相关方法,以此修改对象中的可变collection。

    🌰:


    MSTPerson.m

    当然也可以用NSMutableSet来实现friends属性,这样就可以不借助"addFriend:"和"removeFriends:"方法直接操作。但是这样过分解耦(decouple)数据的做法很容易出bug。
    还需要强调的一点就是:不要在返回的对象上查询类型以确定其是否可变。

    相关文章

      网友评论

        本文标题:编写高质量的iOS代码(四)

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