重构的技巧

本人翻译自:“http://www.merowing.info/2014/03/refactoring-tricks/#.U4Hn2Ba-zW0”.

我想一条童子军的军规:“始终保持露营地比你发现它的时候还要干净”。如果你在地上发现了一点脏东西,不管是谁弄的,都清理掉它。要为了下一拨来露营的人改善环境。(实际上,那条规矩的早期版本,出自Robert Stephenson Smyth Bden-Powell,童子军活动之父,说的是“努力使世界比你发现它时变得更好”。) 这就是为什么我不断的重构我的代码让它干净整洁. 当谈到编程的质量时,代码的可读性是我最关注的部分. 我想分享一些大部分人不知道的我用来简化代码的重构技巧.

字典的映射

假如你有一个switch语句来分配一些值:

switch(condition) {
          case value1:
              result = @"valueFor1";
          break;
          case value2:
              result = @"valueFor2";
          break;
          case value3:
              result = @"valueFor3";
          break;
          case value4:
              result = @"valueFor4";
          break;
          case value5:
              result = @"valueFor5";
          break;
          default:
              result = @"valueForDefault";
          break;
      }
      return result;

记住这儿仅仅就 switch 5个值, 想象一下一对多. 让我们使用字典映射来简化这段代码:

static NSDictionary *mapping = nil;
      if(!mapping) {
          mapping = @{
              @(value1) : @"valueFor1",
              @(value2) : @"valueFor2",
              @(value3) : @"valueFor3",
              @(value4) : @"valueFor4"
              @(value5) : @"valueFor5"
          };
      }
      
      return mapping[@value] ?: @"valueForDefault";

Pro’s(赞成的原因)

1.更方便阅读,即使在原来的switch里面有着更多的值也可以变得更容易阅读. 2.更快,映射仅仅构造一次,然后我们只需要快速的查找值. 3.更不易出错,因为这儿并不需要写break或者return. 4.基于代码映射的性质可以非常轻松的将这个映射转成某种静态数据,例如JSON,PLIST文件,.

用block动态映射

关于更加复杂的switch,怎么样真正动态的做一些事情?我们可以用block来简化这段代码.

最近,我重构了一些代码,字符串格式化来针对不同的类型:

if ([title isEqualToString:@"Scratches"])
  {
      title = [NSString stringWithFormat:(self.vehicle.numberOfScratches == 1 ? @"%d Scratch" : @"%d Scratches"), self.vehicle.numberOfScratches];
  }
     else if ([title isEqualToString:@"Dents"])
  {
      title = [NSString stringWithFormat:(self.vehicle.numberOfDents == 1 ? @"%d Dent" : @"%d Dents"), self.vehicle.numberOfDents];
  }
  else if ([title isEqualToString:@"Painted Panels"])
  {
      title = [NSString stringWithFormat:(self.vehicle.numberOfPaintedPanels == 1 ? @"%d Painted Panel" : @"%d Painted Panels"), self.vehicle.numberOfPaintedPanels];
  }
  else if ([title isEqualToString:@"Chips"])
  {
      title = [NSString stringWithFormat:(self.vehicle.numberOfChips == 1 ? @"%d Chip" : @"%d Chips"), self.vehicle.numberOfChips];
  }
  else if ([title isEqualToString:@"Tires"])
  {
      title = [NSString stringWithFormat:(self.vehicle.numberOfTires == 1 ? @"%d Tire" : @"%d Tires"), self.vehicle.numberOfTires];
  }
  else title = nil;

通过使用block映射,我们可以重构这段代码为下面这样:

static NSDictionary *titleMapping = nil;
if (!titleMapping) {
 NSString *(^const format)(NSUInteger, NSString *, NSString *) = ^(NSUInteger value, NSString *singular, NSString *plural) {
    return [NSString stringWithFormat:@"%d %@", value, (value == 1 ? singular : plural)];
  };

  titleMapping = @{
    @"Scratches" : ^(MyClass *target) {
      return format([target numberOfScratches], @"Scratch", @"Scratches");
    },
    @"Dents" : ^(MyClass *target) {
      return format([target numberOfDents], @"Dent", @"Dents");
    },
    @"Painted Panels" : ^(MyClass *target) {
      return format([target numberOfPaintedPanels], @"Painted Panel", @"Painted Panels");
    },
    @"Chips" : ^(MyClass *target) {
      return format([target numberOfChips], @"Chip", @"Chips");
    },
    @"Tires" : ^(MyClass *target) {
      return format([target numberOfTires], @"Tire", @"Tires");
    }
  };
}

  NSString *(^getTitle)(MyClass *target) = titleMapping[title];
  return getTitle ? getTitle(self) : nil;

Pro’s

1.非常安全,因为没有办法弄错if-else链. 2.缓存了映射,因为我们使用的是静态的变量. 3.我们可以很容易的添加一些宏来使得代码更精简,并且因此更容易来扩展.

PS.我可以用字符串匹配来实现它,甚至使用的代码更少. 但是我不认为它能使可读性更好.

更早使用return和取反if的判断条件来简化的流程

现在你大概可能发现我不太喜欢太多的if条件句并且我讨厌太长的if-esle链. 反而我比较喜欢让if条件语句尽可能的简单并且更早使用return来返回.

原始代码:

if(!error) {
  //! success code
} else {
  //! failure code
}

我比较喜欢的写法:

if(error) {
  //! failure code
  return;
}

//! success code

Pro’s 1.我不需要阅读更多的代码.假如我仅仅只是对error的情况感兴趣. 2.在大段代码的情况我不需要去记住所有的流程,因为我可以非常清楚看到前面的return/break.

使用动态方法解决

有时,我们可能看到类似下面的情况:

if([type isEqualToString:@"videoWidget"]) {
  [self parseVideoWidget:dictionary];
} else
if([type isEqualToString:@"imageWidget"]) {
  [self parseImageWidget:dictionary];
} else
if([type isEqualToString:@"textWidget"]) {
  [self parseTextWidget:dictionary];
} else
if([type isEqualToString:@"twitterWidget"]) {
  [self parseTwitterWidget:dictionary];
}

首先,我可以把上面讲的所有重构技巧应用到此,但是这段代码看起来主要等待解决的问题是将来的可扩展问题,让我们一起看看如何能够让它变得更好.

SEL dynamicSelector = NSSelectorFromString([NSString stringWithFormat:@"parse%@:", type]);
if(![self respondsToSelector:dynamicSelector]) {
  DDLogWarning(@"Unsupported widget type %@", type);
  return;
}
[self performSelector:dynamicSelector withObject:dictionary];

Pro’s 1.非常容易阅读并且理解 2.比if条件语句更安全 3.更容易扩展,例如我可以添加新的工具类型到分类中.当开发一个衍生APP的时候我甚至都不需要去接触它的基础类.

结论

我时常重构我的代码,这儿有一些知道人不多的技巧,但是有助于让你的代码更简化.我通常在“AppCode”上写我的代码.AppCode是一个很棒的IDE,它有着大量的关于重构的函数,我每天都在使用这些,点击链接下载.

玩转Facebook开源的Pop库

原文链接:“http://codeplease.io/playing-with-pop-i/”

Facebook在向外界开源一些三方库上,一直都是做的非常好的. 最近在github发布的“Pop”库在不到24小时之内就已经收到了3.5k个stars.

引用Facebook的原话:

‘pop是一个适用于iOS和OSX上的可扩展的动画引擎.除了基础的静态动画之外,还支持动态的弹性和衰减动画,可用于构建逼真的,基于物理的交互.它的API可以快速集成到现有的Objective-C代码库中,并且能够让任意对象的任何属性具有动画效果.它是一个成熟的并且经过良好测试的框架 在paper中驱动所有的动画和过渡效果.’

我用它创建一个“简单的示例”开始.我的目的是想看一下它在给用户输入框加上阴影时,能达到什么样子的效果;它做的太棒了.我也想要获取第一手的体验,用一些东西快速去构建.这个特定的案例中使用的POPSpringAnimation代码,感觉与“我做过的其他东西”很类似.

为了研究这个库,我的方法是直接进入他们的.h文件一探究竟(顺便提下,它们都是由objective-c++写的):

POPBasicAnimation
POPDecayAnimation
POPPropertyAnimation
POPSpringAnimation
POPCustomAnimation
POPAnimation
POPAnimatableProperty

关于Pop有一些非常酷的东西,当你添加一些动画的时候,他的表现层和模型层是同步的. 这个 Sam Page(@sampage)写的 “圆圈示例” 就使用了pop. 有点类似于 “这个”. 由于strokeEndstrokeStart 并不是“默认属性”的一部分,你需要创建你自己的自定义属性,比如这样:

[POPAnimatableProperty propertyWithName:@"strokeStart" initializer:^(POPMutableAnimatableProperty *prop) {
        prop.readBlock = ^(id obj, CGFloat values[]) {
            values[0] = [obj strokeStart];
        };
        prop.writeBlock = ^(id obj, const CGFloat values[]) {
            [obj setStrokeStart:values[0]];
        };
    }];

必须的说POP库是非常强大的,正如之前所说:表示层和模型层是同步的!

” Facebook’s Paper Tech Talk “里面Brian Amerige(@brianamerige)的话一直困扰在我的脑海里,特别是这一部分,他展示了如何让我们把手势融合进动画中(视频47:40部分).

所以直接从视频中看到:

基于手势的速率,可以旋转一个UIView

- (void)rotate:(UIPanGestureRecognizer*)recognizer
{
    CGPoint velocity = [recognizer velocityInView:self.view];

    POPSpringAnimation *spring = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation];
    spring.velocity = [NSValue valueWithCGPoint:velocity];

    [_outletView.layer pop_addAnimation:spring forKey:@"rotationAnimation"];
}

现在,当你开始接触这些坏东西的时候,这一切又开始变得有趣了.

那么,如果我们同时对positionsize 加上一些”dynamics”,会发生什么?

见代码:

- (void)rotate:(UIPanGestureRecognizer*)recognizer
{
    CGPoint velocity = [recognizer velocityInView:self.view];

POPSpringAnimation *positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPosition];  
positionAnimation.velocity = [NSValue valueWithCGPoint:velocity];  
positionAnimation.dynamicsTension = 5;  
positionAnimation.dynamicsFriction = 5.0f;  
positionAnimation.springBounciness = 20.0f;  
[_outletView.layer pop_addAnimation:positionAnimation forKey:@"positionAnimation"];


POPSpringAnimation *sizeAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerSize];  
sizeAnimation.velocity = [NSValue valueWithCGPoint:velocity];  
sizeAnimation.springBounciness = 1.0f;  
sizeAnimation.dynamicsFriction = 1.0f;  
[_outletView.layer pop_addAnimation:sizeAnimation forKey:@"sizeAnimation"];

在删除所有与dynamics相关的代码后:

你依然可以看到一个轻微的弹力效果,但是这些是POPSpringAnimation的默认值起的效果.

至于POP,或者其他用于娱乐用途的三方库,扪心自问都涉及到一点:我现在应该使用它(这个library)来做吗?? Brian Lovin (@brian_lovin) 所写的“Design Details: Paper by Facebook(设计细节:Facebook的paper)”给了你大量的资料去思考这个问题. 下面来自于Brian Lovin的博客:

我想创建一些内容,允许我展示一个小的弹出窗口,并且有一些震动感.(好吧,是因为我喜欢这样的效果). 上图并不是我想要的效果(甚至差得很远).但是我从这里获得了灵感,所以:

是的,我知道.没有什么奇特的,但是,可以使用POP很轻松实现这个效果.同样,就动画而言,没有能比POP带来”更甜美味道”的感觉了.

- (void)hidePopup
{
    _isMenuOpen = NO;
    POPBasicAnimation *opacityAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
    opacityAnimation.fromValue = @(1);
    opacityAnimation.toValue = @(0);
    [_popUp.layer pop_addAnimation:opacityAnimation forKey:@"opacityAnimation"];

    POPBasicAnimation *positionAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerPosition];
    positionAnimation.fromValue = [NSValue valueWithCGPoint:VisiblePosition];
    positionAnimation.toValue = [NSValue valueWithCGPoint:HiddenPosition];
    [_popUp.layer pop_addAnimation:positionAnimation forKey:@"positionAnimation"];

    POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];

    scaleAnimation.fromValue  = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)];
    scaleAnimation.toValue  = [NSValue valueWithCGSize:CGSizeMake(0.5f, 0.5f)];
    [_popUp.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"];
}

还有显示出效果的代码:

- (void)showPopup
{
    _isMenuOpen = YES;

    POPBasicAnimation *opacityAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
    opacityAnimation.fromValue = @(0);
    opacityAnimation.toValue = @(1);
    opacityAnimation.beginTime = CACurrentMediaTime() + 0.1;
    [_popUp.layer pop_addAnimation:opacityAnimation forKey:@"opacityAnimation"];

    POPBasicAnimation *positionAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerPosition];
    positionAnimation.fromValue = [NSValue valueWithCGPoint:VisibleReadyPosition];
    positionAnimation.toValue = [NSValue valueWithCGPoint:VisiblePosition];
    [_popUp.layer pop_addAnimation:positionAnimation forKey:@"positionAnimation"];


    POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
    scaleAnimation.fromValue  = [NSValue valueWithCGSize:CGSizeMake(0.5, 0.5f)];
    scaleAnimation.toValue  = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)];//@(0.0f);
    scaleAnimation.springBounciness = 20.0f;
    scaleAnimation.springSpeed = 20.0f;
    [_popUp.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"];
}

这里补充三个注意事项: 1.当你添加一个动画类似[myView pop_addAnimation:animation forKey:@"animationKey"]; 要记住,假如你添加其他的动画使用了相同的key,那么将会替换掉之前的那一个.

2.当你想一个动画用CACurrentMediaTime()并使用beginTime来指定. 因此,看起来就像这样:animation.beginTimee = CACurrentMediaTime() + delayInSeconds;. 我添加了一个简单的延迟,当然,他不会工作.感谢Kimon(@kimon)所提出的“警告”;

3.当你看到一个属性类似于kPOPLayerScaleXY,它会期望两个值. 在这种情况下是一个CGSize.现在可能是有意义的,但是我传了一个NSNumber(一个单一值)并且期待它可以同时设置X和Y的值.

这是一位天才(via @_tiagoalmeida):

它起效果了:

POPAnimatableProperty *constantProperty = [POPAnimatableProperty propertyWithName:@"constant" initializer:^(POPMutableAnimatableProperty *prop){  
        prop.readBlock = ^(NSLayoutConstraint *layoutConstraint, CGFloat values[]) {
            values[0] = [layoutConstraint constant];
        };
        prop.writeBlock = ^(NSLayoutConstraint *layoutConstraint, const CGFloat values[]) {
            [layoutConstraint setConstant:values[0]];
        };
    }];

POPSpringAnimation *constantAnimation = [POPSpringAnimation animation];  
constantAnimation.property = constantProperty;  
constantAnimation.fromValue = @(_layoutConstraint.constant);  
constantAnimation.toValue = @(200);  
[_layoutConstraint pop_addAnimation:constantAnimation forKey:@"constantAnimation"];

非常感谢 Jake Marsh (@jakemarsh).

仅仅只是一条小笔记,我没有注意到kPOPLayoutConstraintConstant.所以你不需要自己去创建一个自定义的POPAnimatableProperty.

在玩了几天的POP后,现在已经进入到了除了享受以外,我还可以做出共享的时刻了.

在我第一篇文章中(本文开头部分) ,我创建了一个自定义的属性给strokeStartstrokeEnd (两个都属于CAShapeLayer):

[POPAnimatableProperty propertyWithName:@"strokeStart" initializer:^(POPMutableAnimatableProperty *prop) {
        prop.readBlock = ^(id obj, CGFloat values[]) {
            values[0] = [obj strokeStart];
        };
        prop.writeBlock = ^(id obj, const CGFloat values[]) {
            [obj setStrokeStart:values[0]];
        };
}];

这就几行代码的工作,但是不再害怕了.“我第一次提交的代码请求” (希望不是最后一次) 已经被批准通过 并且已经合并到POP的主仓库.这意味着,我现在可以在CAShapeLayer上使用这两个属性而不用添加任何附加逻辑.

添加一个属性到POP只有三个简单的步骤(也许是4步或者5步). 假如有人想试一试:

1.在POPAnimatableProperty.h内给你的属性名称添加一个extern NSString修饰,努力保持一个好的命名习惯.就像kPOP<class name witout prefix><propertyName>这样.假如他是由超过一个值组成的话,可以这样kPOP<class name witout prefix><propertyName>XY. 在这之后去POPAnimatableProperty.m添加真实的值,就像这样:NSString * const kPop<class name witout prefix><propertyName> = @"<propertyName>".显然这并不是成文的规定,所以你不确定如何命名的话,去看看其他的属性是如何命名的.

2.通过这种方法添加的 读/写的block 将会可以运行,增加threshold值.你可以看到“其他的属性是如何做到的”

  1. 我不需要这样做,我添加的属性很简单.我想说,当你在读写一个新值时,使用辅助方法对你来说会更好.例如,你可以看一下kPOPViewBackgroundColor是如何做的:
{kPOPViewBackgroundColor,
    ^(UIView *obj, CGFloat values[]) {
      POPUIColorGetRGBAComponents(obj.backgroundColor, values);
    },
    ^(UIView *obj, const CGFloat values[]) {
      obj.backgroundColor = POPUIColorRGBACreate(values);
    },
    1.0
  },

这种情况下要确保POPUIColorGetRGBAComponentsPOPUIColorRGBACreate:

void POPUIColorGetRGBAComponents(UIColor *color, CGFloat components[])  
{
  return POPCGColorGetRGBAComponents(color.CGColor, components);
}

UIColor *POPUIColorRGBACreate(const CGFloat components[])  
{
  CGColorRef colorRef = POPCGColorRGBACreate(components);
  UIColor *color = [[UIColor alloc] initWithCGColor:colorRef];
  CGColorRelease(colorRef);
  return color;
}

这些辅助方法位于POPCGUtils,尽管在POPLayerExtras有更多.作为一个好市民,你可以创建其他的方法,这样人们就可以使用它们来模拟属性的行为.

1.添加的你属性到测试组!由于这些属性非常的天真,我仅仅添加它到POPAnimatablePropertyTests.mtestProvidedExistence,来保证它的实现方法实际存在.

2.尽可能更多测试,假如你做一些与众不同的,并且没有被默认的组覆盖.

正如我需要成长,我将会贡献更多的内容到这个仓库.

这儿有些关于旋转的实验:

下面是实现的代码:

POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotationX];  
anim.springBounciness = 20.0f;  
anim.dynamicsMass = 5.0f;  
anim.fromValue = @(degreesToRadians(degrees));  
anim.toValue = @(M_PI);  
[self.bundledLayers pop_addAnimation:anim forKey:@"rotationXAnimation"];

由于 Florien Kluger (@floriankugler) 对我之前的那个“示例”给出的建议,修改了一点儿地方.除了现有的之外,我并且可以在动画的时候抓住模块的顶部.这对于用户来说是一个很愉快的体验.

最开始的时候我禁用并且移除了手势,在下面的条件时:

1.UIGestureRecognizerStateEnded,在交互结束时,很快到动画结束的位置并禁用手势.

2.假如这个view被点击过,我会仅仅只让动画到结束位置.

我用了第二条,它依然是有意义的.第一条已经被修改了.所以,我现在做的就是:

UIGestureRecognizerStateEnded状态已经完成,我就设置下面的完成块:

^(POPAnimation *anim, BOOL finished) {
        if (finished) [self disableGestures];
    }

所以假如动画已经完成了,我将禁用手势.更有趣的部分在UIGestureRecognizerStateBegan状态:

if ([self.bundledLayers pop_animationForKey:RPPopRotationAnimation])  
{
  POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:RPPopRotationAnimation];
  [animation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {

  CGFloat currentDegrees = [(NSNumber *)[self.bundledLayers valueForKeyPath:@"transform.rotation.x"] floatValue];
  [self applyRotationAnimationOnLayer:self.bundledLayers initialDegrees:currentDegrees toFinalDegrees:degrees completionBlock:nil];
            }];
}
else  
{
  [self applyRotationAnimationOnLayer:self.bundledLayers initialDegrees:0.0f toFinalDegrees:degrees completionBlock:nil];
}
  1. 检查是否有动画正在进行(可能由于UIGestureRecognizerStateEnded) 1.1假如是这样,就让我们”劫持”这个完成事件completionBlock 并且替换成一个新的转换效果,基于我触摸的位置.
  2. 假如没有动画的话,我会让消息视图仍然呈现折叠并且在应用一个转换效果从0.0f开始到我触摸的地方.

除了代替劫持掉completionBlock,你还能:

[animation setCompletionBlock:nil];
animation.toValue = @(degreesToRadians(degrees));

带来了不同的效果,但是依然是一个不错的. 首先第一个区别是,我会让动画完成并且应用一个新的转换效果. 第二个区别是,我不会允许动画到达原始的toValue位置.

PS:我的数学非常糟糕,所以基于我触摸点位置计算角度的逻辑在 “这里”.

JSONModel在64位环境下解析为NSUInterge属性的异常问题

在连续的加班中,人已经有一点吃不消了.别人都是996…我们加起班都是9-11-7…而且经常被催几个通宵完成的页面和功能,又突然来一句设计大改,全部重新调整. 不说了,说多了都是泪啊.

今天早上在测试各种环境运行状态的时候,在64bit模拟器下突然崩了(因为公司没有买任何设备,只有自己买的苹果本跟一个iPhone5,所以只能将就用模拟器测试了).

抛出异常的位置:

继续执行后控制台的Reason输出:

*** Terminating app due to uncaught exception 'JSONModelProperty type not allowed', reason: 'Property type of XXModel.XXXXX is not supported by JSONModel.'

奇怪的是打印出来的XXXX这个属性是我自己后来添加到model类的一个index标识(NSUInterge类型)…应该没有问题吧,我以前也添加过json数据以外的属性来扩充model.没有出现过问题….这个Reason异常估计粗问题了(原谅我这个新手不知道为森么)~~~ 抱着不作就不会死的原则…我修改自定义的那个property 类型..从nsstring一直改到了nsobjec…最后MB我都注释掉了!他还是抛出异常了..这次终于打印出来罪魁祸首.

其实在最初debug的时候我是直接在异常上面的源码断点调试的,然后直接查到了是哪个属性执行后异常.这里不断修改出错的那个属性,是写本文时的另外新鲜尝试.

就是上面NSUInteger类型的变量…氮素,这个在32bit下没点问题啊.为什么到了64bit就不行了呢.. 来阅读下jsonmodel的源代码也是一个不错的学习机会. propertyType = valueTransformer.primitivesNames[propertyType]; 这里的propertyType是nil.也就是说他没有取到值,所以在下面直接抛出异常了.(对象类型在更上面的几行代码中会被执行完成).

propertyType是一个NSString.所以上面这行代码是从一个字典取值.我进入这个字典一探究竟.

@interface JSONValueTransformer : NSObject

@property (strong, nonatomic, readonly) NSDictionary* primitivesNames;

果然是一个字典属性,再进入JSONValueTransformer.m看看他的初始化

恩..好像没看到NSUInteger…看了几遍..大概晓得了,没有NSUInteger. 氮素NSUInteger的key是什么?我也不知道,自己写几行小程序看看吧..

在64bit simulator下看到了NSUInteger的key是”Q”.(在32bit下是”I”). 看下NSUInteger的定义:

#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

64位下的其实就是unsigned long了.那么我们继续回到JSONValueTransformer.m中,在初始化的时候加上@"Q":@"NSUInteger". 结果如图所示:

然后继续64bit模拟器下测试….没有抛出异常了~~

因为时间仓促的原因,很多细节没有细扣,如有说的不对的地方,欢迎各路大神拍砖.(我身上已经隐隐渗出冷汗了)

另外jsonmodel没有实现NSCoding协议.本来自己想写一个自动NSCoding的,后来发现网上已经有现成的轮子了.我就不重复造了. https://github.com/nicklockwood/AutoCoding

在你项目使用继承于jsonmodel的BaseModel.m实现中,import下就行了.

Storyboards

在iOS5之前,界面基础和试图都是使用Interface Builder (IB)来创建的,并用nib文件来保存.Storyboards是另外一种创建界面的方式,并且添加了一些内容创建界面基础,你可以在两个界面之间指定导航(叫做segues).这样可以让你在开始写代码之前不用做什么事情.你可以把storyboards想象成一个图表,上面所有view controllers用segues连接,还可以指定之间的转换效果.

storyboards带来的好处还不止于此. 它同样可以很轻松让开发者创建一个静态的table view而不用data source. 有多少次你想去创建一个table view而不需要去绑定一个真正的data source – 例如,一个table来展示一个选项列表代替数据.一般用来app中的设置页面. Storyboards 也可以帮助合作开发者或者客户了解app完整的工作流程.

Storyboards 并不都是浪漫的,在我看来,它依然有着严重的缺陷,不过在以后的章节中,我们会学习如何使用故事版而不受那些缺点的影响.

You May Not Know

Base64 and Percent Encoding

Cocoa 一直以来都有需求去方便的访问Base64编码和解码. Base64也是很多web协议的标准并且在你需要存储任意数据到一个字符串时是非常有用的.

Percent编码也是一个重要的web协议.特别是URs.你现在可以用 [NSString stringByRemovingPercentEncoding]解码percent-encoded(百分比编码)字符串.虽然一直以来可能是用 stringByAddingPercentEscapesUsingEncoding:来做percent编码,但是iOS7添加了stringByAddingPercentEncodingWithAllowedCharacters:方法,它可以让你用来控制字符为 percent-encoded(百分比编码).

instancetype

Objective-C 一直有一些敏感的子类化问题. 思考下面的情况:

@interface Foo : NSObject
+ (Foo *)fooWithInt:(int)x; @end
@interface SpecialFoo : Foo
@end
...
SpecialFoo *sf = [SpecialFoo fooWithInt:1];