iOS学习笔记——CardMatchingGame

继续公开课笔记,这篇笔记是第2和3课的。主要内容是完成CardMatchingGame,是类似对对碰的翻牌匹配游戏,根据两张牌的匹配程度计算分数。完成后的效果大概是这样:

屏幕快照 2014-09-28 下午5.50.43

对MVC的一些新的体会

之前的一篇关于MVC的笔记《iOS学习笔记——MVC》是听了第一课后的想法,是脱离实践的,通过这次对CardMatchingGame的学习,有一些补充。

首先,上一篇笔记中说的MVC是一种结构和MVC的核心任务是将合适的内容合适地显示在屏幕上,这两点是没有问题的。MVC确实只是一种结构,代码结构也好,项目文件组织结构也好,只是一种结构。第二点需要补充一下,这句话中的“合适”既包含了内容的合适,也包含了时间上的合适,也就是说MVC保证了View和Model的同步

之前说到Model和View是隔离的,它们之间是independent的,需要Controller在中间进行协调。而View和Model之间的同步,也是Controller的主要内容之一。落实到这个游戏中就是:

  • 当点按一张牌时,Controller接收到发来的Action,取到这个Action所包含的信息,然后将信息发送给Model中的业务逻辑处理。
  • 当业务逻辑处理完之后,Controller需要及时地更新UI——将Model中已经更改了的信息,在UI上反映出来。
    要知道View部分的实现以及View向Controller的通信,是Cocoa框架已经实现好了的,在开发iOS应用时,这方面是不需要过多担心的。

还有一个地方需要调整,就是对outlet的解释,下面会提到。

使用Xcode创建UI

和Android使用图形界面创建UI的过程,ANT会自动生成xml不同;使用Xcode内置的Interface Builder创建UI时,并不会生成对应的文件。在创建UI时,向开发者开放的,就是storyboard画布、控件库、属性控制面板。将控件拖动到画布上,修改属性,这样一个简单的UI就创建完成了。从某个角度上这个过程反映了View和Model是相互独立的这一事实。没有文件的生成,那么这个UI是如何显示出来的呢?在开发过程中,Xcode只保存控件的属性,这些属性保存的位置是对开发者隐藏的,编译时也只是编译属性,在iOS运行时,动态地将属性解析出来并生成对应的控件进行绘制,这和Android有很大不同。

这样的一个UI,如何和Controller进行关联呢?Xcode也提供了非常elegant的手段,使用辅助编辑器(对照编辑器)将storyboard和Controller.m文件分立两侧,按住control键将控件拖动到Controller.m文件合适的位置,即可实现View和Controller的关联。

  • 对于不需要向Controller通信的控件,比如Label,将控件拖动到内部的Interface部分(分类),即可自动生成outlet属性,注意,outlet属性是Controller中对View的一个引用。
  • 对于需要像Controller发送Action的控件,比如Button,将控件拖动到implementation部分,即可生成对应的target方法。在拖动并释放鼠标的时候,可以对target进行配置,需要什么参数,Action触发条件,Sender的类型等。当View的Action被触发时,Controller就会收到target对应的消息(执行target对应的方法)。
    在Android开发中完成响应的功能,需要在xml中声明ID,在Context中获取View并添加监听器,而Xcode一个拖拽动作即可完成,说它是elegant完全不为过。

下面说下outlet。其实之前说的outlet是Controller伸向View的触角,意思没有错,但是多少有些牵强,没错是因为outlet确实属于Controller,牵强是因为outlet没有“伸向”View。outlet是对View的引用,不管需不需要发送Action。哪怕没有在内部Interface中声明outlet,也是outlet。实际上outlet是Xcode的一个标记,用于对属性和方法的鉴别和开发辅助(IBOutlet或IBAction等并不是关键字,只是Xcode用于识别的flag)。

然后提一下Xcode的资源导入。因为iPhone也包含了多种分辨率,所以在开发过程中是需要对素材做适配的。在Xcode中,素材放到Image.xcasserts下,直接将素材拖动到列表中,会在右侧显示1x,2x,3x三个空位,将不同分辨率的素材放到对应的空位上,就完成了导入,使用时只需要使用名称使用即可,iOS会自动选择对应清晰度的图片。

屏幕快照 2014-09-28 下午6.52.51

一个问题,和视频中使用的Xcode5不同,我已近更新了Xcode6,在创建界面的时候,对storyboard的大小控制和5不同。我就不知道如何控制控件的位置了……下来要再学习下。

分类的使用

之前对分类的理解比较迷惑,写了这个练习之后有些清晰了。首先这个练习并没有完全实践分类的用法,这是体现了一种形式——内部接口。

分类基本上有两个使用场景:对类进行扩展,特别是对不能接触源码的类进行扩展;作为内部接口。

在练习中体现的是作为内部接口使用。在ViewController.m文件中,Xcode自动生成的代码就包含了一个匿名分类。因为OC中@interface部分的实现是完全公开的,相当于public api,当我们需要声明属性、私有api时,就需要在匿名分类中操作。

这个练习中的业务逻辑Model:CardMatchingGame类,也有比较好的体现:

首先在@interface中声明readonly只读的public属性,这样外部访问时只能读取,没有修改的权限。

@property (nonatomic, readonly) NSInteger score;

之后在.m文件中使用匿名分类覆盖声明私有属性,并声明为readwrite。这样在@implementation部分就可以对其进行修改:
@interface CardMatchingGame()
@property (nonatomic, readwrite) NSInteger score;
@end

nil和空指针

在Android中,FC大部分都是由空指针异常引起的,这在iOS中不会出现。nil作为iOS中的空指针,和Android中的NULL不一样。NULL可以看做一个空对象,既然是对象,就是一个具体的事物,所以调用这个空对象的方法,显然是不合理的。而nil,相当于是一个不存在的地址,并不是具体的,同时因为OC的消息机制比Java纯粹,对应Java里的调用方法,OC中是消息发送,当像一个空地址发送一个消息,类似寄一封信,那么什么都不会发生,邮局并不会找你的麻烦。

所以和Java中需要注意空指针异常不同,OC完全不需要担心向空指针发送消息会导致程序FC,但是需要注意的是,既然向nil发送消息不会导致任何结果,那么当需要发送一个靠谱的消息时,需要提前对接受者进行判断——是否是nil。

编码习惯和细节

OC中对于编码习惯的限制不多,很多看似不一样的地方其实只是习惯不同而已,没有过多含义。下面对一些习惯和约定进行一下整理,其实都是公开课老师的习惯,还有一些细节:

  • 覆写父类的方法时,不需要再次在@interface中声明,因为使用这个类的人,“不需要这是覆写的方法”,只需要知道有这个方法,调用即可。
  • 在@interface部分使用NSInteger或NSUInteger等声明整型变量,在实现中使用int、unsigned int等进行对象创建。NS开头的声明方式,会根据运行环境的不同,声明不同大小的内存(32位或64位的差别)。
  • iOS7中新增了instancetype关键字,在init类方法中,作为返回值类型。
  • 使用@[…]语法可以生成NSArray数组。
  • 对于NSArray数组,可以使用array[index]语法直接获取index处的对象。