Swift Tips:常用的知识点总结(持续更新...)

前不久 swift 2.2 发布,感觉差不多快趋于稳定了,就在工作之余把 swift 拿出来重新看了一下,总结一些常用的知识点。话说好记性不如烂笔头,放在这忘记的时候还可以拿出来瞅瞅,免得遗忘的时候到处查找,这里与大家分享一下

if let 和 guard

if letguard 只是语法糖,不使用也没关系,但是使用了以后使代码更简洁,逻辑更加清晰,举个例子,
我们平时写个参数是可选型的( Optional )函数的时候,往往需要在函数中作进一步的判断,比如我简单的判断输入的一个数是不是正数( > 0),正常的写法应该是这样的

1
2
3
4
5
6
7
8
9
func judgeTheNumber(number: Int?) {
    if number != nil {
        if number > 0 {
            print("This is a positive number")
        }
    } else {
        print("none")
    }
}

这里我们 judgeTheNumber() 这个函数的参数是 Int? ,所以我们得判断是不是 nil ,然后再判断是不是 > 0 ,比较正产的写法是这样的这样,但是代码好像比较多,有没有更简洁的呢?

if let

上面的代码我们使用 if let 的写法如下,

1
2
3
4
5
6
7
func judgeTheNumber(number: Int?) {
    if let number = number where number > 0 {
        print("This is a positive number")
    } else {
        print("nil or zero or negative number")
    }
}

这样的效果和上面的是一样的,通过if let number = number where number > 0这么一句是不是,代码更简洁,可读性更高了!

guard

guard 是 swift 2.0 的新特性,在 Xcode 7.0 推出来的,与 if 语句不同的是,guard 只有在条件不满足时才会执行,你可以把 guard 近似的看成 Assert ,上面的代码用 guard 就如下,

1
2
3
4
5
    func judgeTheNumber(number: Int?) {
        guard let number = number where number > 0 else {return}
//        当number满足上述条件时执行下面代码,否则执行上面的return
          print("This is a positive number")
    }

至于 if letguard 语法中出现的 where ,只是附加一些条件。相当于逻辑运算 &&||。当然上面的例子还有很多的写法,如果不熟悉,可以多写几次试试。

@available 和 #available

#available

在以前开发的时候,不同版本的 API 兼容着实让人头疼,所以会看到各种 #define 然后就是在代码中各种,if (iOS 8) 等等之类的,如果有的地方不注意,比如你调用一个方法是 iOS 8 才有的,然后你的版本最低支持 iOS 7 ,毫无疑问在 iOS 7 上调用的时候会崩溃。而 Swift 2.0 新引入的 #available 机制,就解决了这一问题。如果你在低版本上使用高版本的方法时,编译器检查的时候发现你没处理的话就会直接报错,如下
`Deployment Target` 为 `iOS 7.0`时使用`UIAlertController`
上面提示 UIAlertController 只有在 iOS 8 以后才可以使用,这时点击左侧的报错红点就会有提示,如下

点击左侧红点以后的提示
这时我们就可以看到有个提示是 Add if #available version check 就是用 #available 来做版本检查,同时原代码上就会加上一个 if else 的判断点击 Fix-it 后代码就变成

1
2
3
4
5
if #available(iOS 8.0, *) {
    let alert = UIAlertController.init(title: "温馨提示", message: "这是iOS8以后才有的方法", preferredStyle: UIAlertControllerStyle.Alert)
} else {
    // Fallback on earlier versions
}

然后再编译就不会报错了。
#available 这里也算是对开发方式的一个改进,也更加体现出 swift 一直强调的安全。这里 #available(iOS 8.0, *) 中,意思就是 iOS 8 以上,其中 * 表示全平台,无特殊说明的话都是 * ,同时后面还可以加参数,比如 #available(iOS 8.0, OSX 10.10, *) ,表示就是 iOS 8 以及 OSX 10.10 以上。

@available

@available 放在函数(func),类(class)或者协议(protocol)前面。表明这些类型适用的平台和操作系统。比如

1
2
3
4
 @available(iOS 9.0, *)
 func newMethord() {
//   This func can perform after iOS 9.0
 }

这样我们就可以像下面这样调用这个方法

1
2
guard #available(iOS 9.0, *) else {return}
newMethord()

如果你不加上 #available(iOS 9.0, *) 那么当你支持的版本低于 iOS 9.0 的时候毫无疑问会报错的。

selector

首先我们来看个例子,我想弹出一个 alert ,于是我写了个方法,如下

1
2
3
4
5
6
7
8
9
10
func showMyAlertMethord() {
    guard #available(iOS 8.0, *) else {return}
    let alert = UIAlertController.init(title: "温馨提示", message: "这是iOS8以后才有的方法", preferredStyle: UIAlertControllerStyle.Alert)
    alert.addAction(UIAlertAction.init(title: "OK", style: .Destructive, handler: { (action: UIAlertAction) in
        print("Handle OK action ")
    }))
    self.presentViewController(alert, animated: true, completion: {
        print("UIAlertController present")
    })
}

然后我在 viewDidLoad() 调用 showMyAlertMethord() 这个方法,运行发现弹出来了,没有问题。现在我想延迟三秒执行,这里我简单的用 performSelector() 这个方法,如下

1
self.performSelector(showMyAlertMethord(), withObject: nil, afterDelay: 3)

这时候报错

错误提示
一脸懵逼,噢,搞了半天发现原来是方法名写错了,这时候果断换上 Selector("showAlert") ,如下

1
self.performSelector(Selector("showMyAlertMethord"), withObject: nil, afterDelay: 3)

虽然有个警告,但是不管了,运行,出来了,完美解决!
于是就用这种方法写,于是有一天再用这个方法的时候,崩溃了!我的天,不是吧,之前都是好好的,一模一样的代码,查看源码

1
self.performSelector(Selector("showmyAlertethord"), withObject: nil, afterDelay: 3)

哦,找了半天,原来之前是 Selector("showMyAlertMethord" ,现在手一哆嗦,写成了 Selector("showmyAlertethord") ,坑爹啊,写错一个字母。虽然解决了问题,不过这时候你就应该有所思考,这种写法是不对的,或者说是不安全的,再回头过来看看那个警告
警告提示
意思是让我们用 #selector() 来代替 Selector(),所以到最后正确的代码应该如下

1
self.performSelector(#selector(self.showMyAlertMethord), withObject: nil, afterDelay: 3)

这就是在 swift 2.2 中提倡使用的方法 #selector() ,你会发现如果你输错方法名编译器会直接报错,而不是运行期来检查是不是有这个方法,这样编译期(compile-time)来检查出来,而不是运行期(run-time),这样就更安全了!
所以,在调用方法的时候,还是使用 #selector() 吧,最好还是不要使用 Selector() 或者 NSSelectorFromString() ,因为这样会更安全!

inout

升级了 Xcode 7.3 以后,简单的写一个例子,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
      var tempStr = "My "
       let result = stringPlus(tempStr)
        print("result is \(result)")
        print(tempStr)
    }
    
    func stringPlus(var str: String) -> String {
        str = str + "Loveway !"
        return str
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

输出结果

1
2
**result is My Loveway !**
**My **

这是简单的字符串拼接的一个例子,调用、运行并没有什么问题,但是你会发现有一个警告,如下

警告提示
没错,就是

1
'var' parameters are deprecated and will be removed in Swift 3

提示 var 类型的参数已经废弃了,并且将会在 swift 3 中移除!这是因为苹果认为 var 类型的参数会有限制,var变量参数只是在函数体内有用,超出作用域就是失效了,于是就使用了 inout 来代替 var,修改代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        var tempStr = "My "
       let result = stringPlus(&tempStr)
        print("result is \(result)")
        print(tempStr)
    }
    
    func stringPlus(inout str: String) -> String {
        str = str + "Loveway !"
        return str
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

输出结果

1
2
**result is My Loveway !**
**My Loveway !**

嗯,好像没有警告了,完美解决!等等,什么,两次输出的结果不一样???第一次 tempStr 的值是 My,改成 inout 后的值变成 My Loveway ! 了,这是因为 inout 是可以在函数作用域内改变参数的,并且还可以返回(不管你的函数有没有返回值,只要是在函数作用域内改变了这个 inout 参数)。
inout 语意解释为输入输出参数(In-Out Parameters),输入输出参数被传递时遵循如下规则:

  • 1、函数调用时,参数的值被拷贝。
  • 2、函数体内部,拷贝后的值被修改。
  • 3、函数返回后,拷贝后的值被赋值给原参数

这种行为被称为拷入拷出 (copy-in copy-out) 或值结果调用 (call by value result)。例如,当一个计算型属性或者一个具有属性观察器的属性被用作函数的输入输出参数时,其 getter 会在函数调用时被调用,而其 setter 会在函数返回时被调用。
作为一种优化手段,当参数值存储在内存中的物理地址时,在函数体内部和外部均会使用同一内存位置。这种优化行为被称为引用调用 (call by reference),它满足了拷入拷出模型的所有需求,而消除了复制带来的开销。不要依赖于拷入拷出与引用调用之间的行为差异。但是你不能将同一个值传递给多个输入输出参数,因为多个输入输出参数引发的拷贝与覆盖行为的顺序是不确定的,因此原始值的最终值也将无法确定。

现在我们就不难理解为什么 tempStr 的值发生了变化,这也是 swift 要废弃 var 参数的原因。
似乎现在警告也解决了,inout 也知道了,于是继续向下写,写一个数字转英文的例子,如下

1
2
3
4
5
6
7
8
9
10
11
12
        let digitNames = [0: "one", 1: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine"]
        let numbers: Array = [14, 15, 16]
        let strArray = numbers.map( {
            (var number: Int) -> String in
            var output = ""
            while number > 0 {
                output = digitNames[number % 10]! + output
                number /= 10
            }
            return output
        })
        print(strArray)

输出结果为

1
**["twofour", "twofive", "twosix"]**

没有问题,但是警告又出来了

数字转英文的警告提示
通过上面,于是我们改成了 inout ,结果报错

改成`inout`后的报错
似乎在这里 inout 并不能解决问题,这种情况下,有一种消除的警告的方法就是用一个临时变量接受这个输入的参数,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
let digitNames = [0: "one", 1: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine"]
let numbers: Array = [14, 15, 16]
let strArray = numbers.map( {
    (number: Int) -> String in
    var number = number
    var output = ""
    while number > 0 {
        output = digitNames[number % 10]! + output
        number /= 10
    }
    return output
})
print(strArray)

这样就解决了问题,这个也是苹果官方提供的一种解决思路。

@noescape

关于闭包(Closures)这里就不多说了,不清楚的童鞋可以去 这里 了解一下。
我们都知道闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。那么非逃逸闭包(@noescape)到底是个什么意思呢,举个栗子

举个栗子
如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        funcWithEscapeClosure(clouserTest)
        print("222")
    }
    func funcWithEscapeClosure(someFunc: () -> Void) {
    someFunc()
        print("111")
    }
    func clouserTest() -> Void {
        print("333")
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

那么我们会看到控制台的输出信息如下

1
2
3
**333**
**111**
**222**

从上到下看,按照函数的执行顺序,这个我们都能理解,是没错的。这个时候我们知道 clouserTest () 是在 funcWithEscapeClosure()被调用后执行的,那么这个就是非逃逸闭包,对于非逃逸闭包,我们可以在参数名之前加一个 @noescape,用来标注这个函数是不能逃逸出函数体的,这样做的好处就是能让编译器明确的知道这个函数的生命周期,以做进一步的优化。比如上面的函数我们可以改成这样

1
2
3
4
    func funcWithEscapeClosure(@noescape someFunc: () -> Void) {
        clouserTest()
        print("111")
    }

编译运行肯定是没有问题的。
有逃逸闭包肯定就有非逃逸闭包,非逃逸闭包的定义是:当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。这种情况大都是在异步操作的时候用到,还是上面按个例子,我们修改一下,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        funcWithEscapeClosure(clouserTest)
        print("222")
    }    
    func funcWithEscapeClosure(someFunc: () -> Void) {

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
           someFunc()
        }
        print("111")

    }
    func clouserTest() -> Void {
        print("333")
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

然后看看输出结果是

1
2
3
**111**
**222**
**333**

结果表明 someFunc() 也就是 clouserTest() 是在最后执行的,这里的 clouserTest() 就从 funcWithEscapeClosure() 这个函数中逃逸出了,这个时候如果在参数前面加一个 @noescape 会报错,如下

错误提示
如上,提示闭包使用的非逃逸(@noescape)参数可能需要允许它逃逸,这是因为 funcWithEscapeClosure() 这个函数执行完毕之后才会执行 someFunc() ,也就是说这种情况就是 someFunc() 就是逃逸出了函数,这里加 @noescape 肯定是不对的。
还有一点就是编译器知晓非逃逸闭包的上下文环境,所以非逃逸闭包中可以不写 self。比如你的类有一个 name 的变量,在非逃逸闭包中你就可以直接用 name = "loveway" ,而无需 self.name = "loveway" 这样。

mutating

mutating 从字面意思来看就是变化、改变,我们知道在 Objective-C 中只有类(class)中才可以定义方法,然而在 Swift 中,我们可以在类(class)、结构体(struct)、枚举(enum)中定义方法,这也是 Objective-C 和 Swift 的一个区别。下面以一个结构体举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        var man = Man(height: 170.0, weight: 62.0)
        print(man)
        man.gainHeightAndWeight(1.0, gainWeight: 2.0)
        print(man)
        
    }

    struct Man {
        var height = 0.0, weight = 0.0
       mutating func gainHeightAndWeight(gainHeight: Double, gainWeight: Double) {
            print("Now height is(\(height + gainHeight)), weight is (\(weight + gainWeight))")
        }
    }
}

然后输出

1
2
3
**Man(height: 170.0, weight: 62.0)**
**Now height is(171.0), weight is (64.0)**
**Man(height: 170.0, weight: 62.0)**

我们可以看到控制台输出的结果是没有问题的,man 的值没有变化。不过这个时候会有一个警告,如图
警告截图
意思是说这个 man 是不可变的,建议使用 let 来替代 var ,不去管它。现在来修改一下代码,如下

1
2
3
4
5
6
7
8
    struct Man {
        var height = 0.0, weight = 0.0
        func gainHeightAndWeight(gainHeight: Double, gainWeight: Double) {
//            print("Now height is(\(height + gainHeight)), weight is (\(weight + gainWeight))")
            height += gainHeight
            weight += gainWeight
        }
    }

编译就会报如下错误
编译报错
这是因为结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择可变(mutating)行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的 self 属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。要使用可变方法,将关键字 mutating 放到方法的 func 关键字之前就可以了,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        var man = Man(height: 170.0, weight: 62.0)
        print(man)
        man.gainHeightAndWeight(1.0, gainWeight: 2.0)
        print(man) 
    }
    struct Man {
        var height = 0.0, weight = 0.0
       mutating func gainHeightAndWeight(gainHeight: Double, gainWeight: Double) {
//            print("Now height is(\(height + gainHeight)), weight is (\(weight + gainWeight))")
            height += gainHeight
            weight += gainWeight
        }
    }
}

控制台输出

1
2
**Man(height: 170.0, weight: 62.0)**
**Man(height: 171.0, weight: 64.0)**

这个时候 man 的值就改变了

若您觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏!
------------- 本文结束 感谢您的阅读 -------------