开篇
接触Swift大概有1个多月的时间了,刚开始学习Swift的那段日子真是苦不堪言,面对着一个陌生的语言,真是不知道该从哪里着手开始学习,所以跌跌撞撞地到处碰壁,踩坑。不过经过了这一段时间自己的摸索,以及向各路大神请教,加上网上各种检索资料,到现在应该算是一脚踏入了Swift的大门了,尽管另外一只脚还在门外,但我会不断地学习的。
因为我是从Objective-C语言转过来学习Swift的,所以刚开始接触Swift的时候,总想着在Swift里怎么实现OC的那些风格习惯,比如说使用OC中的宏定义、PrefixHeader文件等等,我想,会OC的童鞋们在学习Swift时可能也会有和我一样的想法,因此,在这里我将我学习Swift 的一些经验分享给大家,可能有些内容我说的不准确,甚至是错误的,还请大家帮我指出,另外,我有个和大家一起讨论Swift3.0学习的技术交流群185826825,欢迎大家来与我们共同学习!我的本系列其它文章:
Swift3.0学习笔记(二)
写在前面
当尝试着用一个自己陌生的语言,去实现一个自己想要达到的功能,是很令人兴奋的。所以,在文章的开始,我想先用一个极其简单的demo作为Swift学习的开始,这个demo的功能是页面上有一个按钮,点击按钮跳转到另一个页面。
大家看这段代码,是不是发现和OC的风格很像呢?是不是发现自己很容易就能看懂呢?其实我想说的是,世上无难事,只怕有心人,只要你愿意花时间去学习,你就会发现其实他并不难。
基础
-
导入文件###
在Swift中,同一个工程项目不需要引入各自的类文件,比如我新建了一个工程,里面有两个ViewController,我在vc_A中希望引用vc_B的某个公开属性,这时在我的vc_A中是不需要引入vc_B的文件的。
不在本工程内创建的文件,如一些系统库,或是通过CocoaPods加入到工程的,在使用时则需要引入到工程内,引入时也区别OC,简单使用import 库名
即可,例如:
import UIKit
import ReactiveSwift
-
常量与变量###
在Swift中,使用let
来表示常量,var
来表示变量,所谓常量,即为不可改变的量,比如你声明一个UIButton
对象,后面不会给这个对象赋值成别的什么按钮对象,初始化时即在内存中给这个对象开辟了一块空间,后面不会去改变这个对象的地址,因此,你可以这样来创建这个对象:
let btn = UIButton(type: .system)
改变对btn
的一些属性设置,不会影响这个对象在内存中的地址变化,因此,也就不需要将btn
设置成var
类型,这个看你的需求而定。
像在OC中常使用的NSMutableArray,在Swift中没有类似可变数组这样的类,可以直接声明一个数组类型的变量来达到同样的效果,比如这样:
var array: Array<String>
需要说明的是,Array<String>
为字符串类型的数组,关于数组后面会介绍。
-
数据类型###
大体上数据类型和OC也没有什么差别,布尔类型的值从OC的YES/NO
换成了true/false
,其它值得注意的就是Swift本身是类型安全的语言,因此像在OC中习惯使用的小数,比如CGFloat和Float在使用运算符进行运算时就会报类型不一致的错误了:
修改方法是需要将其中的一个进行类型转换,以保证两个进行运算的值的类型是一致的:
let a: CGFloat = 0.3
let b: Float = 0.4
let sum = Float(a) + b
值得说明的有两点,
- Swift的变量和常量在声明时可以不说明它的类型,编译器会通过初始化的值对该变量或常量进行类型推断。
通过这张图我们可以看出来,我在声明常量a时,并没有指定a的数据类型,而是通过给a进行初始化赋了一个值0.3,这时候编译器会根据初始化的值对常量a进行类型推断,推断出的结果是常量a是一个Double类型的常量。
- Swift中不需要
;
作为句尾结束符,因此在Swift中对于空格
的使用就要注意一些,比如在赋值符=
的左右两边,都必须有至少一个空格
才能正常编译通过。
-
输出函数###
由于Swift可以兼容OC,因此我们仍可以继续使用NSLog输出函数来进行输出,同样,Swift也提供了自己的输出函数,Print,这个函数中不再需要占位符了,你希望输出一个变量类似这样:
let name = "Shaw"
print("Hello \(name)")
或者这样
let name = "Shaw"
print("Hello" + name)
-
可选类型###
这个可以算是Swift相较OC变化较大的内容了,这就是你在阅读Swift的代码时经常能够看到的在一个变量的后面,跟了一个?
或者!
,这就跟可选类型相关。
声明一个可选类型的变量,表示这个变量可以被赋值为nil,这个不同于OC,在OC里,所有的对象
都可以被赋值为nil,在编译时不会报错,但是Swift不可以,如果一个对象在声明时没有声明成可选类型,那么这个对象在编译时是不允许被赋值为nil的。比如下面这样:
这样就是不允许的,这时,我们发现报的这个错误编译器可以帮我们自动修正,修正后就是这个样子了:
var x: UIImageView? = nil
可以发现,编译器只是帮我们在数据类型的后面增加了一个?
,这样就可以将变量x赋值为nil了。接下来我们给这个UIImageView对象赋一张图片,像这样:
x = UIImageView.init(image: UIImage.init(named: "abc"))
接下来,我们再声明一个UIImage类型的常量y,并将变量x的image属性的值赋给y,这时候我们不允许y为nil,我们这样做:
你突然发现编译器给我们报了两个错误,先不要着急,让我们来一一看这两个错误都是什么,
- 第一个错误点在x的下面,说
可选类型的UIImageView没有打开,你是要使用'!'或者'?'么?
,这个错误发生的原因和之前我们在说Float和CGFloat那部分的问题是一样的,由于Swift是类型安全的语言,因此一旦你声明确定了一个变量或常量的类型,那么这个变量或常量无论在编译时还是运行时都只能是这个类型的,对于刚才变量x下面的那个错误点,因为只有真正的UIImageView对象,才会有image属性,而由于我们在声明变量x的类型时,将x声明成了可选类型,也就是允许x = nil
,如果在访问x的image属性前,x的值是nil的话,程序运行就会崩溃,所以编译器为了避免这个直接导致崩溃的问题发生,在编译时会要求我们将可选类型的变量进行解包
操作,只有解包后的变量x的数据类型才是真正的UIImageView类型,解包的方式就是在可选类型变量的后面加上?
或者!
即可,那么这二者的区别又是什么呢?
?
表示尝试将这个变量或常量进行解包,如果解包后x的值是nil,那么程序将不再去访问x的image属性;!
则不同,其表示强制对x进行解包,如果发现解包后的x的值是nil,则程序会崩溃,因此需要慎用!
。 - 明白了第一个错误是如何产生的,第二个问题也就迎刃而解了,同样,我们在给一个UIImage类型的常量y赋值时,编译器不允许y被赋值为nil,因此会强制要求你将
x.image
后面加上!
的,注意,这时候后面不可用?
,原因还是由于Swift是类型安全的语言,不能尝试对x.image
进行解包,如果你解出来是个nil怎么办?因此编译器直接让你强制解包,解出来是nil的话就搞崩溃你。
下面有两种对于刚才这个问题的正确写法,
let y: UIImage = x!.image!
let y: UIImage = (x?.image)!
对于这两种写法,都是正确的,只不过解包的思路有些不同,上面那种是先将x强制解包成UIImageView对象,再对他的image
属性进行强制解包;下面那种是先尝试将x解包,然后访问他的image
属性,最后对访问的这个属性进行强制解包。
说到这也许你会问,虽然大概明白了什么是可选类型,以及什么是解包,为什么要解包,但是你在开发时,仍然不可避免的忽略掉这些,不要捉急,机智的编译器已将替大家考虑到这个问题了,他会在你写代码的时候,悄悄的自动为你加上这些符号,如果你真的写错了的话,他还能帮你自动修正,是不是发现这时候的Xcode真的挺可爱的呢?
-
运算符###
基本的运算符还都和OC一致,不过在使用运算符进行运算的时候需要注意类型一致,另外,在Swift3之前的版本中,支持对浮点数进行求余操作,但是Swift3不再支持了,系统提示使用一个方法进行代替:
var a: Float = 10.5
//a = a % 3
a = a.truncatingRemainder(dividingBy: 3)
输出结果1.5
以下表格列出了在Swift3中支持的基本运算符,举例: x = 10, y = 20
运算符 | 运算 | 结果 |
---|---|---|
+ | x + y | 30 |
- | x - y | -10 |
* | x * y | 200 |
/ | x / y | 0 |
% | x % y | 10 |
对于OC和Swift3之前的版本所支持的++
和--
运算,在Swift3中只支持这样的形式:
实例 | 等价 | 结果 |
---|---|---|
x += 10 | x = x + 10 | 20 |
y -= 1 | y = y - 1 | 19 |
Swift3中的逻辑运算符和位运算符都与OC没有什么差异,需要注意的是,像这样在OC中可以用位运算符中的逻辑或
囊括的多个枚举值:
[UIView animateWithDuration:0.1 delay:0 options:
UIViewAnimationOptionAutoreverse
| UIViewAnimationOptionAllowUserInteraction
animations:^ {
} completion: nil];
在Swift中写起来要麻烦一些:
UIView.animate(withDuration: 0.1, delay: 0, options:
(UIViewAnimationOptions(rawValue:
UIViewAnimationOptions.autoreverse.rawValue
| UIViewAnimationOptions.allowUserInteraction.rawValue)),
animations: {
}, completion: nil)
另外,在Swift3中,增加了一个符号??
,该符号的用法如下:
实例 | 等价 |
---|---|
let a = b ?? c | let a = b != nil ? b! : c |
举例说明,一个函数的功能是接收一个可选字符串类型的参数,返回一个字符串,如果传进来的是nil,就将参数重新赋值成一个既定的字符串并返回,代码如下:
func showMessage(msg: String? = nil) -> String {
let msg = msg ?? "默认字符串"
return msg
}
Swift3.0的区间运算符:
实例 | 等价 | 说明 |
---|---|---|
0...10 | 0 <= x <= 10 | 从0到10的闭区间 |
0..<10 | 0 <= x < 10 | 从0到10的左闭右开区间 |
-
数据存储###
-
枚举####
你可以声明一个枚举,像这样:
enum ControlCMD {
case up, right, down, left
}
比如我们现在有个需求,滑动手指时输出一个手指滑动的方向的英文,在Swift里我们只需要这样实现:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let leftGR = UISwipeGestureRecognizer
.init(target: self, action: #selector(swipe(_:)))
leftGR.direction = .left
let rightGR = UISwipeGestureRecognizer
.init(target: self, action: #selector(swipe(_:)))
rightGR.direction = .right
let upGR = UISwipeGestureRecognizer
.init(target: self, action: #selector(swipe(_:)))
upGR.direction = .up
let downGR = UISwipeGestureRecognizer
.init(target: self, action: #selector(swipe(_:)))
downGR.direction = .down
self.view.addGestureRecognizer(leftGR)
self.view.addGestureRecognizer(rightGR)
self.view.addGestureRecognizer(upGR)
self.view.addGestureRecognizer(downGR)
}
enum ControlCMD: String {
case up, right, down, left
}
func swipe(_ sender: UISwipeGestureRecognizer) {
switch sender.direction {
case UISwipeGestureRecognizerDirection.up:
self.sendMessage(cmd: .up)
case UISwipeGestureRecognizerDirection.down:
self.sendMessage(cmd: .down)
case UISwipeGestureRecognizerDirection.left:
self.sendMessage(cmd: .left)
case UISwipeGestureRecognizerDirection.right:
self.sendMessage(cmd: .right)
default: break
}
}
func sendMessage(cmd: ControlCMD) {
print("滑动的方向" + cmd.rawValue)
}
}
注意,我在声明枚举时,将枚举类型指定为String
类型的枚举,这样,我在发送消息时就可以使用cmd.rawValue
来访问枚举值的字符串了。
Swift中的枚举还有一些更高级的用法,因为我暂时还没有用过,所以也不在此描述了,以后遇上时再补充上。
-
元组####
对于元组这个概念,这是OC中所没有的,我理解的元组是将多个值组合成为一个值,感觉有点像数组,但是元组中的值的类型可以是任意类型的,举个例子:
let tuple = ("abc", 1, 0.5, [UIImage()])
这就是一个元组,元组中可以有多个元素,也可以只有一个元素,当然,由于元组就是为了存储多个值的,如果只有一个值也就不需要元组,多个值的时候,我们可以像数组那样,通过使用序号来访问元组中的元素,比如这样: