21.3 业务逻辑层开发

表示层依赖于业务逻辑层,因此我们最好先开发业务逻辑层。由于我们采用纯Swift编写应用,因此业务逻辑层也要采用Swift语言编写。

21.3.1 创建CalcLogic.swift文件

首先需要创建一个Swift源代码文件。具体操作方法为:右击工程名,在弹出的快捷菜单中选择“New File…”,然后再打开如图21-10所示的Choose a template for your new file对话框。

{%}

图 21-10 选择新创建文件模板

选择iOS→Source→Swif File文件模板,单击“Next”按钮,弹出如图21-11所示的对话框,在Save As中输入文件名“CalcLogic.swift”。在这里我们还可以选择文件保存目录,选择好之后单击“Create”按钮创建文件。如果成功创建文件,就会看到如图21-12所示的界面,一个空的Swift文件就创建好了。

{%}

图 21-11 创建文件对话框

{%}

图 21-12 创建Swift文件成功

21.3.2 枚举类型Operator

枚举类型Operator中包含了加、减、乘、除等运算符成员值,用来表示用户所点击的运算符。

Operator代码如下:

  1. enum Operator : Int {
  2. case Plus = 200, Minus, Multiply, Divide
  3. case Default = 0
  4. }

Operator枚举类型中定义了5个原始值,均是Int类型。Plus表示加运算符,值为200;Minus表示减运算符,值为201;Multiply表示乘运算符,值为202;Divide表示除运算符,值为203;Default表示默认值,值为0。这些原始值与在Interface Builder设计器中加、减、乘、除运算符按钮的tag属性值相同,这样的设计便于计算和判断。

Default成员需要注意,在没有点击任何运算符按钮之前,或者是在用户按了等号按钮计算完成之后,运算符的值都是Default,因此将Default的值设为0。

21.3.3 CalcLogic类中属性

下面我们参考图21-6以及表21-1编写CalcLogic类中属性。在CalcLogic类中添加如下代码:

  1. class CalcLogic {
  2. //保存上一次的值
  3. var lastRetainValue : Double
  4. //最近一次选择的操作符(加、减、乘、除)
  5. var opr : Operator
  6. //临时保存MainLabel内容,为true时,输入数字MainLabel内容被清为0
  7. var isMainLabelTextTemporary : Bool
  8. ......
  9. }

这几个属性都是存储属性,没有设置默认值,所以我们要在构造器中设置它们的默认值。各个属性的含义已在表21-1中说明了,这里不再赘述。

21.3.4 CalcLogic类中构造器和析构器

CalcLogic类中构造器可以初始化存储属性,由于CalcLogic类中存储属性初始化比较简单,也不需要参数初始化,所以我们设计了无参数构造器init(),代码如下:

  1. init () {
  2. println("CalcLogic init")
  3. lastRetainValue = 0.0
  4. isMainLabelTextTemporary = false
  5. opr = .Default
  6. }

我们在无参数构造器init()中初始化了3个存储属性,如果不在构造器中初始化属性,也可以直接在属性声明时初始化。

与构造器对应的是析构器,析构器代码如下:

  1. deinit {
  2. println("CalcLogic deinit")
  3. }

在析构器中也没有什么资源需要释放,因此在本例中我们只是简单地打印输出字符串。

21.3.5 CalcLogic类中更新主标签方法

CalcLogic类中定义了更新主标签方法,当用户点击数字按钮后,需要更新主标签内容,表示层给该方法传递的参数是数字按钮的tag属性和当前主标签内容。

这个过程的计算是比较复杂的:一方面,需要判断当前主标签内容是否是临时的,由于是进行数学计算,当前主标签内容就是计算的结果,当再次输入数字的时候,该数字应该替换主标签内容。另一方面,需要进行小数点的判断,如果已经有小数点了,就不能将小数点追加到主标签之后。在上述两种情况之外,就是正常的将数字追加到主标签之后。

该方法的代码如下:

  1. func updateMainLabelStringByNumberTag(tag : Int,
  2. withMainLabelString mainLabelString : String)->String {
  3. var string = mainLabelString
  4. if (isMainLabelTextTemporary) {
  5. string = 0
  6. isMainLabelTextTemporary = false
  7. }
  8. let optNumber = tag - 100
  9. //把String转为double
  10. var mainLabelDouble = (string as NSString).doubleValue
  11. if mainLabelDouble == 0 && doesStringContainDecimal(string) == false {
  12. return String(optNumber)
  13. }
  14. let resultString = string + String(optNumber)
  15. return resultString
  16. }

上述代码第①行定义方法,其中第一个参数是tag,它是按钮的标签属性。第二个参数是mainLabelString主标签内容,withMainLabelString是外部参数名。第②行代码是将参数mainLabelString赋值给字符串变量string。第③行代码是判断当前主标签内容是否是临时的,如果是临时的,就设置字符串变量string = “0”,并且设置isMainLabelTextTemporary = false

第⑥行代码let optNumber = tag - 100是通过按钮tag属性计算操作数,我们在Interface Builder设计器中设置0~9数字按钮的tag属性是100~109,因此tag -100计算要进行计算的操作数,如果我们点击的是数字按钮8,它的tag属性是108,要计算的操作数就是8。

第⑦行代码是将字符串string转换为Double类型。字符串string类型是Swift的String类型,不能直接转换为Double类型,需要借助于Foundation框架中的NSString类进行转换,Foundation框架是用Objective-C语言编写的。在Swift中,只要遵守一定的规范,Swift和Objective-C之间可以互相调用。因此有些功能在Swift中实现很困难,我们可以通过Objective-C的一些类实现该功能,然后再把结果返回给Swift调用程序。

虽然是在Swift中调用Objective-C语言,但是语法还是Swift的语法。第⑦行的(string as NSString).doubleValue语句是Swift的语法,首先是通过string as NSString语句将Swift的字符串String转换为Objective-C的字符串NStringdoubleValueNSString类的实例属性,能够将字符串转换为Double类型。

上述代码第⑧行是判断主标签内容转换为Double类型后是否等于0,而且通过doesStringContainDecimal方法判断是否有小数点。第⑨行代码是将Double类型转换为String类型。第⑩行代码是将字符串拼接起来,并返回给调用程序。

21.3.6 CalcLogic类中判断是否包含小数点方法

CalcLogic类中定义了判断字符串中是否包含小数点方法,在很多情况下都需要判断一个字符串中是否已经包含了小数点,如果包含则用户不能再输入小数点。总之这个方法是暴露给表示层,至于表示层判断它的目的是什么CalcLogic类中并不关心。

该方法的代码如下:

  1. func doesStringContainDecimal(string : String)->Bool {
  2. for ch in string {
  3. if ch == "." {
  4. return true
  5. }
  6. }
  7. return false
  8. }

Swift中的字符串String类没有提供直接判断字符串中是否包含某个特定字符的方法。我们可以使用循环遍历字符串,判断如果有字符为小数点“.”,则返回true ,否则返回false

21.3.7 CalcLogic类中计算方法

CalcLogic类中定义了计算方法,该方法的代码如下:

  1. func calculateByTag(tag : Int, withMainLabelString mainLabelString : String)->String {
  2. //把String转为为double
  3. var currentValue = (mainLabelString as NSString).doubleValue
  4. switch Operator {
  5. case .Plus:
  6. lastRetainValue += currentValue
  7. case .Minus:
  8. lastRetainValue -= currentValue
  9. case .Multiply:
  10. lastRetainValue *= currentValue
  11. case .Divide:
  12. if currentValue != 0 {
  13. lastRetainValue = currentValue
  14. } else {
  15. opr = .Default
  16. isMainLabelTextTemporary = true
  17. return "错误"
  18. }
  19. Default:
  20. lastRetainValue = currentValue
  21. }
  22. /记录当前操作符,下次计算时使用
  23. opr = Operator(rawValue:tag)!
  24. let resultString = NSString(format: "%g", lastRetainValue)
  25. isMainLabelTextTemporary = true
  26. return resultString
  27. }

上述代码第①行是定义方法,其中第一个参数是tag,它是按钮的标签属性。第二个参数是mainLabelString主标签内容。

第②行代码是switch 多分支语句,通过switch语句判断用户点击了哪个运算符,然后进行计算,计算的结果被保存到存储属性lastRetainValue中。注意当分支为.Divide(除法运算)时,如果除数为0,计算发生错误,则将字符串“错误”返回。

第③行代码是默认分支,把当前值currentValue赋值给lastRetainValue 属性。

第④行代码是将枚举的原始值转换为成员值,其中的tag是按钮tag属性值。表达式后面的“!”表示对其进行强力拆封,当tag无法转换时则把nil赋值给Operator

第⑤行代码是将Double类型的lastRetainValue格式化为字符串。格式化过程使用Objective-C的NSString类协助完成,format:参数是指定的格式,%g是用于格式化浮点类型数值。与%f%e不同的是,%g可以防止出现.00现象,.00现象就是,当输入的是一个整数,例如10,经过格式化后变成了10.00,在计算器应用中是不希望出现.00现象的。

第⑥行代码是给属性isMainLabelTextTemporary赋值为true,这是因为计算结束后,用户再输入数字时,原来主标签内容将被清除。通过布尔变量isMainLabelTextTemporary判断是否已清除主标签内容。

21.3.8 CalcLogic类中清除方法

当用户点击C(清除)按钮时,需要将CalcLogic类恢复到初始状态。我们可以通过重新实例化CalcLogic类来达到这一目的,但是重新实例化会耗费更多系统资源。如果不重新实例化,我们可以编写一个方法类似于构造器初始化存储属性,该方法代码如下:

  1. func clear() {
  2. lastRetainValue = 0.0
  3. isMainLabelTextTemporary = false
  4. opr = .Default
  5. }

该方法所做之事与init构造器是一样的,都是初始化3个存储属性。表示层通过调用该方法将CalcLogic类重新初始化。