iOS8 Day-by-Day -- Day1 -- Swift引子

开场白

在今年的WWDC大会上绝对不容忽视的一点就是苹果除了宣布iOS8的发布,还介绍了一门新的编程语言Swift。是与Objective-C大不相同的一门语言,它不但是强类型的语言,而且吸收了当前多种优秀编程语言的特点。

为了能够涵盖到Swift语言所有的新特性,该博客系列将只使用Swift语言来进行讲解。我认为,学习如何使用Swift语言、如何与Cocoa框架交互最有价值的资料应该官方提供的两个文档。我相信如果你一开始就跟着这两个文档学习,你能避免不少弯路。

你还应该经常去看Swift官方博客以及Apple提供的关于该语言的其他资源

既然已经有很多资料和文档能帮助你学习如何使用Swift语言,所以这个博客系列不会将已有的知识点重复讲解。相反,我会根据初学者在使用Swift语言中可能会遇到的一些陷阱、难以理解的知识点进行讲解,尤其当和系统框架一起使用的时候。

今天这篇文章主要使用Xcode 6提供的Playground演示每个章节的代码示例。你可以在ShinobiControls的Github主页上下载这些示例代码。地址:github.com/ShinobiControls/iOS8-day-by-day

如果你对这篇文章里的知识点有什么问题或者其他建议,你可以联系我。我会在最新一期的博客中进行说明。你可以在底部进行评论或者给我发Twitter-@iwantmyrealname

构造函数

Swift围绕着对象构造过程形成了一个概念,包括命名、方便的构造器以及在对象的构造阶段指定了严格的调用顺序等。在未来几周内,该博客系列将会有一篇文章深入详细地介绍Swift中对象的构造过程,以及对你写的Objective-C有何影响。

Swift与Objective-C另外一个最大的不同之处就是构造函数的返回值和构造失败的处理。在Objective-C中的构造器大多数都是这样:


- (instancetype)init {
self = [super init];
if (self) {
// Do some stuff
}
return self;
}

然而在Swift中是这样:


init {
variableA = 10
...
super.init()
}

这里要注意的是,Objective-C中的构造器负责创建并返回self,但是Swift中的构造函数却没有返回值。这意味着在Swift的构造函数中不可能也没有机会返回一个nil的返回值,而这在Objective-C中恰恰是表明构造失败的通用模式。

这显然是Swift语言新的Beta版中需要修改的问题。那么现在我们唯一的解决办法是通过类方法返回一个Optional类型用于表明是否构造成功:


class MyClass {
class func myFactoryMethod() -> MyClass? {
...
}
}

有趣的是Objective-C中的工厂方法被转换成了Swift中的构造方法,这一点显然是不可取的。但是,到目前为止,我们只能选择Swift提供的有潜在隐患的构造方法。

可变性

在Cocoa开发者中,可变性这个概念已然不是什么新鲜的东西了,比如在合适的情景下,我们会使用NSArray的可变类型NSMutableArray。Swift将这个概念进行了升华,并且它将不可变性升级为一个基本概念。

Swift中用let关键字定义一个不可变的变量,也就是常量。比如说:


let a = MyClass()
a = MySecondClass() // 不允许

这意味着,我们不能改变被let关键字定义的常量的值,以及该常量引用对象的类型。这里需要注意的是,如果引用的类型是一个值类型(比如结构体)那么不论是引用类型还是引用类型自身都是不可变的。如果引用的是一个类,那么它的引用类型也是是不可变的,但是引用类型自身是可以改变的。

我们来看看下面这个结构体:


struct MyStruct {
let t = 12
var u: String
}

如果你用var关键字定义一个变量名叫struct1,那么你可以对其做以下操作:


var struct1 = MyStruct(t: 15, u: "Hello")
struct1.t = 13 // 编译错误,因为t是一个常量,也就是不可变属性
struct1.u = "GoodBye"
struct1 = MyStruct(t: 10, u: "You")

你可以对u属性重新赋值,也可以对struct1重新赋值,因为他们都是用var定义的变量。但是你不能对t属性重新赋值,因为它是用let定义的常量属性。下面我们再来看看如果用let关键字定义一个MyStruct的实例常量,会发生什么:


let struct2 = MyStruct(t: 12, u: "World")
struct2.u = "Planet" // 编译错误,struct2是一个常量,也就是不可变的
struct2 = MyStruct(t: 10, u: "Defeat") // 编译错误,struct2的类型是不可变的

在上述代码片段的情况下,你不能改变struct2的类型,同样也不能改变这个类型自身(比如该类型的属性u),这是因为结构体是一个值类型

但如果引用的是一个类,那么上述代码示例的结果就会有所不同了,我们先看看下面这个类的定义:


class MyClass {
let t = 12
var u: String

init(t: Int, u: String) {
self.t = t
self.u = u
}
}

我们使用var定义一个变量class1


var class1 = MyClass(t: 15, u: "Hello")
class1.t = 13 // 编译错误,因为t是一个常量,也就是不可变属性
class1.u = "GoodBye"
class1 = MyClass(t: 10, u: "You")

从上述代码示例可以看出,你可以改变类型以及该类型中使用var定义的属性,但是不能改变使用let定义的属性。当我们用let定义一个常量class2,看看会发生什么:


let class2 = MyClass(t: 12, u: "World")
class2.u = "Planet" // 编译通过
class2 = MyClass(t: 11, u: "Geoid") // 编译错误,class2的类型是不可变的

从上述代码示例中可以看出,你可以改变类型自身,也就是可以改变类型中使用var定义的属性。这是因为类是一个引用类型

这种行为很好理解,在文档中也有较全面的解释。但是当我们查看Swift中的集合类型时,就会产生一些疑惑。

NSArray是一个引用类型。也就是说,当你实例化一个NSArray时,你创建的变量实际是指向了该数组在内存中的位置,所以在Objective-C中,用*号定义该变量。如果你回顾一下我们刚才说的使用letvar关键字定义引用类型和值类型的知识点,那么你应该基本清楚letvar关键字的用法和区别。但实际上,在Objective-C中,要想使用一个可变的NSArray,你需要使用另外一个类NSMutableArray

Swift中的数组与Objective-C中的就有很大区别了,数组不再是引用类型,而是值类型。这意味着在使用中,它们像上述代码示例中的结构体一样使用,而不像类那样使用。因此,letvar关键字不仅仅是指定变量是否可以被重新定义,同时也会指定创建的数组是否是一个可变数组。

使用var定义的数组不仅可以对其重新赋值,也可以改变数组:


var array1 = [1,2,3,4]
array1.append(5) // [1,2,3,4,5]
array1[0] = 27 // [27,2,3,4,5]
array1 = [3,2] // [3,2]

但是用let定义的数组都不能进行上述的改变:


let array2 = [4,3,2,1]
array2.append(0) // 编译错误,array2是不可变数组
array2[2] = 36 // 编译错误,array2是不可变数组
array2 = [5,6] // 编译错误,不能对array2重新赋值

在这一点,Swift与Objective-C有巨大的差异,所以会给我们在实际开发中造成一些概念上混淆。也许在Swift之后的版本中对此会有所改变,所以要时刻关注Swift的文档。

通过上面的示例代码我们可以得出一个结论,因为在Swift中数组是一个值类型,所以当你创建一个数组时,实际上是将数组的副本赋值给了变量或常量。但在Objective-C中,创建一个数组时,是将一个指针赋值给变量,也就是引用了该数组在内存中的地址。所以在Objective-C中将它作为方法的参数传递时,始终传递的是数组在内存中的那块相同的地址。而在Swift中,传递的是数组的副本。同时根据数组中存储的对象的类型你可以对数组进行完全复制或者部分复制。在开发过程中要时刻注意这点。

强类型和AnyObject

强类型是Swift语言最大的一个特点。它可以提高我们代码的安全性,因此,以前在Objective-C中会在运行时才能发现的异常,现在在编译时就可以发现了。

这一点非常棒。但是当你在Swift中使用Objective-C框架进行开发时,你要特别注意AnyObject这个类型。它相当于Objective-C中的id。在有些时候,AnyObject给我们的感觉好像又不太符合Swift的特性。因为你可以将AnyObject作为任何的类型去使用,但是这会导致程序在运行时发生异常。实际上,AnyObject的使用方式大多时候都和Objective-C中的id一样。不同的一点是,当你用AnyObject代表一个类型,但是你使用了该类型中并不存在的方法或属性时,它会返回nil


let myString: AnyObject = "hello"
myString.cornerRadius // 返回 nil

为了更符合Swift语言的特性,在使用Cocoa API时,你也许该使用如下的模式:


func someFunc(parameter: AnyObject!) -> AnyObject! {
if let castedParameter = parameter as? NSString {
// Now I know I have a string :)
...
}
}

如果你确定你传入的参数就是String类型的,你就没必要在强制转换时用?来保护了:


let castedParameter = parameter as NSString

根据上述内容我们得知,转换数组其实也是一件很容易的事情。你从Cocoa框架接收的所有数组的类型都是[AnyObject],因为NSArray不支持泛型。然而在大多数情况下,不是所有的元素都是同一类型,但是他们都有已知类型。所以你可以使用有条件判断的转换,也可以不用条件判断进行转换,像下面代码中的语法:


func someArrayFunc(parameter: [AnyObject]!) {
let newArray = parameter as [String]
// Do something with your strings :)
}

协议一致性

协议在Swift很好理解,定义方式如下:


protocol MyProtocol {
func myProtocolMethod() -> Bool
}

有件事一定是你经常想做的,那就是判断一个对象是否遵循了指定的协议,我们可以这样写:


if let class1AsMyProtocol = class1 as? MyProtocol {
// We're in
}

但是这里存在一个错误,因为判断指定的这个协议必须是一个Objective-C协议,所以在定义协议时要加上@objc标签;


@objc protocol MyNewProtocol {
func myProtocolMethod() -> Bool
}

if let class1AsMyNewProtocol = class1 as? MyNewProtocol {
// We're in
}

虽然看起来只是加了@objc标签,但实际上不仅如此,因为在协议前加上了@objc标签,所以协议里的所有属性和方法返回值类型都被解析成Objective-C中的类型了。

枚举

枚举在Swift中变得更加实用。现在不仅可以在枚举中关联值(它们可以是不同类型的),也可以包含函数。


enum MyEnum {
case FirstType
case IntType (Int)
case StringType (String)
case TupleType (Int, String)

func prettyFormat() -> String {
switch self {
case .FirstType:
return "No params"
case .IntType(let value):
return "One param: \(value)"
case .StringType(let value):
return "One param: \(value)"
case .TupleType(let v1, let v2):
return "Some params: \(v1), \(v2)"
default:
return "Nothing to see here"
}
}
}

这非常有用:


var enum1 = MyEnum.FirstType
enum1.prettyFormat() // "No params"
enum1 = .TupleType(12, "Hello")
enum1.prettyFormat() // "Some params: 12, Hello"

只需要做一些小练习,你就能体会出它的强大之处。

总结

Swift的确很强大,它还需要我们通过实践去摸索新的开发模式,而不必局限在Objective-C的模式中。这篇文章列举了一些从Objective-C转到Swift中可能遇到的潜在的问题和疑惑,但希望不要让你对Swift却步。所有与本博客系列相关的项目和代码示例都是用Swift写的,并且都很通俗易懂。

本文中的代码示例都是在Playground中完成的,你可以在github.com/ShinobiControl/iOS8-day-by-day下载这些示例。

原文地址:iOS8 Day-by-Day :: Day 1 :: Blagger’s Guide to Swift

分享到: