在前面的几章中我们经常接触到Optional(可选型),今天来细致讲解一下可选型。
在上一话中我们接触到了enum(枚举),那么有一个惊人的事实是:可选型就是一个枚举!它是一个非常简单的枚举,它是一个泛型类似于数组。就像数组的定义Array<T>,我们给T不同的值,那么就代表不同值类型的数组,而数组的定义只有一个Array<T>,非常的简便。可选型也是一样,看上面的例子。x是一个可选型,如果有值得话值类型是String,没有值的话就是nil,那么有值没值分别对应于可选型的Some和None,写法如上所示。有可选型的时候总是有对应的解包,也就是感叹号!那么感叹号其实是一个switch操作,如果可选型里面是有值的就会取到这个值,如果没有就会抛出异常。所以当你解包一个nil的时候会崩溃,大家要注意。
Array(数组)之前我们用到很多次了,大家应该比较熟悉。通常我们定义一个数组使用var关键字,如果你使用let关键字,证明数组是一个常量,你将不能使用方法来增加或者删除这个数组中的元素。如果你的数组中只有四个元素,但是你要访问第五个元素,那么会提示数组越界。如果你想要遍历数组的话,使用for in语句。
Dictionary(字典)我们在上一话中也用到过,它有点类似于数组,它的鲜明特征是键值对[Key,Value]。我们使用key来访问value的值,如果你的key不在字典中,那就会返回一个nil,这点不像数组的越界。这很好理解,就像字面意思,我们遇到不认识的字去查字典结果可能找到也可能没找到。区别于数组,如果要遍历一个字典,那么我们使用一个元组来返回值。
Range(范围)这是一个新的结构体,之前没有遇到过。Range在Swift中就是一个指向头和尾的指针。Range也是泛型的,Range通过Int索引,在String中有一种特殊的索引叫String.Index,所以String中我们使用Range<String.Index>。在使用Range索引数组的时候我们通常使用“...”或者“..<”这样的写法。我们使用.的时候其实就已经建立了Range,这样的写法比较简单明了,好过我们使用结构体形式的Range。
在OC中NSObject是所有类的基类,但是在Swift中我们并没有这种类,但是它有一些在IOS中的高级特性,你的Swift类也可以继承自NSObject。在IOS中你最好让你的所有类都继承自NSObject类。
NSNumber类是一个装数字的类,它里面有很多方法,比如DoubleValue、IntValue等,会将自身的值以Double、Int的类型返回给我们,在Swift中不会用太多这样的东西,因为Swift是强类型的。
NSDate类型可以获取当前的日期和时间。
NSData很简单,他就是一个比特包,不管大小它里面都是无类型的数据,IOS通过这个类传递无类型的数据,原始数据。
下面来介绍一下啊Swift中的数据结构。
Swift中的结构体包含三大块,类、结构体和枚举。它们的声明在结构上看起来很相似
另外它们还可以拥有方法和属性,枚举本身不能存储数据,但是你可以将数据存储在枚举的关联信息中。
结构体和类甚至可以有自己的构造器。
类是三者中唯一拥有继承属性的,内省和转型也是类的特性,三种类型最主要的区别就是值类型和引用类型,接下来的Demo中会讨论这个问题。结构体和枚举的传递和存储是通过拷贝过的变量,类属于引用类型,我们传递的是这些对象的指针,而这些对象存储在(Heap)堆中,堆中的对象系统会自动为我们管理(ARC),这样我们就不用去开辟和释放内存空间了,一旦没有指针指向对象,那么对象会马上被清理掉,但这不是垃圾回收机制,这种机制叫做自动引用计数(ARC)。
下面细致地讲一下值引用与类型引用。
对于值引用,比如结构体,它表示你将它传递给一个方法的时候,使用的是拷贝,当你将它赋值给另一个变量的时候也是如此,得到拷贝后进行修改,你修改的也是你拷贝的值而不是原来的那份。基于这个原理,当你得到使用方法修改结构体或者枚举的时候,你必须在方法前加上关键字“mutating”。
引用类型存储在堆中,即使是一个常量指针,也会导致引用计数增加,对于一个常量计数的指针,你同样可以向这个常量指针发送消息来修改所指对象里面的属性,因为这个对象存储在堆里面,而你可以用一个指针来引用它,当你把这个对象传递给一个方法的时候,你传递的是指向这个对象的指针,如果这个方法修改了这个对象,那它修改的就是存储在堆中的那个对象了。
那么问题来了,我们该如何正确选择这两种对象呢,90%的情况我们都会使用类,因为这是面向对象编程语言,当你使用类时你可以使用继承、重写之类的特性,而结构体更适合基础数据类型,比如Int、Array等,当我们使用绘图的时候我们会更多地使用points、sizes、rectangles,它们都是结构体。大多数时候我们使用结构体,因为使用类是一件非常轻松的事情了,系统会自动帮我们管理内存。
下面来聊聊继承,当你要重写父类的方法时,你需要在方法前加上override关键字。你也可以把方法标注为final,这样表示这个方法不能被重写。你甚至可以把类标注为final表示任何人都不能修改这个类,当然也不能继承这个类。
类和实例都有自己的方法和属性,如示例中所示,d是一个Double类型,判断d是否是负数,如果是负数就把它设置为自己的绝对值。所以isSignMinus是d的一个属性,你通过一个具体的实例d发送isSignMinus消息,来判断d是否是负数,然而abs是Double这个类的方法(所有对象共享),你向这个方法中传入你需要操作的Double类型的实例,它返回你要的结果,这里你并没有向一个特定的实例发送消息,而是向Double这个类在发送消息,所以这个方法和判断语句中的方法是不一样的,但是规则相同,区别只是你叫谁去完成这个任务。
如果你在定义一个变量或者方法的时候使用了关键字static,那么我们调用的时候就必须用这个类本身来调用,而不是类的实例,如例子中的abs。
所有方法中的参数都有一个内部的名字和一个外部的名字,内部的名字是在方法内部使用的,外部的名字是在调用的时候使用的,在外部调用的时候我用了external,尽管在方法内部它的名字叫internal,其实是一个东西。
你可以使用下划线来忽略参数的外部名称,这样我们调用的时候直接写值就可以了。其实方法的第一个参数的默认外部名称就是一个下划线,这也就解释了为什么我们在做IOS开发的时候方法的第一个参数总是不用写参数名。
如果你需要输入第一个参数名的话,需要在内部变量名前加上一个#,这样你在调用的时候就不得不输入第一个参数名。
除了第一个参数外的其他参数似乎就没有这种礼遇。它们的内部名称和外部名称是必须要写的。
。
你可以在后面的参数中添加下划线来取消调用时的参数名,但是这样做不好,这不是标准做法。第一个参数名不需要显示的原因是在Swift中你通常会任务方法的名字描述了它的第一个参数,你的第一个参数的名字始终需要基于方法的功能来定义,基于这种关联,所以我们不需要知道第一个参数的名字。
接下来我们来讲一下属性的相关知识。
Property Observers(属性观察者)可以用来检测属性值的变化。我们可以看到示例中有一个Int实例,值为42,它没有计算过程,但是后面却有一对花括号,这跟我们之前接触的存储属性、计算属性的概念不太一致。花括号的意思并不是说这个属性的值是通过计算得到的,而是说它的值是通过willSet和didSet得到的。willSet和didSet中的代码会在设置或者获取属性值得时候被调用,这也就是为什么哥俩会被叫做属性观察者的原因。根据字面意思我们可以知道,这两个方法分别表示值被调用之前和调用之后起作用。
在willSet中有一个特殊的变量叫做newValue,代表属性将要被赋值的新值,同样didSet中的oldValue代表属性被设置之前的旧值。
在IOS开发中非常常见的做法是更新用户界面(UI),比如你对页面上的某些属性做了修改,那么就需要在didSet方法中对用户界面进行更新。
接下来来谈谈惰性实例化的内容。如果你在属性前面使用了lazy关键字,代表着它只有被用到的时候才会初始化,比如赋值的初始化操作,惰性初始化使得这个属性的赋值只有在属性被调用的时候才会发生。你也可以在初始化的时候在后面放一个圆括号,也就是通过执行闭包来初始化属性,但是这个闭包只有在属性被用到的时候才执行。甚至可以使用方法来初始化,但是这种做法必须使用lazy关键字,因为在类被初始化之前是不能调用类的方法的。惰性属性依旧遵循类在初始化的时候所有属性必须初始化的规则,即便这些惰性初始化的属性在被用到之前不会初始化。另外只有用var定义的属性 才能用lazy,而let定义的属性必须在类的初始化方法中进行初始化。
这个特性通常被用来处理一些错综复杂的初始化依赖。依旧是因为类初始化其属性必须初始化的规则。比如有些属性初始化的时候会依赖另一些属性,这样可能会造成卡顿,所以依靠惰性加载的方法可以解决,这是非常好的特性。