《编写高质量iOS与OS X代码的52个有效方法》14-21

第14条:理解“类对象”的用意

  1. 方法签名:概念需要弄清楚

  2. Class.png

    super_class指针确立了继承关系,而isa指针描述了实例所属的类。

  3. 描述OC对象所使用的数据结构定义:typedef struct objc_object {Class is;} *id
    Class对象的定义:

    Class对象结构.png
  4. 对象比较.png

三个分支都走进去了,由此可见类对象是“单例”(singleton),在应用程序范围内,每个类的Class仅有一个实例。

要点:

  • 在每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系(如2)。
  • 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。
  • 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能,比如“代理”(proxy)。

第15条:用前缀避免命名空间冲突

1、Objective-C没有其他语言那种内置的命名空间(namespace)机制,所以就很容易出现重名。
2、Apple 宣称其保留使用所有“两字母前缀”的权利,所以你自己选用的前缀最好是三个字母的。
3、在类的实现文件中所用的纯C函数及全局变量,这个问题必须注意。在编译好的目标文件中(.o),这些名称是要算作“顶级符号”的,顶级符号很容易导致重名,需要加前缀。

要点:

  • 选择与你的公司、应用程序或二者皆有关联之名称作为类名的前缀,并在所有代码中均适用这一前缀。
  • 若自己所开发的程序中用到了第三方库,则应为其中的名称加上前缀。这是为了不和引用自己程序的项目(同时引入同一个第三方)出现第三方库冲突的问题,最好加前缀

第16条:提供“全能初始化方法”

1、什么是全能初始化方法? 全能初始化方法就是指的在一个类中,可能有很多初始化方法,例如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;

在这些初始化方法里面- (id)initWithTimeinterValSinceReferenceDate:(NSTimeInterval)seconds;就是全能初始化方法,也就是说其他初始化函数都要调用它初始化。只有在全能初始化方法中才能存储内部数据。这样的话,当底层数据存储机制改变时,只需修改此方法的代码就好了,无需改动其他初始化方法。

要点:

  • 在类中提供一个全能初始化方法,并与文档里指明,其他初始化方法均应调用此方法。
  • 若全能初始化方法与超类不同,则需复写超类中的对应方法。
  • 如果超类的初始化方法不适用于子类,那么应该覆写这个超类方法,并在其中抛出异常。

第17条:实现 description 方法

对于已经熟悉 iOS 开发的程序员来说,这是debug必备的技能。只需要在自定义的类中覆写- (NSString *)description函数即可,这个函数在事在你调用NSLog的时候输出的。与此相似的还有一个函数- (NSString *)debugDescription,这个函数是在调试器中使用命令的方式输出的,其命令格式po +输出对象,例如po person。需要注意的是,要在调试器中使用命令的方式输出需要打断点,使程序停在断点处,然后在调试器中使用命令。

要点:

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

第18条:尽量使用不可变对象

  1. 只读属性也可以通过“键值变化”(Key-Value Coding,KVC)技术设置这些属性值。
  2. 什么是不可变对象?
    在使用属性时,将其声明为“只读”(read-only)。默认情况下,属性是 “即可读又可写的”(read-write),这样设计出来的类是可变的。
  3. 为什么要使用不可变对象?
    把可变对象(mutable object)放入collection(NSSet,NSArray,NSDictionary以及其子类)之后又修改其内容,那么很容易就会破坏set(集合)的内部数据结构,使其失去固有的语义。
  4. 怎么使用?
    若某属性仅可于对象内部修改,则在“class-continuation分类”中将其由readonly属性扩展为readwrite。不要把可变的collection对象作为属性公开,而应提供相关方法(增删改),以此修改对象中的可变collection。

要点:

  • 尽量创建不可变的对象。
  • 若某属性仅可于对象内部修改,则在“class-continue 分类”中将其由readonly属性扩展为readwrite。
  • 不要把可变的collection对象作为属性公开,而应提供相关方法(增删改),以此修改对象中的可变collection。

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

  1. 方法命名
  • 如果方法的返回值是新建的,那么方法名的首个词应是返回值的类型,除非前面还有修饰语,例如localizedString。属性的存取方法不遵循这种命名方法,因为一般认为这些方法不会创建新的对象,即便有时返回内部对象的一份拷贝,我们也认为那相当于原有的对象。这些存取方法应该按照其所对应的属性来命名。
  • 应该把表示参数类型的名词放在参数前面。
  • 如果方法要在当前对象上执行操作,那么就应该包含动词;若执行操作时还需要参数,则应该在动词后面加上一个或多个名词。
  • 不要使用str这种简称,应该使用string这样的全称。
  • Boolean属性应该加is前缀。如果某方法返回非属性的Boolean值,那么应该根据其功能,选用has或is当前缀。
  • 将get这个前缀留给那些借由“输出参数”来保存返回值的方法,比如说,把返回值填充到“C语言式数组”里的那种方法就可以使用这个词做前缀。
  1. 类与协议的命名,应该为类与协议的名称加上前缀,以避免命名空间冲突(参加第15条)

要点:

  • 起名时应遵从标准的Objective-C命名规范,这样创建出来的接口更容易为开发者所理解。
  • 方法名要言简意赅,从左只有读起来要像个日常用语中的句子才好。
  • 方法名里不要使用缩略后的类型名称。
  • 给方法起名时的第一要务就是确保其风格与你自己的代码或索要继承的框架相符。

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

苹果只出现在实现文件(.m文件)中的函数可以看成是私有方法,私有方法建议以以下格式命名:

- (void)p_privateMethod()

要点:

  • 给私有方法的名称加上前缀,这样就可以很容易地将其公共方法区分开。
  • 不要单用一个下划线(_)做私有方法的前缀,因为这种做法是预留给苹果公司自己用的。

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

  1. 在Objective-C里面我们可以使用NSError来描述错误,NSError对象封装了三条信息:
  • Error domain(错误范围,其类型为字符串,应该定义成NSString型的全局常量)错误发生的范围。也就是产生错误的根源,通常用一个特有的全局变量来定义。比方说,“处理URL的子系统” 在从URL中解析或取得数据时如果出错了,那么就会用NSURLErrorDomain来表示错误范围。
  • Error code(错误码,其类型为整数,应该定义成枚举)独有的错误代码,用以指明在某个范围内具体发生了何种错误。某个特定范围内可能会发生一系列相关错误,这些错误情况通常采用enum来定义。例如,当HTTP请求出错时,可能会把HTTP状态码设为错误码。
  • User info(用户信息,其类型为字典)有关此错误的额外信息,其中或许包含了一段“本地化的描述”(localized description),或许还含有导致该错误发生的另外一个错误,经由此种信息,可将相关错误穿成一条“错误链”。
  1. NSError的常见用法
  • 通过委托协议来传递此错误。例如:
    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

  • 经由方法的 “输出参数” 返回给调用者。例如:-(BOOL)doSomething:(NSError **)error,传递给方法的参数是一个指针,而该指针本身又指向另一个指针,那个指针指向NSError对象。或者也可以把它当成一个直接指向NSError对象的指针。这样来一来,此方法不仅能有普通的返回值,而且还能经由“输出对象”把NSError对象回传给调用者。其用法如下:

      typedef NS_ENUM(NSUInteger, EOCError) {
          EOCErrorUnknown = -1,
          EOCErrorInteralInconsistency = 100,
          EOCErrorGeneralFault = 105,
          EOCErrorBadInput = 500,
      };
    
      NSString *const EOCErrorDomain = @"EOCErrorDomain";
    
      - (BOOL)doSomething:(NSError **)error {
          // Do something that may cause an error
          if (/* there was an error*/) {
              if (error) {
                  NSDictionary *userInfo = @{};
                  // Pass the 'error' through the out-parameter
                  *error = [NSError errorWithDomain:EOCErrorDomain code:EOCErrorGeneralFault userInfo:userInfo];
              }
              return NO;////< Indicate failure
          } else {
              return YES;////< Indicate success
          }
      }
      NSError *error = nil;
      BOOL ret = [self doSomething:&error];
      if (error) {
         //There was an error
     }
    
  1. 异常@throw [NSException exceptionWithName:@“ExceptionName”reason:@“There was an error”userInfo:nil];
    要点:
  • 只有发生了可使整个应用程序崩溃的严重错误时,才应使用异常。

你可能感兴趣的:(《编写高质量iOS与OS X代码的52个有效方法》14-21)