Swift 分享大纲
- Swift 简介
- Swift 优缺点
- Swift 常用关键词的解释和用法
- Swift 使用示例
- Swift 与 OC 混合开发注意点
- SwiftUI简单了解
Swift 简介
Swift 社区有详细的官网介绍和学习入门。
Swift 发布相关历史。
Swift 官网快速体验。
Swift 官网基础知识。
Swift 是苹果于2014年 WWDC(苹果开发者大会)发布的新开发语言,是一个很强大的语言,可以用于 iOS 、iPadOS 、macOS 、tvOS 和 watchOS 等开发。目前最新版本已经到 Swift 5.5,已经趋于稳定,不会出现大量的修改的问题,而且没有了 mac 系统的开发限制,还可以使用 windows,和 Linux 系统进行开发。
Swift 是最新的编程语言研究的结果,结合了几十年构建苹果平台的经验。命名参数用干净的语法表示,这使得 Swift 中的 api 更容易阅读和维护。更好的是,您甚至不需要输入分号。推断类型使代码更整洁,更不容易出错,而模块消除头文件并提供名称空间。为了更好地支持国际语言和表情符号,Strings 是 unicode 正确的,并使用基于 UTF-8 的编码来优化各种用例的性能。使用紧凑的确定性引用计数自动管理内存,在不产生垃圾收集开销的情况下将内存使用保持在最低水平。
Swift 还有许多其他功能,可以让您的代码更富表现力:
- 泛型强大且易用
- 协议扩展使得泛型代码编写变得更为容易
- 头等函数和轻量级闭包语法
- 对范围或集合进行快速、简洁的迭代
- 元组和多值返回
- 支持方法、扩展和协议的结构
- 枚举能执行有效负载并支持模式匹配
- 函数式编程模式,例如映射和过滤
- 使用
try
/catch
/throw
处理原生错误
Swift 优缺点
Swift 优点
- swift 自动做类型推断,可以不声明类型,也保证了类型使用安全
- swift 注重面向协议编程、函数式编程、面向对象编程,使用点语法调用
- swift 没有运行时特性,它是一门静态语言,但是 Swift 是使用 OC 的
runtime
接口间接拥有了运行时的特性 - swift 容易阅读,文件结构和大部分语法简易化,只有 .swift 文件,结尾不需要分号
- swift 中的可选类型,适用于所有数据类型,而不仅仅局限于类。相比于 OC 中的
nil
更加安全和简明 - swift 中的[泛型类型]更加方便和通用,而非 OC 中只能为集合类型添加泛型
- swift 中各种方便快捷的[高阶函数](函数式编程)( Swift 的标准数组支持三个高阶函数:
map
,filter
和reduce
,以及map
的扩展flatMap
) - swift 新增了两种权限,细化权限。
open
>public
>internal
(默认)>fileprivate
>private
- swift 中独有的元组类型(
tuples
),把多个值组合成复合值。元组内的值可以是任何类型,并不要求是* 相同类型的。 - swift 支持函数式编程,OC 本身是不支持的,需要通过引入 ReactiveCocoa 这个库才可支持函数式编程。
函数式编程
- 代码简洁,开发快速
函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。 - 接近自然语言,易于理解
函数式编程的自由度很高,可以写出很接近自然语言的代码。
表达式(1 + 2) * 3 - 4,写成函数式语言:
subtract(multiply(add(1,2), 3), 4)
对它进行变形,不难得到另一种写法:
add(1,2).multiply(3).subtract(4)
因此,函数式编程的代码更容易理解。 - 更方便的代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。 - 易于"并发编程"
函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。
- 代码简洁,开发快速
Swift 缺点
Swift 与 OC 混编开发项目时,OC 不能继承 Swift 类。
-
Swift 与 OC 混编项目打包 framework 需要注意
(纯 Swift 项目打包 framework 还未进行过,暂时不知)
,如果需要真机和模拟器的合并包,那就需要修改 XXXXX-swift.h 文件。修改模拟器中或者真机其中的任意一个文件即可,修改之后的文件放入合并包中。// 模拟器 #if 0 #elif defined(__x86_64__) && __x86_64__
// 真机 #if 0 #elif defined(__arm64__) && __arm64__
修改这两行代码为
#if defined(__x86_64__) && __x86_64__ || (__arm64__) && __arm64__
App体积变大:使用 Swift 后, App 体积大概增加 5-8 M 左右,对体积大小敏感的慎用。(体积变大的原因是因为 Swift 还在变化,所以 Apple 没有在 iOS 系统里放入 Swift 的运行库,反而是每个 App 里都要包含其对应的 Swift 运行库。)
上线方式改变:不能使用application Loader上传包文件,会提示你丢失了swift support files,只能使用xcode直接上传。
[这条有待测试,网上的资料]
Swift 常用的关键词解释和用法
详细的 Swift 关键词解释和用法比较多,就不详细讲解了,就列举几个开发中一定要用的或者最常使用的。
基础的学习可以使用 Swift 的 Playground ,可以不用运行 cmd + r 就可以看到结果,体验不错。
-
权限类型
首先了解一个模块是什么?可以这样理解:一个APP就是一个模块,一个第三方API,第三放框架等都是一个完整的模块。
public
权限
public:共有访问权限,类或者类的公有属性或者公有方法可以从文件或者模块的任何地方进行访问。如果要对该模块外留有访问的属性或者方法,就应该使用public
的访问权限,public
的权限无法在其他模块被复写方法、属性或被继承。
open
权限
open:公开权限;最高的权限,可以被其他模块访问,继承及复写。只能用于类和类的成员。
internal
权限(默认)
internal:internal
是内部的意思,即有着internal
访问权限的属性和方法说明在模块内部可以访问,超出模块内部就不可被访问了。在 Swift 中默认就是internal
的访问权限
private
权限
private:私有访问权限,被private
修饰的类或者类的属性或方法可以在同一个物理文件中的同一个类型(包含extension
)访问。如果超出该物理文件或不属于同一类型,那么有着private
访问权限的属性和方法就不能被访问。
fileprivate
权限
fileprivate:文件私有访问权限,被fileprivate
修饰的类或者类的属性或方法可以在同一个物理文件中访问。如果超出该物理文件,那么有着fileprivate
访问权限的类,属性和方法就不能被访问。权限从高到低排序如下:
open
>public
>internal
>fileprivate
>private
权限使用潜规则
1、如果一个类的访问级别是fileprivate
或private
那么该类的所有成员变量都是fileprivate
或private
(此时成员无法修改访问级别),如果一个类的访问级别是open
、internal
或者public
那么它的所有成员都是internal
,类成员的访问级别不能高于类的访问级别(注意:嵌套类型的访问级别也符合此条规则)
2、常量、变量、属性、下标脚本访问级别低于其所声明的类型级别,并且如果不是默认访问级别(internal
)要明确声明访问级别(例如一个常量时一个private
类型的类类型,那么此常量必须声明为private
或fileprivate
)
3、在不违反1、2两条潜规则的情况下,setter
的访问级别可以低于getter
的访问级别(例如一个属性访问级别是internal
,那么可以添加private(set)
修饰符将setter
权限设置为private
,在当前模块中只有此源文件可以访问,对外部是只读的)。
4、必要构造方法(required
修饰)的访问级别必须和类访问级别相同,结构体的默认逐一构造函数的访问级别不高于其他成员的访问级别(例如一个成员是private
那么这个构造函数就是private
,但是可以通过自定义来声明一个public
的构造函数),其他方法(包括其他构造方法和普通方法)的访问基本遵循潜规则1什么时候需要使用
required
?
Swift 不会自动继承父类的构造器方法,但是在这两个条件下会继承
如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器。
所以父类里有init
方法,子类里并不一定会有,那么当你要求子类必须有父类的init方法时,需在父类init
方法前加上required
-
类和结构体
struct
: 结构体- 值类型(类似于深
copy
) - 没有继承,也没有重写、多态等特性
- 编译后,方法地址就是固定明确的,所以方法的运行效率高
- 栈内存(
String
、Array
、Dictionary
等发生copy
时,会存到堆中) - 结构体中的
String
、Array
、Dictionary
也都是结构体实现 - 自动生成初始化器(如下图1)
- 必须保证所有的成员都有值
如果某个成员是可为
nil
的,且是var
类型则初始化时不必赋值
类和结构体的成员变量如果都是var
类型且是可为空的则不必所有成员变量都初始化class
: 结构体- 引用类型(类似于浅
copy
) - 有继承,有重写、多态等特性
- 汇编代码比结构体多且复杂,因为有重写、多态等特性,不能固定地址调用,运行效率低。
- 堆内存
- 类中的
NSString
、NSArray
、NSDictionary
也都是类实现 - 不会自动生成初始化器,需要手动编写初始化器(如图1)
值类型的赋值是深拷贝。
在 swift 标准库中为了提升性能,对于系统中的String
、Array
、Dictionary
采取了Copy On Write 技术,只有当被拷贝的数据发生变化时才进行深拷贝(联想 OC 中的copy
)。
如果初始化后需要改变成员变量的值,class
不必声明成可变类型,而 struct 必须声明称可变类型。 - 值类型(类似于深
-
常量与变量
let
声明常量,使用中不能再次改变值,再次改变会编译报错。
var
声明变量,可以多次赋值或修改。
Swift 是语言安全型的,会对没有注释类型的常量或变量进行推断出类型。// 声明常量 let letMessage = "Hello" // 推断是 String 类型 let letMessage: String = "Hello" letMessage = "Message" // 直接报错,error,因为是常量,会提示将 let 改为 var // 声明变量 var varMessage = "Hello" // 推断是 String 类型 var varMessage: String = "Hello" varMessage = "Message" // 正常
-
函数
func
func greet(person: String, alreadyGreeted: Bool) -> String { if alreadyGreeted { return greetAgain(person: person) } else { return greet(person: person) } } func stepForward(_ input: Int) -> Int { return input + 1 } func stepBackward(_ input: Int) -> Int { return input - 1 } // 以 函数 (Int) -> Int 作为返回值 func chooseStepFunction(backward: Bool) -> (Int) -> Int { return backward ? stepBackward : stepForward }
-
开发可以提供 OC 使用的属性或方法
需要在属性或方法前面加@objc
@objc public var currentIndex: Int? @objc public func showSingle(name: String? = "默认") {}
-
安全判断
guard ... else { ... return }
也可以使用if let
guard arr.count > 0 else { return } // 只有 arr.count > 0 为真才能往下走 var name:String? if let nameS = name { // nameS 不等于 nil 才会执行 }
-
扩展 [类似于 OC 中的分类]
extension
extension
:允许给已有的类、结构体、枚举、协议类型,添加新功能。class Person { var name:String = "" var age:Int = 0 var gender:String = "" } extension Person { func printInfo() { print("My name is \(name), I'm \(age) years old and I'm a \(gender).") } }
-
类型转换
as
:类型转换运算符,用于尝试将值转成其它类型。let age = 28 as Int let money = 20 as CGFloat let cost = (50 / 2) as Double switch person1 { case let person1 as Student: print("是Student类型,打印学生成绩单...") case let person1 as Teacher: print("是Teacher类型,打印老师工资单...") default: break }
-
调试台打印
print
print("123456") // 123456 let number = "666" print(number) // 666 print("num==\(number)") // num==666
Swift 开发中常用示例
1、基础属性 let
,var
let name = "Baron"
var age = 26
var current: String? // 可选型,可以为nil
var currentNormal: String? = "normal" // 可选型,默认为 "normal"
@objc var arrayString: [String]? // 数组,字符串类型 可以在 OC 中使用
private var dicString: [String:String]? // 字典,字符串类型 私有不能跨文件使用
2、函数 func
// funcname 方法名称; name:调用时显示的参数名称; str 函数内使用的参数名称;returntype 返回值
func funcname(name str:String) -> returntype
{
// code...
return parameters
}
// 无参数 无返回值 是类方法
class func funcname()
{
Statement1
}
// 无参数 无返回值 是 OC 方法 一般按钮绑定的的方法要使用 @objc
@objc func funcname()
{
Statement1
}
3、协议 protocol
,与 OC 有不同之处,可以有默认实现。
//oc协议默认可选 swift默认必须实现
@objc protocol VCTypeProtocol where Self: UIViewController {
var model: Model { get set }
func shouldShow(container: VC, completed:@escaping (Bool) -> Void) // @escaping (Bool) -> Void 是逃逸闭包, 类似 OC 中的异步 block
func test()
}
extension VCTypeProtocol {
func test() {
// 默认实现,继承 VCTypeProtocol 可以不用实现 test 函数就可以直接调用。
}
}
4、闭包 类似于 OC Block,但是逃逸闭包一般会放在函数的后面实现。
// 无参数 无返回值
() -> ()
// name:参数 ; String 类型返回值
(String) -> String
// 逃逸闭包, 类似 OC 中的异步 block,网络回调基本都是逃逸闭包
@escaping (Bool) -> Void)
5、条件判断 if 语句,if...else 语句, if...else if...else 语句, switch 语句, guard ... else { ... return }
let name:String? = "lisa"
if let nameS = name {
// nameS 不是Nil 执行
}
else {
// nameS 是Nil 执行
}
switch expression {
case expression1 :
statement(s)
fallthrough /* 不写这句等于是 break,写了就会执行下面的 case */
case expression2, expression3 :
statement(s) // 默认是 break 的情况
default : /* 如果上面的情况已经全部满足可以不用写 defult */
statement(s);
}
guard let nameS = name else {
// nameS 是Nil 执行
return
}
// nameS 不是Nil 执行
6、集合类型
数组(Array
): 使用有序列表存储同一类型的多个值, 相同的值可以出现在不同位置中.
集合(Set
): 存储相同类型并且没有确定顺序的值, 每个元素只出现一次.
字典(Dictionary
): 使用无序的键值对存储相同类型的键和相同类型的值, 每个值(value)都关联唯一的键(key).
字面量创建方式 Array
[] , Dictionary
[:],Set
有点类似 Array
[]
// 声明的类型可以不写,Swift 会自动推断, 写声明是为了我们开发者自己可以清楚地知道是什么类型
var dic:Dictionary = ["1":"1","2":"2"]
// Dictionary 可以用 int 做 key
var dic2:Dictionary = [1:"1",2:"2"]
var strSet:Set = ["1","2"]
var strArr:Array = ["1","2"]
strArr += ["3","4"]
// strArr ["1","2","3","4"]
strArr.append("Flour")
// strArr ["1","2","3","4","Flour"]
7、循环遍历,控制流
for item in strArr {
print("Item:\(item)")
}
// Item:1
// Item:2
// Item:3
// Item:4
// Item:Flour
for(index,item) in strArr.enumerated() {
print("index:\(index); Item:\(item)")
}
// index:0; Item:1
// index:1; Item:2
// index:2; Item:3
// index:3; Item:4
// index:4; Item:Flour
for(key,value) in dic {
print("key:\(key); value:\(value)")
}
// key:1; value:1
// key:1; value:2
// Dictionary 可以用 int 做 key
var dic:Dictionary = [1:"1",2:"2"]
for(key,value) in dic {
print("key:\(key); value:\(value)")
}
// key:1; value:1
// key:1; value:2
8、可选型
可选型是在后面添加 ?
来表示的,表示有可能为 nil
。在使用是,要进行安全判断,如果确定有值可以进行强制解析,!
表示强制解析,强制解析之后如果是空值,就会崩溃,!
强制解析不安全,尽量的不使用。可以用 ??
来避免,也可以 if let
和 guard let ... else { return }
??
a ??
b 表示 a 如何不存在即使用 b
var name:String?
// 省略代码 ...
// 后面使用 name,不能确定是否有值,
// 1、可以使用 `??`
let nameS = name ?? "lisa" // lisa 表示默认值, 也可以默认 "" 空字符串
// 2、if let
if let nameS = name {
// nameS 有值时或是空字符串"" ,才会执行
}
// 3、guard let ... else { return }
guard let nameS = name else {
// nameS 为 nil 时,会执行这里面
return
}
// nameS 有值时或是空字符串"",才能执行下面
9、泛型
说到泛型,会想到 Swift 中的 Any
、AnyObject
和 OC 中的 id
。那么都有什么区别呢
OC 中的 id
- 在
id
的定义中,已经包好了*号。id
指针只能指向OC中的对象- 为了尽可能的减少编程中出错,Xcode 做了一个检查,当使用
id
类型的调用本项目中所有类中都没有的方法,编译器会报错id
类型不能使用 . 语法,因为 . 语法是编译时特性,而id
是运行时特性
Swift 中的 Any
、AnyObject
概括来说
AnyObject
用于任何类(class)的实例,而Any
可以用于表示任何变量,包括各种基本类型、值类型以及实例。而在 swift 中,枚举类型和结构体(例如Array
和Dictionary
)都属于值类型,因而不能用AnyObject
来进行修饰。
Swift 中的泛型
通常来讲,泛型为类或者方法提供一个类型参数,以方便某个参数类型保持前后的一致性。
Swift 中的泛型和 Any
//泛型修饰
func singleGenericFunc(x: T ,y: Int)-> T {
......
}
//Any修饰
func singleAnyFunc(x: Any,y:Int) -> Any {
......
}
此处最大的不同是,singleGenericFunc 中的参数 x 的类型与方法的返回类型是一致的。而singleAnyFunc 中却没有这个特性。x 与方法的返回类型都可以是任意值,不一定相同。
这是由于泛型的类型检查由编译器负责,而 Any
修饰则避开了类型系统。
综合比较而言,应该尽量多使用泛型,少使用 Any
,以尽量转换类型时发生的类型错误。
10、常见错误处理
常用的有 do-catch
、try?
、或 try!
Swift 中的错误处理类似于其他语言中的异常处理,使用
try
,catch
和throw
关键字。与许多语言中的异常处理(包括 Objective-C)不同,Swift 中的错误处理不涉及展开调用堆栈,这是一个计算成本很高的过程。因此,throw
语句的性能特征与语句的性能特征相当return
。
要指示函数、方法或初始值设定项可以抛出错误,请 throws
在函数声明中的参数后面写上关键字。标记 throws
为 的函数称为抛出函数。如果函数指定了返回类型,则 throws
在返回箭头 ( ->)之前写上关键字。
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
您可以使用 do-catch
语句通过运行代码块来处理错误。如果 do
子句中的代码抛出错误,则将其与 catch
子句进行匹配以确定其中哪一个可以处理错误。
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch pattern 3, pattern 4 where condition {
statements
} catch {
statements
}
提倡前往Swift 官网学习
Swift 与 OC 混合开发
1、创建并添加桥接文件 (桥接文件是 Swift 使用 OC 是用的)
如果是 OC 项目与 Swift 混编,创建第一个 Swift 文件,系统会自动生成桥接文件并配置。
如果是 Swift 项目与 OC 混编,创建第一个 OC 文件,系统会自动生成桥接文件并配置。
如果删除了,就需要手动创建 projectName-Bridging-Header.h
的桥接文件,并需要自己手动配置。在 Targets->Bulid Setting ,然后搜索 Swift,选择 Swift Compiler-General 下 Objective-C Bridging Header ,然后配置文件路径。
2、使用方法
Swift 文件使用 OC, 只需要把 OC 的 .h 文件导入在 projectName-Bridging-Header.h
文件中即可
OC 文件使用 Swift ,需要在 OC 文件中导入头文件 projectName-Swift.h
。
projectName-Swift.h
(这个是 xcode 自动生成的,并配置好的路径,是找不到文件,但是真是生成的,是把 Swift 编译成 OC 的形式)
3、OC宏文件
Swift 中是不能使用宏定义语法,但是因为命名空间的缘故,在其中,我们将原本 OC 中不需要接受参数的宏,定义成 let
常量或枚举,将需要接受参数的宏定义成函数。
let UScreenWidth = UIScreen.main.bounds.width
let UScreenHeight = UIScreen.main.bounds.height
var topVC: UIViewController? {
var resultVC: UIViewController?
resultVC = _topVC(UIApplication.shared.keyWindow?.rootViewController)
while resultVC?.presentedViewController != nil {
resultVC = _topVC(resultVC?.presentedViewController)
}
return resultVC
}
var isIphoneX: Bool {
return UI_USER_INTERFACE_IDIOM() == .phone
&& (max(UIScreen.main.bounds.height, UIScreen.main.bounds.width) == 812
|| max(UIScreen.main.bounds.height, UIScreen.main.bounds.width) == 896)
}
private func _topVC(_ vc: UIViewController?) -> UIViewController? {
if vc is UINavigationController {
return _topVC((vc as? UINavigationController)?.topViewController)
} else if vc is UITabBarController {
return _topVC((vc as? UITabBarController)?.selectedViewController)
} else {
return vc
}
}
5、开发时提示情况
Swift 文件中使用 OC 的方法或属性时,有可能没有提示,需要敲代码出来,如果敲的正确,可以正常编译和运行。
SwiftUI
SwiftUI官网
SwiftUI简单认识,SwiftUI 是一种使用Swift语言在苹果设备上构建用户界面的创新且简单的方式。使用 SwiftUI 在苹果设备上创建用户界面可以使用一套统一的工具和 API。SwiftUI 使用声明式的 Swift 语法,代码易读并且写起来很自然。同时它可以和 Xcode 中的设计工具配合使用,让设计工具中的展示样式和代码同步起来
。使用 SwiftUI 创建的用户界面自动支持了动态类型(Dynamic Type)、暗黑模式(Dark Mode)、语言本地化(Localization)以及所有人都可以使用的可访问性(Accessibility),也就是说,创建用户界面时,SwiftUI 写的代码比使用其它方式写的代码具有更多的功能。
这种声明式语法与最近很火的快平台开发语言 Flutter 一样。SwiftUI 就不细讲了,总体来说比较简单,重点在于大量的实践,只有大量实践才能对 API 快速掌握和熟练应用。
那么为什么会提到 SwiftUI 呢,那就要先了解一下 iOS 提出的小组件功能了。
ios 常用小组件开发
1、Today Widget 小组件
2、WidgetExtension 小组件
自iOS8之后,苹果支持了扩展(Extension)的开发,开发者可以通过系统提供给我们的扩展接入点 (Extension point) 来为系统特定的服务提供某些附加的功能。
但iOS14后,苹果更新了扩展组件,引入了新的UI组件:WidgetKit 而舍弃了iOS14以下版本的 Today Extension 组件
WidgetExtension 使用的是新的 WidgetKit 不同于 Today Widget,
它只能使用 SwiftUI 进行开发,所以需要 SwiftUI 和 Swift 基础
Widget 只支持3种尺寸systemSmall(2x2)、systemMedium(4x2)、systemLarge(4x4)
默认点击 Widget 打开主应用程序
Widget 类似于 Today Widget 是一个独立运行的程序,需要在项目中进行 App Groups 的设置才能使其与主程序互通数据,这点与 Today Widget 相同
Apple官方已经弃用 Today Extension,Xcode12 已经不再提供 Today Extension 的添加,已经有 Today Widget 的应用则会显示到一个特定的区域进行展示
所以如果需要开发 WidgetExtension 小组件就需要了解 SwiftUI 了。
提供学习的 Swift 项目
仿有妖气漫画项目:https://github.com/spicyShrimp/U17
提供学习的 SwiftUI 项目
SwiftUI 官网教程:https://swiftui.jokerhub.cn/tutorials
Copy官网学习项目:https://gitee.com/sihj/swift-ui_study
参考博客
Swift 社区
Swift 官网快速体验
Swift 官网基础知识
Swift 介绍及优缺点
Swift 中的权限控制
Swift 关键词解释和用法
Swift对比Objective-C的优缺点