iOS教程:《Effective Objective-C 》技巧篇

  iOS培训在iOS开发中,我们也会使用“类族”(class cluster)这一设计模式,通过“抽象基类”来实例化不同的实体子类。

  举例:

  + (UIButton *)buttonWithType:(UIButtonType)type;

  在这里,我们只需要输入不同的按钮类型(UIButtonType)就可以得到不同的UIButton的子类。在OC框架中普遍使用这一设计模式。

  为什么要这么做呢?

  笔者认为这么做的原因是为了“弱化”子类的具体类型,让开发者无需关心创建出来的子类具体属于哪个类。

  我们可以看一个具体的例子:

  对于“员工”这个类,可以有各种不同的“子类型”:开发员工,设计员工和财政员工。这些“实体类”可以由“员工”这个抽象基类来获得:

  1. 抽象基类

  //EOCEmployee.h

  typedef NS_ENUM(NSUInteger, EOCEmployeeType) {

  EOCEmployeeTypeDeveloper,

  EOCEmployeeTypeDesigner,

  EOCEmployeeTypeFinance,

  };

  @interface EOCEmployee : NSObject

  @property (copy) NSString *name;

  @property NSUInteger salary;

  // Helper for creating Employee objects

  + (EOCEmployee*)employeeWithType:(EOCEmployeeType)type;

  // Make Employees do their respective day's work

  - (void)doADaysWork;

  @end

  //EOCEmployee.m

  @implementation EOCEmployee

  + (EOCEmployee*)employeeWithType:(EOCEmployeeType)type {

  switch (type) {

  case EOCEmployeeTypeDeveloper:

  return [EOCEmployeeDeveloper new];

  break;

  case EOCEmployeeTypeDesigner:

  return [EOCEmployeeDesigner new];

  break;

  case EOCEmployeeTypeFinance:

  return [EOCEmployeeFinance new];

  break;

  }

  }

  - (void)doADaysWork {

  // 需要子类来实现

  }

  @end

  将EOCEmployee作为抽象基类,这个抽象基类有一个初始化方法,通过这个方法,我们可以得到多种基于这个抽象基类的实体子类。

  2. 实体子类(concrete subclass)

  @interface EOCEmployeeDeveloper : EOCEmployee

  @end

  @implementation EOCEmployeeDeveloper

  - (void)doADaysWork {

  [self writeCode];

  }

  @end

  注意:

  如果对象所属的类位于某个类族中,那么在查询类型信息时就要小心。因为类族中的实体子类并不与其基类属于同一个类。

  第10条:在既有类中使用关联对象存放自定义数据

  我们可以通“关联对象”机制来把两个对象连接起来。这样我们就可以从某个对象中获取相应的关联对象的值。先看一下关联对象的语法:

  1. 为某个对象设置关联对象的值:

  void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)

  这里,第一个参数是主对象,第二个参数是键,第三个参数是关联的对象,第四个参数是存储策略:是枚举,定义了内存管理语义。

  2. 根据给定的键从某对象中获取相应的关联对象值:

  id objc_getAssociatedObject(id object, void *key)

  3. 移除指定对象的关联对象:

  void objc_removeAssociatedObjects(id object)

  举个例子:

  #import

  static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";

  - (void)askUserAQuestion {

  UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question"

  message:@"What do you want to do?"

  delegate:self

  cancelButtonTitle:@"Cancel"

  otherButtonTitles:@"Continue", nil];

  void (^block)(NSInteger) = ^(NSInteger buttonIndex){

  if (buttonIndex == 0) {

  [self doCancel];

  } else {

  [self doContinue];

  }

  };

  //将alert和block关联在了一起

  objc_setAssociatedObject(alert,EOCMyAlertViewKey,block, OBJC_ASSOCIATION_COPY);

  [alert show];

  }

  // UIAlertViewDelegate protocol method

  - (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex

  {

  //alert取出关联的block

  void (^block)(NSInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey)

  //给block传入index值

  block(buttonIndex);

  }

  第13条:用“方法调配技术”调试“黑盒方法”

  与选择子名称相对应的方法是可以在运行期被改变的,所以,我们可以不用通过继承类并覆写方法就能改变这个类本身的功能。

  那么如何在运行期改变选择子对应的方法呢?

  答:通过操纵类的方法列表的IMP指针

  什么是类方法表?什么是IMP指针呢?

  类的方法列表会把选择子的名称映射到相关的方法实现上,使得“动态消息派发系统”能够据此找到应该调用的方法。这些方法均以函数指针的形式来表示,这些指针叫做IMP。例如NSString类的选择子列表:

  

  有了这张表,OC的运行期系统提供的几个方法就能操纵它。开发者可以向其中增加选择子,也可以改变某选择子对应的方法实现,也可以交换两个选择子所映射到的指针以达到交换方法实现的目的。

  举个 :交换lowercaseString和uppercaseString方法的实现:

  Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));

  Method swappedMethod = class_getInstanceMethod([NSString class],@selector(uppercaseString));

  method_exchangeImplementations(originalMethod, swappedMethod);

  这样一来,类方法表的映射关系就变成了下图:

  

  这时,如果我们调用lowercaseString方法就会实际调用uppercaseString的方法,反之亦然。

  然而!在实际应用中,只交换已经存在的两个方法是没有太大意义的。我们应该利用这个特性来给既有的方法添加新功能(听上去吊吊的):

  它的实现原理是:先通过分类增加一个新方法,然后将这个新方法和要增加功能的旧方法替换(旧方法名 对应新方法的实现),这样一来,如果我们调用了旧方法,就会实现新方法了。

  不知道这么说是否抽象。还是举个 :

  需求:我们要在原有的lowercaseString方法中添加一条输出语句。

  步骤一:我们先将新方法写在NSString的分类里:

  @interface NSString (EOCMyAdditions)

  - (NSString*)eoc_myLowercaseString;

  @end

  @implementation NSString (EOCMyAdditions)

  - (NSString*)eoc_myLowercaseString {

  NSString *lowercase = [self eoc_myLowercaseString];//eoc_myLowercaseString方法会在将来方法调换后执行lowercaseString的方法

  NSLog(@"%@ => %@", self, lowercase);//输出语句,便于调试

  return lowercase;

  }

  @end

  步骤二:交换两个方法的实现(操纵调换IMP指针)

  Method originalMethod =

  class_getInstanceMethod([NSString class],

  @selector(lowercaseString));

  Method swappedMethod =

  class_getInstanceMethod([NSString class],

  @selector(eoc_myLowercaseString));

  method_exchangeImplementations(originalMethod, swappedMethod);

  这样一来,我们如果交换了lowercaseString和eoc_myLowercaseString的方法实现,那么在调用原来的lowercaseString方法后就可以输出新增的语句了。

  “NSString *string = @"ThIs iS tHe StRiNg";

  NSString *lowercaseString = [string lowercaseString];

  // Output: ThIs iS tHe StRiNg => this is the string”

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

  有时,由于要实现各种设计需求,一个类可以有多个创建实例的初始化方法。我们应该选定其中一个作为全能初始化方法,令其他初始化方法都来调用它。

  注意:

  只有在这个全能初始化方法里面才能存储内部数据。这样一来,当底层数据存储机制改变时,只需修改此方法的代码就好,无需改动其他初始化方法。

  全能初始化方法是所有初始化方法里参数最多的一个,因为它使用了尽可能多的初始化所需要的参数,以便其他的方法来调用自己。

  在我们拥有了一个全能初始化方法后,最好还是要覆写init方法来设置默认值。

  //全能初始化方法

  - (id)initWithWidth:(float)width andHeight:(float)height

  {

  if ((self = [super init])) {

  _width = width;

  _height = height;

  }

  return self;

  }

  //init方法也调用了全能初始化方法

  - (id)init {

  return [self initWithWidth:5.0f andHeight:10.0f];

  }

  现在,我们要创造一个squre类继承这上面这个ractangle类,它有自己的全能初始化方法:

  - (id)initWithDimension: (float)dimension{

  return [super initWithWidth:dimension andHeight:dimension];

  }

  这里有问题!

  然而,因为square类是rectangle类的子类,那么它也可以使用initWithWidth: andHeight:方法,更可以使用init方法。那么这两种情况下,显然是无法确保初始化的图形是正方形。

  因此,我们需要在这里覆写square的父类rectangle的全能初始化方法:

  - (id)initWithWidth:(float)width andHeight:(float)height

  {

  float dimension = MAX(width, height);

  return [self initWithDimension:dimension];

  }

  这样一来,当square用initWithWidth: andHeight:方法初始化时,就会得到一个正方形。

  并且,如果用init方法来初始化square的话,我们也可以得到一个默认的正方形。因为在rectangle类里覆写了init方法,而这个init方法又调用了initWithWidth: andHeight:方法,并且square类又覆写了initWithWidth: andHeight:方法,所以我们仍然可以得到一个正方形。

  而且,为了让square的init方法得到一个默认的正方形,我们也可以覆写它自己的初始化方法:

  - (id)init{

  return [self initWithDimension:5.0f];

  }

  我们做个总结:

  因为子类的全能初始化方法(initWithDimension:)和其父类的初始化方法并不同,所以我们需要在子类里覆写initWithWidth: andHeight:方法。

  还差一点:initWithCoder:的初始化

  有时,需要定义两种全能初始化方法,因为对象有可能有两种完全不同的创建方式,例如initWithCoder:方法。

  我们仍然需要调用超类的初始化方法:

  在rectangle类:

  // Initializer from NSCoding

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

  // Call through to super's designated initializer

  if ((self = [super init])) {

  _width = [decoder decodeFloatForKey:@"width"];

  _height = [decoder decodeFloatForKey:@"height"];

  }

  return self;

  }

  在square类:

  // Initializer from NSCoding

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

  // Call through to super's designated initializer

  if ((self = [super initWithCoder:decoder])) {

  // EOCSquare's specific initializer

  }

  return self;

  }

  每个子类的全能初始化方法都应该调用其超类的对应方法,并逐层向上。在调用了超类的初始化方法后,再执行与本类相关的方法。

  第17条:实现description方法

  在打印我们自己定义的类的实例对象时,在控制台输出的结果往往是这样的:

  object =

  这里只包含了类名和内存地址,它的信息显然是不具体的,远达不到调试的要求。

  但是!如果在我们自己定义的类覆写description方法,我们就可以在打印这个类的实例时输出我们想要的信息。

  例如:

  - (NSString*)description {

  return [NSString stringWithFormat:@"

  本文摘自:http://www.cdtedu.com/ios/course/8296.html,如需转载,请保存!

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31382915/viewspace-2125934/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/31382915/viewspace-2125934/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值