前言
因为公司打算项目全面转 Swift
,于是我与同事开始了漫长的 Swift
探索之路。初用的时候会很不习惯,因为Swift
和Objective-C
从语法上来说是两门完全不同的语言,OC
延续了C
系语言一贯的啰嗦,需要些很多的代码才能完善这个类或变量,而这些啰嗦的语句其实也深深的嵌入了项目之中,拖慢整个项目的编译进度,增大安装包(虽然不太明显)。
这篇文章也不是Swift
的教程,而是一名OC
程序猿转Swift
时所遇到的困难。
Optional
Swift
是一门更加安全的语言,相信很多和我一样的OC
开发者在新建项目和变量的时候已经开始习惯了这样的方式
1 | NS_ASSUME_NONNULL_BEGIN |
其中的 NS_ASSUME_NONNULL_BEGIN
和NS_ASSUME_NONNULL_END
还有nullable
与之对应的nonnull
就是用来增加安全性的设置。我们以前跑项目时经常遇到某个不应该为nil
的对象突然变成nil
的情况,其实这样是非常不安全的,但是在多线程的运行中,我们也不一定知道这个对象是在哪里变成nil
的,所以这样的声明是非常有必要的。这样我们就可以清楚的知道这个对象是否可以为空,这样在传值的时就可以接收到警告,或者运行时就会崩溃方便定位。
Swift
的解决方式就是使用Optional
,在Swift
里我们经常会这样声明变量
1 | var str: String? |
这个问号就很精髓,它就是让这个没有初始化的变量变成一个Optional
。Optional
相当于向上封装了一层,代表这个变量有值,或者这个变量没有值(废话),但是和不用nullable
和nonnull
的OC
变量不同,你如果直接打印str
,那你会发现它并不是String
类型,而是一个Optional
的类型,里边有Optional.Some
和Optional.None
两种类型,如果我们没有给他一个初值,那Optional
就会返回None
来告诉我们这个变量为nil
,而如果我们想访问这个变量的值,那我们需要做的,就是解包。
解包
常见的解包方式一般有两种,一种就是比较暴力的!
和相对科学的??
语法,而另一种则为可选绑定
! 与 ??
!
通常用在我们知道这个值是必有的情况下,如果我们使用了!
但这个值为nil
,那就会引起崩溃,一般来说如果不是100%确定变量有值,是不推荐使用的。
1 | var str: String? |
另一种??
是一个比较常规的方式,代表如果这个变量为nil
,则变量为??
之后的值。我们在OC
中也会经常用三段表达式进行类似的判断(比如 NSString *str = tempStr ?: @""
,而Swift
里的??
会使这个表达式更加方便简单。
1 | var str: String? |
可选绑定
可选绑定是一种比较推荐的解包方法,除了麻烦一点,几乎没什么缺点。
它是用一个变量去取Optional
的值,再根据这个变量的值来进行操作。
比较常见的是if let
和 guard let else {return}
方法。多说无益,上代码。
1 | var str1: String? |
由代码可见,if let
方法在它的作用域内,我们可以用解包后安全的值进行操作。而guard let else {return}
拿到的安全值可以在这个方法的作用域内进行操作。具体选用哪种就需要根据开发者的需求来定了。
还有一种可以将两种方式结合的解包方法,不过极不推荐,至于为什么,看代码就知道了!
1 | var str1: String? |
因为guard else
方法将nil
的变量return
了,所以我们强制解包的时候不会有什么问题。但if let
就不一样了,因为用了强制解包,根据语义应该是必定有值的,但是却并没有,这对于要写优雅代码的我们来说是个不好的写法。
总结
Optional
让 Swift
的变量更加安全了,我们在写代码时就能发现很多变量存在的问题,在运行时也不用担心变量突然为 nil
的问题了。
Initialize
搞过 Swift 开发的同学应该知道,Swift 的初始化可以说是相当严苛的,那苹果这么做的目的是什么呢?
其实就是安全,单纯的安全。我们知道,在 OC 中,init 方法是很不安全的,你永远也猜不到一个 init 方法里到底有没有初始化每个变量。
所以 Swift 有了一套很严格的初始化方法,引入了一套名词——或许以前就有?——designated
、required
、convenience
。
designated
designated
关键字主要是指明这个方法是子类必须要调用的方法比如 cell 里需要调用的initWithStyle:reuseIdentifier:
,虽然这个解释看起来更像required
,但是required
其实是来指明子类必须进行重写实现的方法。designated
保证了父类指定的方法肯定会被子类调用,保证该对象可以进行完整的初始化。
convenience
convenience
则是一种旨在“补充”的初始化方法,相信大家都在 OC 中的类中声明了许多 init
方法,而这些方法都是调用的一个方法(其实就是 designated)
,与那个唯一方法的区别就是少了几个变量的初始化(转为了固定值)。convenience
就是这样的一个方法,以convenience
声明的init
方法必须调用自己类 (self) 的designated
。值得一提的是convenience
方法是不允许子类调用的或重写的,这也保证了所有类都必须调用父类的 designated 方法完成完整的初始化
。
1 | class ClassA { |
不过如果你重写了父类convenience
中调用的init
方法,那你也可以直接在代码中用父类的convenience
初始化方法初始化子类。
1 | let A = ClassB(bigNum: true) |
required
我们可以将一个初始化设置为required
来让子类强制重写它。比如我们可以让子类重写父类convenience
中所需要的designated
方法,来确保子类也可以用父类的convenience
方法初始化。
1 | // 还是上面那个方法,不过这次强制子类实现 init(num:)方法了 |
我们甚至组合使用convenience
和required
来让子类强制实现(相当于重新声明一个convenience
)父类的convenience
方法。这样做可以保证子类不能直接使用到父类的convenience
方法初始化。
总结
根据上面说的,我们可以总结出初始化的时候应该遵循几个原则:
本类必须要有个
designated
来达到初始化的目的本类的所有
convenience
初始化方法,都必须调用自己类的designated
初始化方法来达到完全的初始化子类的
designated
必须调用父类的designated
方法以保证父类也初始化完成