Swift和OC有什么区别?
Swift和Objective-C是两种不同的编程语言,用于开发应用程序和软件。它们有以下几个主要区别:
语法:Swift的语法更加现代化和简洁,受到了其他现代编程语言的影响,如Python和Ruby。它采用了更简洁的语法结构、类型推断和函数式编程的特性。相比之下,Objective-C的语法更加冗长和繁琐,使用方括号表示方法调用和消息发送。
安全性:Swift在设计上更加注重安全性和类型安全。它引入了可选类型(Optional)和强制解包(Force Unwrapping)的概念,以减少空指针异常。Swift还提供了内存安全机制,通过自动管理内存访问来避免常见的内存错误,如访问野指针和内存泄漏。相比之下,Objective-C在类型安全和内存安全方面较为薄弱。
兼容性:Swift是苹果公司在2014年推出的全新编程语言,与Objective-C并存。它可以与Objective-C代码进行混合编程,允许开发者在现有的Objective-C项目中逐步采用Swift。Swift代码可以直接调用Objective-C的代码,反之亦然。这种兼容性使得开发者可以逐步迁移到Swift,而不需要完全重写现有的Objective-C代码。
性能:Swift在性能方面进行了优化,与Objective-C相比,它通常更快。Swift引入了一些编译时和运行时的优化机制,如静态派发、内联函数和结构体的值类型语义。这些优化可以提高代码的执行效率和响应速度。
功能特性:Swift引入了许多新的功能特性,如可选类型、模式匹配、闭包、泛型和枚举的关联值等。它还提供了更丰富的标准库,包含了许多常用的数据结构和算法。相比之下,Objective-C在功能特性方面相对较少,需要依赖于C语言的特性和Objective-C运行时。
可读性和可维护性:由于Swift的语法更加简洁和现代化,代码通常更易于阅读和理解。Swift使用了更自然的命名约定和表达方式,使得代码更加清晰和易于维护。相比之下,Objective-C的语法相对冗长和繁琐,可能需要更多的代码行数来表达相同的逻辑。
强大的类型推断:Swift具有强大的类型推断能力,可以根据上下文自动推断变量和表达式的类型。这减少了开发者需要显式声明类型的频率,使代码更加简洁和易于编写。Objective-C则需要显式地声明变量和方法的类型。
错误处理机制:Swift引入了错误处理机制,使用try、catch和throw关键字来处理错误。这种机制使得开发者可以更好地处理和传播错误,提高代码的健壮性。相比之下,Objective-C使用错误码和异常处理来处理错误,但它的错误处理机制相对较弱。
函数式编程特性:Swift支持函数式编程范式,如高阶函数、闭包和不可变性。这些特性使得代码更具表达力和灵活性,并且可以编写更简洁、可复用的代码。Objective-C在函数式编程方面的支持较弱,需要使用Objective-C的传统编程风格。
交互性和实时性:由于Swift的编译速度较快,开发者可以更快地进行代码迭代和实时调试。Swift Playground提供了一个交互式的开发环境,可以即时查看代码的执行结果。Objective-C的编译速度相对较慢,需要更多的时间来进行代码编译和调试。
可拓展性和性能优化:Swift在设计上注重可拓展性和性能优化。它引入了结构体(Structures)和枚举(Enumerations)的值类型语义,使得复杂数据类型的操作更高效。Swift还支持泛型(Generics),允许开发者编写通用的代码,提高代码的复用性和性能。Objective-C相对较弱的可拓展性和性能优化限制了其在某些场景下的应用。
动态性和反射:Objective-C是一种动态语言,支持运行时(Runtime)特性,如动态类型识别、动态方法调用和动态添加方法。这使得Objective-C在某些情况下更加灵活和动态。相比之下,Swift是一种静态语言,编译时确定类型和方法调用,限制了一些动态特性。然而,Swift也提供了一些反射(Reflection)机制,允许开发者在运行时获取和操作类型的信息。
跨平台支持:Swift在苹果平台上得到广泛支持,包括iOS、macOS、watchOS和tvOS。此外,Swift还可以通过Swift for TensorFlow和Swift on Server等项目在其他领域和平台上使用。相比之下,Objective-C主要用于苹果平台上的应用程序开发,对于其他平台的支持相对较少。
总体而言,Swift是一种现代化、安全性更高、性能更好的编程语言,逐渐取代Objective-C成为苹果平台上的首选开发语言。然而,Objective-C仍然广泛用于现有的iOS和macOS应用程序,并且在与C语言和现有Objective-C代码的兼容性方面具有优势。开发者可以根据项目需求和个人偏好选择使用Swift或Objective-C进行开发。
Swift中struct和class有什么区别?
在Swift中,struct(结构体)和class(类)都是用于创建自定义类型的关键字。它们具有以下区别:
值类型 vs 引用类型:结构体是值类型,而类是引用类型。当你创建一个结构体实例并将其赋值给一个变量或常量,实际上是将该值复制到新的变量或常量中。而当你创建一个类的实例并将其赋值给一个变量或常量,实际上是将一个引用指向该实例。这意味着结构体的赋值是对值的拷贝,而类的赋值是对引用的拷贝。
内存管理:由于结构体是值类型,它们在栈上分配内存,并在超出其作用域时被自动释放。而类是引用类型,它们在堆上分配内存,并使用引用计数(reference counting)来管理内存。当没有任何引用指向一个类实例时,内存会自动被释放。
继承:类支持继承,可以通过派生子类来继承父类的特性。而结构体不支持继承,不能派生出子结构体。
类型转换:类之间可以进行类型转换,包括向上转型(upcasting)和向下转型(downcasting)。而结构体之间没有继承关系,所以没有类型转换的需求。
可变性:结构体实例是值类型,即它们的属性在实例化后不能被修改,除非该实例被声明为可变(使用var关键字)。而类实例是引用类型,它们的属性可以在任何时候被修改,即使该实例被声明为常量(使用let关键字)。
使用场景:结构体适合表示相对简单的数据结构,例如坐标点、时间日期等。它们通常用于传递和复制数据。类适合表示更复杂的对象,具有状态和行为,并且可能在多个地方共享和修改。
总结起来,结构体和类在Swift中具有不同的特性和用途。选择使用哪种类型取决于你的需求和设计考虑。如果你只需要简单的数据封装和传递,或者希望避免引用类型的特殊行为(如共享和修改),则可以选择结构体。如果你需要更复杂的对象、继承、类型转换和可变性等特性,则应选择类。
Swift中的方法调用有哪些形式?
在Swift中,方法调用有以下几种形式:直接派发(Direct Dispatch)、函数表派发(Table Dispatch)、消息机制派发(Message Dispatch)以及动态派发(Dynamic Dispatch)。
这么多不同的调用方式存在的原因是为了在不同的编程场景中提供灵活性和性能优化。
直接派发(Direct Dispatch):直接派发是在编译时确定方法的调用目标,然后直接调用目标方法的机制。这种方式的优势是效率高,因为它不需要运行时的查找或动态决策。直接派发适用于非虚方法(non-virtual method)和值类型,因为它们不涉及继承和多态的特性。
函数表派发(Table Dispatch):函数表派发是通过在对象中维护一个函数表(或虚函数表)来确定方法的调用目标的机制。函数表是一个包含了对象的虚方法的指针数组。当调用方法时,编译器会根据对象的类型查找函数表中相应的方法,并进行调用。函数表派发适用于类的实例方法,因为类支持继承和多态。它可以实现动态的方法查找,允许子类重写父类的方法。
消息机制派发(Message Dispatch):消息机制派发是一种动态派发机制,它通过在运行时发送消息并根据接收者的实际类型来确定方法的调用目标。消息机制派发适用于与Objective-C运行时特性交互的情况,例如在Objective-C类中调用Swift方法或使用@objc关键字标记的方法。
动态派发(Dynamic Dispatch):动态派发是一种特殊的动态派发形式,它根据对象的实际类型来确定方法的调用目标。它支持继承和多态,允许子类重写父类的方法,并根据实际类型进行调用。在Swift中,类的实例方法默认情况下是动态派发的。
这些不同的方法调用形式在性能、灵活性和语言特性上有所区别。直接派发是最高效的,适用于非虚方法和值类型。函数表派发支持继承和多态,适用于类的实例方法。消息机制派发适用于Objective-C运行时特性和与Objective-C代码的交互。动态派发支持继承和多态,通过在运行时根据实际类型确定方法调用目标。
在选择方法调用形式时,应根据具体需求和场景进行考虑。通常情况下,直接派发是最佳选择,因为它具有最佳的性能。函数表派发和消息机制派发适用于需要继承、多态或与Objective-C代码的交互的情况。动态派发适用于需要在运行时根据实际类型确定方法调用目标的情况。
需要注意的是,Swift编译器会根据上下文和代码结构自动选择最佳的方法调用形式,以提供最佳的性能和行为。
swift abi 是什么
Swift ABI(Application Binary Interface)是Swift编程语言的应用程序二进制接口。ABI定义了编译器如何生成二进制代码以及如何与其他代码(无论是Swift还是其他编程语言)进行交互的规范。
Swift ABI涵盖了函数调用约定,类型布局,内存分配和管理,名称修饰和符号表等方面。这些规范使得在不同的编译器和操作系统之间进行Swift代码交互变得更加容易。
在Swift 5之前,Swift ABI是不稳定的,并且每个版本之间可能存在不兼容性。从Swift 5开始,Swift ABI被认为是稳定的,并且具有向后兼容性,这意味着编译器生成的二进制代码可以在未来的Swift版本中继续使用,而不需要重新编译。
总之,Swift ABI是Swift编程语言的应用程序二进制接口规范,定义了如何生成和与其他代码进行交互的规则,它使得不同编译器和操作系统之间的Swift代码交互变得更加容易。
swift基础知识
基础
常量、变量
Swift使用let来声明一个常量,用var来声明一个变量。
常量或者变量必须拥有一个类型。不过,并不需要总是显式地写出类型。在声明一个常量或者变量的时候直接给它们赋值就可以让编译器推断它们的类型。
如果初始值并不能提供足够的信息(或者根本没有提供初始值),就需要在变量的后边标明。
1 | var myVariable = 42 |
在Swift中,值绝对不会隐式地转换为其他类型。如果你需要将一个值转换为不同的类型,需要使用对应的类型显示地声明。
1 | let label = "The price is " |
类型安全和类型推断
整数和浮点数转换
整数和浮点数的转换必须显式指定类型:
1 | let three = 3 |
这个例子中,常量 three 的值被用来创建一个 Double 类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。
浮点数到整数的反向转换同样行,整数类型可以用 Double 或者 Float 类型来初始化:
1 | let integerPi = Int(pi) |
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 4.75 会变成 4,-3.9 会变成 -3。
注意
结合数字类常量和变量不同于结合数字类字面量。字面量 3 可以直接和字面量 0.14159 相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。
布尔值
Swift 有一个基本的 布尔(Boolean)类型,叫做 Bool
。布尔值指 逻辑 上的值,因为它们只能是真或者假。Swift 有两个布尔常量,true
和 false
:
1 | let orangesAreOrange = true |
元组
元组(tuples) 把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
1 | let http404Error = (404, "Not Found") |
如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(_
)标记:
1 | let (justTheStatusCode, _) = http404Error |
此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:
1 | print("The status code is \(http404Error.0)") |
你可以在定义元组的时候给单个元素命名:
1 | let http200Status = (statusCode: 200, description: "OK") |
给元组中的元素命名后,你可以通过名字来获取这些元素的值:
1 | print("The status code is \(http200Status.statusCode)") |
可选类型
使用 可选类型(optionals) 来处理值可能缺失的情况。可选类型表示两种可能: 或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。
注意
C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回 nil,nil 表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如 NSNotFound)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值。
来看一个例子。Swift 的 Int
类型有一种构造器,作用是将一个 String
值转换成一个 Int
值。然而,并不是所有的字符串都可以转换成一个整数。字符串 "123"
可以被转换成数字 123
,但是字符串 "hello, world"
不行。
下面的例子使用这种构造器来尝试将一个 String
转换成 Int
:
1 | let possibleNumber = "123" |
因为该构造器可能会失败,所以它返回一个 可选类型 (optional)Int
,而不是一个 Int。一个可选的 Int 被写作 Int?
而不是 Int
。问号暗示包含的值是可选类型,也就是说可能包含 Int
值也可能 不包含值
。(不能包含其他任何值比如 Bool
值或者 String
值。只能是 Int 或者什么都没有。)
nil
你可以给可选变量赋值为 nil 来表示它没有值:
1 | var serverResponseCode: Int? = 404 |
注意
nil 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
如果你声明一个可选变量但是没有赋值,它们会自动被设置为 nil:
1 | var surveyAnswer: String? |
注意
Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。
if 语句以及强制解析
你可以使用 if
语句和 nil
比较来判断一个可选值是否包含值。你可以使用“相等”(==
)或“不等”(!=
)来执行比较。
如果可选类型有值,它将不等于 nil
:
1 | if convertedNumber != nil { |
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!
)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的 强制解析(forced unwrapping)
:
1 | if convertedNumber != nil { |
可选绑定
使用 可选绑定(optional binding) 来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。
1 | if let constantName = someOptional { |
你可以像上面这样使用可选绑定来重写 在 可选类型 举出的 possibleNumber 例子:
1 | if let actualNumber = Int(possibleNumber) { |
隐式解析可选类型
有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型_总会_有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 String 和隐式解析可选类型 String 之间的区别:
1 | let possibleString: String? = "An optional string." |
注意
如果一个变量之后可能变成 nil 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 nil 的话,请使用普通可选类型。
基本运算符
空合运算符(Nil Coalescing Operator)
空合运算符(a ?? b)
将对可选类型 a 进行空判断,如果 a 包含一个值就进行解包,否则就返回一个默认值 b。表达式 a 必须是 Optional
类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。
空合运算符是对以下代码的简短表达方法:
1 | a != nil ? a! : b |
如果 a 为非空值(non-nil),那么值 b 将不会被计算。这也就是所谓的短路求值
区间运算符(Range Operators)
闭区间运算符
闭区间运算符(a...b)
定义一个包含从 a 到 b(包括 a 和 b)的所有值的区间。a 的值不能超过 b。
闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 for-in
循环中:
1 | for index in 1...5 { |
半开区间运算符
半开区间运算符(a..<b)
定义一个从 a 到 b 但不包括 b 的区间。 之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。
半开区间的实用性在于当你使用一个从 0 开始的列表(如数组)时,非常方便地从0数到列表的长度。
1 | let names = ["Anna", "Alex", "Brian", "Jack"] |
数组有 4 个元素,但 0..<count 只数到3(最后一个元素的下标),因为它是半开区间。
单侧区间
闭区间操作符有另一个表达形式,可以表达往一侧无限延伸的区间 —— 例如,一个包含了数组从索引 2 到结尾的所有值的区间。在这些情况下,你可以省略掉区间操作符一侧的值。这种区间叫做单侧区间,因为操作符只有一侧有值。例如:
1 | for name in names[2...] { |
半开区间操作符也有单侧表达形式,附带上它的最终值。就像你使用区间去包含一个值,最终值并不会落在区间内。例如:
1 | for name in names[..<2] { |
单侧区间不止可以在下标里使用,也可以在别的情境下使用。你不能遍历省略了初始值的单侧区间,因为遍历的开端并不明显。你可以遍历一个省略最终值的单侧区间;然而,由于这种区间无限延伸的特性,请保证你在循环里有一个结束循环的分支。你也可以查看一个单侧区间是否包含某个特定的值,就像下面展示的那样。
1 | let range = ...5 |
集合类型
数组与字典
Objective-C | Swift 2 | Swift 3 |
---|---|---|
id | AnyObject | Any |
NSArray * | [AnyObject] | [Any] |
NSDictionary * | [NSObject: AnyObject] | [AnyHashable: Any] |
NSSet * | Set<NSObject> | Set<AnyHashable> |
Swift中字典和数组的使用相较于OC更加简单方便,可以使用方括号[]
来创建数组或者字典,并且使用方括号来按照序号或者键访问它们的元素,可以参考上面的表格。
1 | var array = ["red", "green", "yellow", "blue"] |
控制流
if
、switch
、for-in
、for
、while
、repeat-while
,作为合格的程序员,这应该属于一看就会的类型了。但是细节相较于OC还是存在不少区别的,比如:条件不需要用圆括号括起来了(当然如果想括起来也没问题);switch
没有隐式贯穿逻辑,并且支持任意类型的数据和各种类型的比较操作。
1 | for (key, value) in dict { |
Swift里最有特色的一个控制流语法就当属guard
了,与if
语句相同的是,guard
也是基于一个表达式的布尔值去判断一段代码是否该被执行。它会让正常地写代码而不用把它们包裹进 else
代码块,并且它允许你保留在需求之后处理危险的需求。
1 | // guard |
闭包
OC中的Block的语法可以说是非常恶心的了,最突出的一点就是长,这也是写OC代码神似写作文的元凶之一:
1 | returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...}; |
而在Swift中,闭包的语法得到了很好的规范,变量、参数、返回值、实现,都一目了然,在使用中比OC的Block方便了许多:
1 | let a1 = [1,3,2].sorted(by: { (l: Int, r: Int) -> Bool in |
对象和类
Swift同样通过在class后接类名称来创建一个类。在类里边声明属性与声明常量或者变量的方法是相同的,唯一的区别的它们在类环境下。比如下面一个非常普通的类的例子:
1 | class C1 { |
协议和扩展
Swift使用protocol
来声明协议,类、结构体、枚举都可以遵循协议。
1 | protocol ExampleProtocol { |
使用extension
来给现存的类型增加功能,比如说新的方法和计算属性,对应OC中的category
。
1 | extension Int: ExampleProtocol { |
错误处理
Swift的错误处理也更简单易用、可以用任何遵循Error
协议的类型来表示错误。
1 | enum PrinterError: Error { |
使用throws
来标记一个可以抛出错误的函数并在这个函数内部使用throw
来抛出一个错误。
1 | func send(toPrinter printerName: String) throws -> String { |
处理错误一般有两种方式:
- 使用
do-catch
语法,在do
代码块里,用try
来在能抛出错误的函数前标记,在catch
代码块,错误会自动赋予名字error
。 - 处理错误的方法是使用
try?
来转换结果为可选项,如果函数抛出了错误,那么错误被忽略并且结果为nil
。1
2
3
4
5
6
7
8
9
10//方案一
do {
let printerResponse = try send(toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
//方案二
let printerSuccess = try? send(toPrinter: "Mergenthaler")
1 |