iOS 开发者指南:从 Objective-C 到学习 Swift

已发表: 2022-03-11

2008 年,Apple 宣布并发布了 iPhone SDK 2.0。 这一事件开启了软件开发的另一场革命,新一代的开发者诞生了。 他们现在被公认为 iOS 开发者。

其中许多开发人员以前从未使用过 Objective-C,这是 Apple 向他们提出的第一个挑战。 尽管语法和手动内存管理不熟悉,但它还是取得了巨大的成功,帮助 App Store 填充了数以万计的应用程序。 Apple 在每个版本中不断改进 Objective-C,添加块和文字,通过自动引用计数简化内存管理,以及许多其他表明现代编程语言的特性。

经过 6 年的改进并致力于 Objective-C,Apple 决定向开发人员提出另一个挑战。 再一次,iOS 开发者需要学习一门新的编程语言: Swift 。 Swift 移除了不安全的指针管理并引入了强大的新功能,同时保持与 Objective-C 和 C 的交互。

Swift 1.0 已经是一个稳定而强大的开发平台,它肯定会在未来几年以有趣的方式发展。 现在是开始探索这种新语言的绝佳时机,因为它显然是 iOS 开发的未来。

本教程的目的是让 Objective-C 开发人员快速了解 Swift 语言的新特性,帮助您迈出下一步并开始在日常工作中采用 Swift。 我不会花太多时间解释 Objective-C,我会假设你熟悉 iOS 开发。

Swift 改变了 Objective-C iOS 开发者的游戏规则。

尝试 Swift 与 Objective-C

为了开始探索 Swift,您只需从 App Store 下载 XCode 并创建一个 Playground 进行实验。 本文中提到的所有示例都是以这种方式完成的。

Apple 的 Swift 主页是学习 Swift 编程的最佳参考。 你会发现它非常宝贵,在你完全跟上 Swift 开发的速度之前,我相信你会经常回到这里。

变量和常量

在 Swift 中声明一个变量是使用var关键字完成的。

 var x = 1 var s = "Hello"

您会注意到两个变量xs的类型不同。 x是一个整数,而s是一个字符串。 Swift 是一种类型安全的语言,它会从赋值中推导出变量类型。 如果您希望使您的代码更具可读性,您可以选择注释变量的类型:

 var y: Int y = 2

常量是相似的,但是你使用let而不是var来声明它们。 常量的值不需要在编译时就知道,但你必须给它赋值一次。

 let c1 = 1 // Constant known at compile time var v = arc4random() let c2 = v // Constant known only at run time

顾名思义,它们是不可变的,所以下面的代码会导致编译时错误。

 let c = 1 c = 3 // error

其他类型也可以声明为常量。 例如,下面的代码将一个数组声明为一个常量,如果你试图修改其中的任何一个元素,Swift 编译器就会报错:

 var arr2 = [4, 5, 6] arr2[0] = 8 print (arr2) // [8, 5, 6] let arr = [1, 2, 3] a[0] = 5 // error

可选的

常量在声明时需要初始化,变量在使用前需要初始化。 那么Objective-C nil等价物在哪里呢? Swift 引入了可选值。 可选值可以有一个值或为nil 。 如果您查看以下代码,您会注意到x被分配了一个Optional2014 。 这意味着 Swift 编译器知道x也可能是nil

 var s = "2014" var x = s.toInt() print(x) // Optional(2014)

如果您对此代码进行更改并将值"abc"分配给无法转换为整数s ,您会注意到x现在是nil

 var s = "abc" var x = s.toInt() print(x) // nil

toInt()函数的返回类型是Int? ,这是一个可选的 Int 。 让我们尝试在x上调用标准函数:

 var x = "2014".toInt() print(x.successor()) // error

编译器发出错误信号,因为x可选的并且可能是 nil 。 我们必须先测试x ,并确保在实数上调用successor函数,而不是nil值:

 var x = "2014".toInt() if x != nil { print(x!.successor()) // 2015 }

请注意,我们必须通过附加感叹号 (!)来解开x 。 当我们确定x包含一个值时,我们可以访问它。 否则我们会得到一个运行时错误。 我们也可以做 Swift 所称的可选绑定,将可选变量转换为非可选变量

let x = "123".toInt() if let y = x { print(y) }

if语句中的代码仅在x有值时才会执行,并将其分配给y 。 请注意,我们不需要解包y ,它的类型不是可选的,因为我们知道x不是nil

查看 Apple 的 Swift 教程,了解更多关于可选项和不错的特性(如可选链)的详细信息

字符串插值

在 Objective-C 中格式化字符串通常使用stringWithFormat:方法:

 NSString *user = @"Gabriel"; int days = 3; NSString *s = [NSString stringWithFormat:@"posted by %@ (%d days ago)", user, days];

Swift 有一个称为字符串插值的功能可以做同样的事情,但它更紧​​凑且更易于阅读:

 let user = "Gabriel" let days = 3 let s = "posted by \(user) \(days) ago"

您还可以使用表达式:

 let width = 2 let height = 3 let s = "Area for square with sides \(width) and \(height) is \(width*height)"

要了解有关 Swift 的字符串插值和其他新功能的更多信息,请访问此处。

职能

Swift 中的函数定义与 C 不同。示例函数定义如下:

 func someFunction(s:String, i: Int) -> Bool { ... // code }

Swift 函数是一流的类型。 这意味着您可以将函数分配给变量,将它们作为参数传递给其他函数,或者使它们返回类型:

 func stringLength(s:String) -> Int { return countElements(s) } func stringValue(s:String) -> Int { if let x = s.toInt() { return x } return 0 } func doSomething(f:String -> Int, s:String) -> Int { return f(s).successor() } let f1 = stringLength let f2 = stringValue doSomething(f1, "123") // 4 doSomething(f2, "123") // 124

同样,Swift 推断出f1f2的类型( String -> Int ),尽管我们可以明确定义它们:

 let f1:String -> Int = stringLength

函数还可以返回其他函数:

 func compareGreaterThan(a: Int, b: Int) -> Bool { return a > b } func compareLessThan(a: Int, b: Int) -> Bool { return a < b } func comparator(greaterThan:Bool) -> (Int, Int) -> Bool { if greaterThan { return compareGreaterThan } else { return compareLessThan } } let f = comparator(true) println(f(5, 9))

可以在此处找到 Swift 中的函数指南。

枚举

Swift 中的枚举比 Objective-C 中的强大得多。 作为 Swift 结构,它们可以有方法,并按值传递:

 enum MobileDevice : String { case iPhone = "iPhone", Andro, WP8 = "Windows Phone8", BB = "BlackBerry" func name() -> String { return self.toRaw() } } let m = MobileDevice.Android print(m.name()) // "Android"

与 Objective-C 不同,Swift 枚举除了整数之外,还可以将字符串、字符或浮点数作为每个成员的值。 方便的toRaw()方法返回分配给每个成员的值。

枚举也可以参数化:

 enum Location { case Address(street:String, city:String) case LatLon(lat:Float, lon:Float) func description() -> String { switch self { case let .Address(street, city): return street + ", " + city case let .LatLon(lat, lon): return "(\(lat), \(lon))" } } } let loc1 = Location.Address(street: "2070 Fell St", city: "San Francisco") let loc2 = Location.LatLon(lat: 23.117, lon: 45.899) print(loc1.description()) // "2070 Fell St, San Francisco" print(loc2.description()) // "(23.117, 45.988)"

有关枚举的更多信息,请参见此处。

元组

元组将多个值组合成一个复合值。 元组中的值可以是任何类型,并且不必彼此具有相同的类型。

 let person = ("Gabriel", "Kirkpatrick") print(person.0) // Gabriel

您还可以命名单个元组元素:

 let person = (first: "Gabriel", last: "Kirkpatrick") print(person.first)

对于需要返回多个值的函数,元组作为返回类型非常方便:

 func intDivision(a: Int, b: Int) -> (quotient: Int, remainder: Int) { return (a/b, a%b) } print(intDivision(11, 3)) // (3, 2) let result = intDivision(15, 4) print(result.remainder) // 3

与 Objective-C 不同,Swift 支持在 switch 语句中进行模式匹配:

 let complex = (2.0, 1.1) // real and imaginary parts switch complex { case (0, 0): println("Number is zero") case (_, 0): println("Number is real") default: println("Number is imaginary") }

在第二种情况下,我们不关心数字的实部,所以我们使用_来匹配任何东西。 您还可以检查每种情况下的附加条件。 为此,我们需要将模式值绑定到常量:

 let complex = (2.0, 1.1) switch complex { case (0, 0): println("Number is zero") case (let a, 0) where a > 0: println("Number is real and positive") case (let a, 0) where a < 0: println("Number is real and negative") case (0, let b) where b != 0: println("Number has only imaginary part") case let (a, b): println("Number is imaginary with distance \(a*a + b*b)") }

请注意我们如何只需要绑定我们将在比较或 case 语句中使用的值。

要阅读有关元组的更多信息,请转到此处。

类和结构

与 Objective-C 不同,Swift 不需要您为自定义类和结构创建单独的接口和实现文件。 在学习 Swift 时,您将学习在单个文件中定义类或结构,并且该类或结构的外部接口会自动提供给其他代码使用。

定义类

类定义非常简单:

 class Bottle { var volume: Int = 1000 func description() -> String { return "This bottle has \(volume) ml" } } let b = Bottle() print(b.description())

如您所见,声明和实现在同一个文件中。 Swift 不再使用头文件和实现文件。 让我们为示例添加一个标签:

 class Bottle { var volume: Int = 1000 var label:String func description() -> String { return "This bottle of \(label) has \(volume) ml" } }

编译器会抱怨,因为 label 是一个非可选变量,并且在实例化 Bottle 时不会保存值。 我们需要添加一个初始化器:

 class Bottle { var volume: Int = 1000 var label:String init(label:String) { self.label = label } func description() -> String { return "This bottle of \(label) has \(volume) ml" } }

或者,我们可以对不需要初始化的属性使用Optional类型。 在以下示例中,我们将volume Optional Integer

 class Bottle { var volume: Int? var label:String init(label:String) { self.label = label } func description() -> String { if self.volume != nil { return "This bottle of \(label) has \(volume!) ml" } else { return "A bootle of \(label)" } } }

结构

Swift 语言也有structs ,但它们比 Objective-C 灵活得多。 以下代码教程定义了一个struct

 struct Seat { var row: Int var letter:String init (row: Int, letter:String) { self.row = row self.letter = letter } func description() -> String { return "\(row)-\(letter)" } }

与 Swift 中的类一样,结构可以具有方法、属性、初始化器并符合协议。 类和结构的主要区别在于类是通过引用传递的,而结构是通过值传递的

这个例子演示了通过引用传递类:

 let b = Bottle() print(b.description()) // "b" bottle has 1000 ml var b2 = b b.volume = 750 print(b2.description()) // "b" and "b2" bottles have 750 ml

如果我们用struct尝试类似的情况,你会注意到变量是按值传递的:

 var s1 = Seat(row: 14, letter:"A") var s2 = s1 s1.letter = "B" print(s1.description()) // 14-B print(s2.description()) // 14-A

我们什么时候应该使用struct什么时候应该使用class ? 与在 Objective-C 和 C 中一样,当您需要对几个值进行分组并期望它们被复制而不是引用时,请使用结构。 例如,复数、2D 或 3D 点或 RGB 颜色。

类的实例传统上称为对象。 然而,Swift 的类和结构在功能上比其他语言更接近,而且很多功能可以应用于类或结构类型的实例。 正因为如此,Swift 引用中使用的更通用的术语是instance ,它适用于这两个中的任何一个。

在这里学习 Swift 类和结构的基础知识。

特性

正如我们之前看到的,Swift 中的属性是在类或结构定义中使用var关键字声明的。 我们也可以使用let语句来声明常量。

 struct FixedPointNumber { var digits: Int let decimals: Int } var n = FixedPointNumber(digits: 12345, decimals: 2) n.digits = 4567 // ok n.decimals = 3 // error, decimals is a constant

还要记住,类属性是强引用的,除非您在它们前面加上weak关键字。 然而,弱非可选属性有一些微妙之处,因此请阅读 Apple 的 Swift 指南中的自动引用计数章节。

计算属性

计算属性实际上并不存储值。 相反,它们提供了一个 getter 和一个可选的 setter 来间接检索和设置其他属性和值。

以下代码提供了计算值sign的示例:

 enum Sign { case Positive case Negative } struct SomeNumber { var number:Int var sign:Sign { get { if number < 0 { return Sign.Negative } else { return Sign.Positive } } set (newSign) { if (newSign == Sign.Negative) { self.number = -abs(self.number) } else { self.number = abs(self.number) } } } }

我们也可以通过实现一个 getter 来定义只读属性:

 struct SomeNumber { var number:Int var isEven:Bool { get { return number % 2 == 0 } } }

在 Objective-C 中,属性通常由实例变量支持,由编译器显式声明或自动创建。 另一方面,在 Swift 中,属性没有对应的实例变量。 也就是说,不能直接访问属性的后备存储。 假设我们在 Objective-C 中有这个

// .h @interface OnlyInitialString : NSObject @property(strong) NSString *string; @end // .m @implementation OnlyInitialString - (void)setString:(NSString *newString) { if (newString.length > 0) { _string = [newString substringToIndex:1]; } else { _string = @""; } } @end

因为,在 Swift 中,计算属性没有后备存储,我们需要做这样的事情:

 class OnlyInitialString { var initial:String = "" var string:String { set (newString) { if countElements(newString) > 0 { self.initial = newString.substringToIndex(advance(newString.startIndex, 1)) } else { self.initial = "" } } get { return self.initial } } }

属性在这里有更详细的解释

待续

在 Swift 中有许多更重要的新东西需要学习,比如泛型、与 Objective-C 库的交互、闭包、可选链和运算符重载。 单一的教程无法完整地描述一门新语言,但我毫不怀疑关于学习 Swift 编程的更多内容。 但是,我相信这篇快速阅读将帮助许多没有时间和学习 Swift 语言细节的 Objective-C 开发人员走上正轨,让 Swift 鸟把他们带到一个新的高度。