前言
学如逆水行舟,不进则退。共勉!!!
越来越多同学打算开始用 Swift 来开发了,可很多人以前都没接触过 Swift。这篇和我以前文章不同的是,本篇只是面向 Swift 零基础的同学。
语法
基础
变量 let, var
变量是可变的,使用var修饰,常量是不可变的,使用let修饰。类、结构体和枚举里的变量是属性。
var v1:String = "hi" // 标注类型
var v2 = "类型推导"
let l1 = "标题" // 常量
class a {
let p1 = 3
var p2: Int {
p1 * 3
}
}
属性没有set可以省略get,如果有set需加get。变量设置前通过willSet访问到,变量设置后通过didSet访问。
打印 print(“”)
控制台打印值
print("hi")
let i = 14
print(i)
print("9月(i)是小柠檬的生日")
注释 //
// 单行注释
/*
多行注释第一行。
多行注释第二行。
*/
// MARK: 会在minimap上展示
// TODO: 待做
// FIXME: 待修复
可选 ?, !
可能会是 nil 的变量就是可选变量。当变量为 nil 通过??操作符可以提供一个默认值。
var o: Int? = nil
let i = o ?? 0
闭包
闭包也可以叫做 lambda,是匿名函数,对应 OC 的 block。
let a1 = [1,3,2].sorted(by: { (l: Int, r: Int) -> Bool in
return l < r
})
// 如果闭包是唯一的参数并在表达式最后可以使用结尾闭包语法,写法简化为
let a2 = [1,3,2].sorted { (l: Int, r: Int) -> Bool in
return l < r
}
// 已知类型可以省略
let a3 = [1,3,2].sorted { l, r in
return l < r
}
// 通过位置来使用闭包的参数,最后简化如下:
let a4 = [1,3,2].sorted { $0 < $1 }
print(a)
函数也是闭包的一种,函数的参数也可以是闭包。@escaping 表示套以闭包,逃逸闭包是可以在函数返回之后继续调用的。@autoclosure 表示自动闭包,可以用来省略花括号。
函数 func
函数可以作为另一个函数的参数,也可以作为另一个函数的返回。函数是特殊的闭包,在类、结构体和枚举中是方法。
// 为参数设置默认值
func f1(p: String = "p") -> String {
"p is (p)"
}
// 函数作为参数
func f2(fn: (String) -> String, p: String) -> String {
return fn(p)
}
print(f2(fn:f1, p: "d")) // p is d
// 函数作为返回值
func f3(p: String) -> (String) -> String {
return f1
}
print(f3(p: "yes")("no")) // p is no
函数可以返回多个值,函数是可以嵌套的,也就是函数里内可以定义函数,函数内定义的函数可以访问自己作用域外函数内的变量。inout 表示的是输入输出参数,函数可以在函数内改变输入输出参数。defer 标识的代码块会在函数返回之前执行。
访问控制
在 Xcode 里的 target 就是模块,使用 import 可导入模块。模块内包含源文件,每个源文件里可以有多个类、结构体、枚举和函数等多种类型。访问级别可以通过一些关键字描述,分为如下几种:
- open:在模块外可以调用和继承。
- public:在模块外可调用不可继承,open 只适用类和类成员。
- internal:默认级别,模块内可跨源文件调用,模块外不可调用。
- fileprivate:只能在源文件内访问。
- private:只能在所在的作用域内访问。
重写继承类的成员,可以设置成员比父类的这个成员更高的访问级别。Setter 的级别可以低于对应的 Getter 的级别,比如设置 Setter 访问级别为 private,可以在属性使用 private(set) 来修饰。
类型
数字 Int, Float
数字的类型有Int、Float和Double
// Int
let i1 = 100
let i2 = 22
print(i1 / i2) //四舍五入得4
// Float
let f1: Float = 100.0
let f2: Float = 22.0
print(f1 / f2) // 4.5454545
// Double
let d1: Double = 100.0
let d2: Double = 22.0
print(d1 / d2) // 4.545454545454546
// 字面量
print(Int(0b10101)) // 0b开头是二进制
print(Int(0x00afff)) // 0x开头是十六进制
print(2.5e4) // 2.5x10^2
print(2_000_000) // 2000000
布尔数 Bool
布尔数有 true 和 false 两种值,还有一个能够切换这两个值的 toggle 方法。
var b = false\
b.toggle() // true\
b.toggle() // false\
元组 (a, b, c)
元组里的值类型可以是不同的。元组可以看成是匿名结构体。
let t1 = (p1: 1, p2: "two", p3: [1,2,3])
print(t1.p1)
print(t1.p3)
// 类型推导
let t2 = (1, "two", [1,2,3])
// 通过下标访问
print(t2.1) // two
// 分解元组
let (dp1, dp2, _) = t2
print(dp1)
print(dp2)
字符串
let s1 = "Hi! This is a string. Cool?"
/// 转义父\n表示换行。
/// 其它转义字符有 \0 空字符)、\t 水平制表符 、\n 换行符、\r 回车符
let s2 = "Hi!\nThis is a string. Cool?"
// 多行
let s3 = """
Hi!
This is a string.
Cool?
"""
// 长度
print(s3.count)
print(s3.isEmpty)
// 拼接
print(s3 + "\nSure!")
// 字符串中插入变量
let i = 1
print("Today is good day, double (i)(i)!")
/// 遍历字符串
/// 输出:
/// o
/// n
/// e
for c in "one" {
print(c)
}
// 查找
print(s3.lowercased().contains("cool")) // true
// 替换
let s4 = "one is two"
let newS4 = s4.replacingOccurrences(of: "two", with: "one")
print(newS4)
// 删除空格和换行
let s5 = " Simple line. \n\n "
print(s5.trimmingCharacters(in: .whitespacesAndNewlines))
Unicode、Character 和 SubString 等内容参见官方字符串文档: Strings and Characters — The Swift Programming Language (Swift 5.1)
枚举
Swift的枚举有类的一些特性,比如计算属性、实例方法、扩展、遵循协议等等。
enum E1:String, CaseIterable {
case e1, e2
}
// 关联值
enum E2 {
case e1([String])
case e2(Int)
}
let e1 = E2.e1(["one","two"])
let e2 = E2.e2(3)
switch e1 {
case .e1(let array):
print(array)
case .e2(let int):
print(int)
}
print(e2)
// 原始值
print(E1.e1.rawValue)
// 遵循 CaseIterable 协议可迭代
for ie in E1.allCases {
print("show (ie)")
}
// 递归枚举
enum RE {
case v(String)
indirect case node(l:RE, r:RE)
}
let lNode = RE.v("left")
let rNode = RE.v("right")
let pNode = RE.node(l: lNode, r: rNode)
switch pNode {
case .v(let string):
print(string)
case .node(let l, let r):
print(l,r)
switch l {
case .v(let string):
print(string)
case .node(let l, let r):
print(l, r)
}
switch r {
case .v(let string):
print(string)
case .node(let l, let r):
print(l, r)
}
}
泛型
泛型可以减少重复代码,是一种抽象的表达方式。where 关键字可以对泛型做约束。
func fn<T>(p: T) -> [T] {
var r = [T]()
r.append(p)
return r
}
print(fn(p: "one"))
// 结构体
struct S1<T> {
var arr = [T]()
mutating func add(_ p: T) {
arr.append(p)
}
}
var s = S1(arr: ["zero"])
s.add("one")
s.add("two")
print(s.arr) // ["zero", "one", "two"]
关联类型
protocol pc {
associatedtype T
mutating func add(_ p: T)
}
struct S2: pc {
typealias T = String // 类型推导,可省略
var strs = [String]()
mutating func add(_ p: String) {
strs.append(p)
}
}
不透明类型
不透明类型会隐藏类型,让使用者更关注功能。不透明类型和协议很类似,不同的是不透明比协议限定的要多,协议能够对应更多类型。
protocol P {
func f() -> String
}
struct S1: P {
func f() -> String {
return "one\n"
}
}
struct S2<T: P>: P {
var p: T
func f() -> String {
return p.f() + "two\n"
}
}
struct S3<T1: P, T2: P>: P {
var p1: T1
var p2: T2
func f() -> String {
return p1.f() + p2.f() + "three\n"
}
}
func someP() -> some P {
return S3(p1: S1(), p2: S2(p: S1()))
}
let r = someP()
print(r.f())
类型转换
使用 is 关键字进行类型判断, 使用 as 关键字来转换成子类。
class S0 {}
class S1: S0 {}
class S2: S0 {}
var a = [S0]()
a.append(S1())
a.append(S2())
for e in a {
// 类型判断
if e is S1 {
print("Type is S1")
} else if e is S2 {
print("Type is S2")
}
// 使用 as 关键字转换成子类
if let s1 = e as? S1 {
print("As S1 (s1)")
} else if let s2 = e as? S2 {
print("As S2 (s2)")
}
}
类和结构体
类
类可以定义属性、方法、构造器、下标操作。类使用扩展来扩展功能,遵循协议。类还以继承,运行时检查实例类型。
class C {
var p: String
init(_ p: String) {
self.p = p
}
// 下标操作
subscript(s: String) -> String {
get {
return p + s
}
set {
p = s + newValue
}
}
}
let c = C("hi")
print(c.p)
print(c[" ming"])
c["k"] = "v"
print(c.p)
结构体
结构体是值类型,可以定义属性、方法、构造器、下标操作。结构体使用扩展来扩展功能,遵循协议。
struct S {
var p1: String = ""
var p2: Int
}
extension S {
func f() -> String {
return p1 + String(p2)
}
}
var s = S(p2: 1)
s.p1 = "1"
print(s.f()) // 11
属性
类、结构体或枚举里的变量常量就是他们的属性。
struct S {
static let sp = "类型属性" // 类型属性通过类型本身访问,非实例访问
var p1: String = ""
var p2: Int = 1
// cp 是计算属性
var cp: Int {
get {
return p2 * 2
}
set {
p2 = newValue + 2
}
}
// 只有 getter 的是只读计算属性
var rcp: Int {
p2 * 4
}
}
print(S.sp)
print(S().cp) // 2
var s = S()
s.cp = 3
print(s.p2) // 5
print(S().rcp) // 4
willSet 和 didSet 是属性观察器,可以在属性值设置前后插入自己的逻辑处理。
方法
enum E: String {
case one, two, three
func showRawValue() {
print(rawValue)
}
}
let e = E.three
e.showRawValue() // three
// 可变的实例方法,使用 mutating 标记
struct S {
var p: String
mutating func addFullStopForP() {
p += "."
}
}
var s = S(p: "hi")
s.addFullStopForP()
print(s.p)
// 类方法
class C {
class func cf() {
print("类方法")
}
}
static和class关键字修饰的方法类似 OC 的类方法。static 可以修饰存储属性,而 class 不能;class 修饰的方法可以继承,而 static 不能。在协议中需用 static 来修饰。
继承
// 类继承
class C1 {
var p1: String
var cp1: String {
get {
return p1 + " like ATM"
}
set {
p1 = p1 + newValue
}
}
init(p1: String) {
self.p1 = p1
}
func sayHi() {
print("Hi! (p1)")
}
}
class C2: C1 {
var p2: String
init(p2: String) {
self.p2 = p2
super.init(p1: p2 + "'s father")
}
}
C2(p2: "Lemon").sayHi() // Hi! Lemon's father
// 重写父类方法
class C3: C2 {
override func sayHi() {
print("Hi! (p2)")
}
}
C3(p2: "Lemon").sayHi() // Hi! Lemon
// 重写计算属性
class C4: C1 {
override var cp1: String {
get {
return p1 + " like Out of the blade"
}
set {
p1 = p1 + newValue
}
}
}
print(C1(p1: "Lemon").cp1)### // Lemon like ATM
print(C4(p1: "Lemon").cp1)### // Lemon like
Out of the blade
通过 final 关键字可以防止类被继承,final 还可以用于属性和方法。使用 super 关键字指代父类。
函数式
map
map 可以依次处理数组中元素,并返回一个处理后的新数组。
let a1 = ["a", "b", "c"]
let a2 = a1.map {
"\($0)2"
}
print(a2) // ["a2", "b2", "c2"]
使用 compactMap 可以过滤 nil 的元素。flatMap 会将多个数组合成一个数组返回。
filter
根据指定条件返回
let a1 = ["a", "b", "c", "call my name"]
let a2 = a1.filter {
$0.prefix(1) == "c"
}
print(a2) // ["c", "call my name"]
reduce
reduce 可以将迭代中返回的结果用于下个迭代中,并,还能让你设个初始值。
let a1 = ["a", "b", "c", "call my name.", "get it?"]
let a2 = a1.reduce("Hey u,", { partialResult, s in
// partialResult 是前面返回的值,s 是遍历到当前的值
partialResult + " (s)"
})
print(a2) // Hey u, a b c call my name. get it?
sorted
排序
// 类型遵循 Comparable
let a1 = ["a", "b", "c", "call my name.", "get it?"]
let a2 = a1.sorted()
let a3 = a1.sorted(by: >)
let a4 = a1.sorted(by: <)
print(a2) // Hey u, a b c call my name. get it?
print(a3) // ["get it?", "call my name.", "c", "b", "a"]
print(a4) // ["a", "b", "c", "call my name.", "get it?"]
// 类型不遵循 Comparable
struct S {
var s: String
var i: Int
}
let a5 = [S(s: "a", i: 0), S(s: "b", i: 1), S(s: "c", i: 2)]
let a6 = a5
.sorted { l, r in
l.i > r.i
}
.map {
$0.i
}
print(a6) // [2, 1, 0]
控制流
If If let If case let
// if
let s = "hi"
if s.isEmpty {
print("String is Empty")
} else {
print("String is (s)")
}
// 三元条件
s.isEmpty ? print("String is Empty again") : print("String is (s) again")
// if let-else
func f(s: String?) {
if let s1 = s {
print("s1 is (s1)")
} else {
print("s1 is nothing")
}
// nil-coalescing
let s2 = s ?? "nothing"
print("s2 is (s2)")
}
f(s: "something")
f(s: nil)
// if case let
enum E {
case c1(String)
case c2([String])
func des() {
switch self {
case .c1(let string):
print(string)
case .c2(let array):
print(array)
}
}
}
E.c1("enum c1").des()
E.c2(["one", "two", "three"]).des()
Guard guard, guard let
更好地处理异常情况
// guard
func f1(p: String) -> String {
guard p.isEmpty != true else {
return "Empty string."
}
return "String (p) is not empty."
}
print(f1(p: "")) // Empty string.
print(f1(p: "lemon")) // String lemon is not empty.
// guard let
func f2(p1: String?) -> String {
guard let p2 = p1 else {
return "Nil."
}
return "String (p2) is not nil."
}
print(f2(p1: nil)) // Nil.
print(f2(p1: "lemon")) // String lemon is not nil.
For-in
let a = ["one", "two", "three"]
for str in a {
print(str)
}
// 使用下标范围
for i in 0..<10 {
print(i)
}
// 使用 enumerated
for (i, str) in a.enumerated() {
print("第(i + 1)个是:(str)")
}
// for in where
for str in a where str.prefix(1) == "t" {
print(str)
}
// 字典 for in,遍历是无序的
et dic = [ "one": 1, "two": 2, "three": 3]
for (k, v) in dic {
print("key is (k), value is (v)")
}
// stride
for i in stride(from: 10, through: 0, by: -2) {
print(i)
}
/*
10
8
6
4
2
0
*/
While while, repeat-while
// while
var i1 = 10
while i1 > 0 {
print("positive even number (i1)")
i1 -= 2
}
// repeat while
var i2 = 10
repeat {
print("positive even number (i2)")
i2 -= 2
} while i2 > 0
使用 break 结束遍历,使用 continue 跳过当前作用域,继续下个循环
Switch
func f1(pa: String, t:(String, Int)) {
var p1 = 0
var p2 = 10
switch pa {
case "one":
p1 = 1
case "two":
p1 = 2
fallthrough // 继续到下个 case 中
default:
p2 = 0
}
print("p1 is (p1)")
print("p2 is (p2)")
// 元组
switch t {
case ("0", 0):
print("zero")
case ("1", 1):
print("one")
default:
print("no")
}
}
f1(pa: "two", t:("1", 1))
/*
p1 is 2
p2 is 0
one
*/
// 枚举
enum E {
case one, two, three, unknown(String)
}
func f2(pa: E) {
var p: String
switch pa {
case .one:
p = "1"
case .two:
p = "2"
case .three:
p = "3"
case let .unknown(u) where Int(u) ?? 0 > 0 : // 枚举关联值,使用 where 增加条件
p = u
case .unknown(_):
p = "negative number"
}
print(p)
}
f2(pa: E.one) // 1
f2(pa: E.unknown("10")) // 10
f2(pa: E.unknown("-10")) // negative number
小结
故不及跬步无以致千里,不积小流无以成江海。想成为大神都是从小白一点一点学习变成大神的,所以我们最重要的就是保持学习的习惯。优秀的人已经点赞和关注了,祝你早日成为大牛。
iOS资料|地址
相关阅读: Swift 并发初步