logo头像
Snippet 博客主题

swift使用

String类型

声明

1
2
3
4

let str = "小仙女"
let str1:String = "hahh"

拼接

字符串的连接有两种方法,一种是通过加号来连接,另一种则是通过反斜杆进行插入
在做字符串拼接时要注意加号和反斜杠后面都不能出现空格,不然会报错。

1
2
3
4
5
6

let str = "小仙女"
let mesg1 = "one"+str //用加号的方式
let mesg2 = "two,\(str)" //反斜杠的方式
print(mesg1,mesg2)

拼接字符串时格式的变化

假设在某些特定的地方需要输出特定位数的字符,比如或时间的输出,就需要使用占位符来调整字符串的格式。使用String的构造函数,调用format方法,%0后面加上数字就表示需要占多少位数

1
2
3
4
let min = 2
let second = 10

String(format: "d:d", min,second)

遍历

调用字符串的characters属性,采用for…in…的方式来遍历字符串。

1
2
3
4
5
6
7
8
9
10
11

for c in str{
print(c) //swift4中的遍历
}
print(str.count) //打印字符串长度

for char in myString.characters {
print(char) // swift3的遍历
}
print(str..characters.count) //swift3打印字符串长度

字符串的截取

最方便的方式就是将String类型转换成OC的NSString类型,再来截取。

1
2
3
4
5
6
let urlStr = "www.baidu.com"
let header = (urlStr as NSString).substring(to: 3) //截取前三位

let middle = (urlStr as NSString).substring(with: NSMakeRange(4, 5))//去除前四个字符截取,范围之后五位字符

let footer = (urlStr as NSString).substring(from: 10) //从第十个字符开始截取

Bool类型

与其他语言一样,Bool类型表示的就是真假,但是不同于Objective-C,swift中用true和false来表示真假。

可选类型

在Objective-C开发中,如果一个变量暂时不会使用到,可以将它赋值为0或者赋值为空,而在swift中,nil是一个特殊的类型,如果它和真实类型不匹配是不能进行赋值的。但是开发中将变量赋值为空是在所难免的事情,因此就推出了可选类型。

可选类型是swift的一大特色,在定义变量时,如果指定这个变量是可选的话,就是说这个变量可以有一个指定类型的值或者为nil。

1
2
3
4
let x:Optional = 10  //第一种写法

let x:Int? = 20 //第二种写法
print(x)

解包

swift中有规定,对象中的任何属性在创建对象时,都必须有明确的初始化值。
使用let定义的是常量,在初始化时必须要给出值。
强制解包是危险操作,如果可选值为nil,强制解包系统会奔溃。

1
2
3
4
5
//默认值测试
let x: Int?
print(x)
var y :Int?
print(y)

可选绑定

用if let/var表示。它将变量赋值给一个临时变量,在这个操作中会做两步操作:首先判断变量是否有值,如果没有值,则直接不执行大括号里面的内容;如果有值,系统会自动将变量进行解包,并且将解包后的结果,赋值给临时变量。

1
2
3
4
5
let url: URL? = URL(string: "https://www.baidu.com")
接着创建NSURLRequest对象。强制解包非常危险,当url有中文的时候可能会变成nil。所以要判断url是否为空再对其进行解包。
if let url = url {
let request = URLRequest(url: url)
}

swift中的分支

在swift中,if语句是不用带小括号的,但是后面跟的语句必须有花括号,哪怕只有一行代码。许多公司的代码规范也是规定必须使用这一格式。

注意:在swift中没有非0即真的说法,所以不能写成if(num)这样的格式。

if语句

1
2
3
4
5
6
let x = 9
if x > 5 {
print("小仙女")
}else{
print("妖精哪里跑")
}

三目运算符

三目运算符的写法是表达式后跟一个问号,用冒号来隔开条件是否成立的值。

1
2
3
4
5
let x = 10
x > 5 ? print("小仙女"):print("妖精")
非常有意思的是,如果开发者只想处理条件成立的部分,此时可以在冒号后面用一个小括号来代替条件不成立的部分。
x > 5 ? print("你都写了我两次啦"):()

三目运算符的简单模式

三目运算符的简单模式通常是用于处理可选项的。“??”的意思是说,如果表达式有值,就使用那个值,如果没有,就使用“??”后面的值来代替。

1
2
3
4
5
6
7
let x:Int? = nil
let y:Int? = 9
print((x ?? 0) + (y ?? 0))
从运行的结果可以看到,“??”的优先级是最低的。如果没有小括号的约束,它会将后面的语句都当成是一个表达式。
let name:String? = "安琪拉"
print((name ?? "") + "火烧屁屁咯")
print(name ?? "" + "火烧屁屁咯")

guard的用法

分支若是写得过多,就会导致代码可读性较差的问题。为了降低代码的层次,swift推出了guard。guard后面跟判断表达式,else后面写表达式不成立的代码。

需要注意的是guard必须写在函数内部,在最末尾出必须要跟关键字return/continue/break/throw中的一种。

1
2
3
4
5
6
7
8
9
import UIKit
let age = 20
func online(age : Int){
guard age >= 18 else {
print("还未成年呢")
return
}
print("一起来开黑吖")
}

switch

switch后面的小括号可以省略。用case关键字来表示不同的情形,case语句结束后,break也可以省略。

1
2
3
4
5
6
7
8
9
10
let sex = 0
switch sex {
case 0:
print("男")
case 1:
print("女")
default:
print("其他")
}

如果系统某一个case中产生case穿透,可以在case结束后跟上fallthrough

1
2
3
4
case  0:
print("男")
fallthrough

case后面可以判断多个条件,这些条件以逗号分开

1
2
3
4
5
6
7
8
let sex = 0
switch sex {
case 0,1:
print("正常人")

default:
print("其他")
}

switch可以判断浮点型、字符串类型和Bool类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
switch 3.14 {
case 0:
print("正常人")

default:
print("其他")
}

let opration = "+"
switch opration {
case "+":
print("加法")
case "-":
print("减法")
default:
print("其他")
}

swift的for循环和表示区间

swift常见区间有两种,开区间用..<表示,闭区间用…表示。要注意的是数字和省略号之间是不能加空格的。

1
2
3
4
5
6
7
8
9
10
11
func demo1() {
for i in 0..<5 {
print(i)
}
print("^^^^^^^")

for i in 0...5 {
print(i)
}
}
demo1()

逆序操作

如果想要做逆序操作,只要在in后面的表达式后添加reversed()即可。

1
2
3
如果想要做逆序操作,只要在in后面的表达式后添加reversed()即可。


swift中的数组

Swift语言提供了Arrays、Sets和Dictionaries三种基本的集合类型用来存储集合数据。数组是有序数据的集,集合是无序无重复数据的集,而字典则是无序的键值对的集。

数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中

定义数组

用let定义出来的数组就是不可变的

1
2
//定义不可变数组
let array = ["爱丽丝","小红帽","白雪公主"]

使用var来定义可变数组。正确的写法是Array这样的形式。其中Element是这个数组中唯一允许存在的数据类型。但是为了简便,推荐使用Element的写法。

1
2
3
4
//定义可变数组
var arrayM = [String]()
var arrayM1:[String]
var arrayM2 = Array()

创建带有默认值的数组

swift中的array类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。开发者可以在里面指定它的数量和类型。

1
2
var threeDouble = Array(repeating: 0.0, count: 3)
print(threeDouble[1])

对可变数组的基本操作

使用append给数组添加元素

1
2
3
4
5
arrayM.append("1")
arrayM.append("2")
arrayM.append("3")
arrayM.append("4")
arrayM.append("5")

使用insert方法将值添加到具体索引值之前

1
arrayM.insert("10", at: 2)

使用remove系列方法可以对数组做删除操作

1
2
3
4
arrayM.remove(at: 0)
arrayM.removeSubrange(1..<3)
arrayM.removeAll()
arrayM.removeLast() //可以去除最后一项,避免捕获数组count属性

通过取下标的方式对数组进行修改和查找

1
2
arrayM[0] = "小红帽"
print(arrayM[2])

利用区间对具体范围内的值替换

1
2
3
//替换第2项和第3项的值
arrayM[2...4] = ["22","33"]
print(arrayM[3])

数组的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13

for i in 0..<arrayM.count {
print(arrayM[i])
}

for i in arrayM {
print(i)
}
若同时需要每个数据项的值和索引,可以使用数组的emumerated()方法来进行数组遍历。
for(index,value) in arrayM.enumerated(){
print(String(index+1)+":"+value)
}

数组的合并

只有相同类型的数组才能进行合并。

1
let resultArray = arrayM + array

swift中的集合

集合(Set)用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。

集合中的元素必须有确定的hashvalue,或者是实现了hashable协议。而swift提供的Int,String等类型其实都是实现了hashable协议的。hashable是equable的子协议,如果要判断两个元素是否相等,就要看他们的hashvalue是否相等。

定义集合。

使用set定义。

Element表示集合中允许存储的类型,和数组不同的是,集合没有等价的简化形式

1
2
3
4
//创建空集合
var letters = Set()
//使用字面量创建集合
var favorite:Set = ["绮罗生","意琦行"]

要注意的是一个Set类型是不能直接后面跟的字面量被单独推断出来的,因此这个Set是必须要显示声明的。但是由于swift的自动推断功能,可以不用写出Set的具体类型。比如说上面那个例子,省去String,也能推断出Set的正确类型。

1
var favorite:Set = ["绮罗生","意琦行"]

访问和修改集合

通过.count属性知道集合的长度,通过isEmpty判断集合是否为空。

添加元素

1
2
favorite.insert("寒烟翠")
print(favorite.count)

删除元素

通过remove的方法删除元素,若这个值真的存在就会删除改值,并且返回被删除的元素。若集合中不包含这个值,就会返回nil。

1
2
3
4
5
if let removeBack = favorite.remove("意琦行"){
print(removeBack)
}else{
print("没有找到值")
}

集合操作

swift提供了许多数学方法来操作集合。

1
2
3
4
print(oddD.union(evenD).sorted()) //并集
print(oddD.intersection(evenD).sorted())//交集
print(oddD.subtracting(siggleDPrime).sorted())//取差值
print(oddD.symmetricDifference(siggleDPrime).sorted())//去掉相同值

遍历集合

1
2
3
4
5
6
7
for item in favorite {
print(item)
}
//按照首字母的顺序输出
for item1 in favorite.sorted() {
print(item1)
}

集合的成员关系

1
2
3
4
5
6
7
用 ==来判断两个集合是否包含全部相同的值

用 isSubset(of:)来判断一个集合中的值是否也被包含在另外一个集合中

用 isSuperset(of:)来判断一个集合中包含另一个集合所有的值

用isStrictSubset(of:)或者isStrictSuperset(of:)方法来判断一个集合是否是另外一个集合的子集合或父集合并且两个集合不相等

字典

字典是一种存储多个相同类型的值的容器。每个值value都关联这唯一的键key。键就是这个字典的标识符。而且字典中的数据项并没有具体顺序。键集合不能有重复元素,而值集合是可以重复的。

1

定义字典

使用let定义不可变的字典,使用var定义可变字典。用字面量赋值时,系统会自动判断[]中存放的是键值对还是要一个个的元素。

1
2
3
4
5
6
let dict = [1:"one",2:"two",3:"three"]  //定义不可变字典

var dictM = Dictionary() //定义可变字典
var dictM1 = [String:NSObject]()

//AnyObject一般用于指定类型,NSObject一般用于创建对象

对可变字典做基本操作

添加、删除和获取元素

1
2
3
4
5
dictM1["name"] = "小仙女" as NSObject
dictM["age"] = 17 as NSObject
dictM.removeValue(forKey:"name")
//获取:swift中只保留了最简单的写法,OC中有objectforkey的方法在swift中也被删除掉了。
dictM["name"]

修改元素

若字典中已经有对应的key,操作的结果是直接修改原来的key中保存的value。若字典中没有对应的key,则会添加新的键值对。

1
dictM["name"] = "llx"

遍历字典

可以通过范围for遍历所有的key和value。也可以遍历所有的键值对。

1
2
3
4
for (key,value) in dictM {
print(key)
print(value)
}

合并字典

合并字典时通过遍历的方式将第二个字典的内容添加到第一个字典中。绝对不能用相加的方式对字典进行合并。

1
2
3
4
5
6
7
var dict1 = ["name":"llx","age":"17"]
var dict2 = ["num":"007"]

for (key,value) in dict2 {
dict1[key] = value
}
dict

元组

元组是swift中特有的一种数据结构,用于定义一组数据,元组在数学中的应用十分广泛。

定义元组

使用()包含信息,组成元组类型的数据可以被称为“元素”。

1
2
//使用元组来描述个人信息
let info1 = ("1001","张三",30)

起别名

可以给元素加上名称,之后可以通过元素名称访问元素

1
2
3
//给元素加上名称,之后可以通过元素名称访问元素
let info2 = (id:"1001",name:"张三",age:30)
info2.name

元组一般用于作为方法的返回值。元组中元素的别名,就是元组的名称

1
2
let (name,age) = ("张三",18)
name

函数

函数相当于Objective-C中的方法,是一段完成特定任务的独立代码片段。可以通过给函数命名来标志某个函数的功能。而这个名字可以用来在需要的时候“调用”该函数完成其任务。格式如下:

1
2
3
4
func 函数名(参数列表)-> 返回值类型 {
代码块
return 返回值
}

func表示关键字,多个参数列表之间用逗号隔开,也可以没有参数。使用->指向返回值类型。如果没有返回值,可以用Void代替,也可以省略。

定义无参无返回的函数

1
2
3
4
func phone()->Void {
print("小米")
}
phone()

定义无参有返回的函数

1
2
3
4
func phoneNum() -> String {
return "123456"
}
print(phoneNum())

定义有参无返回的函数

1
2
3
4
func callPhone(phoneNum:String){
print("打电话给\(phoneNum)")
}
callPhone(phoneNum: "123456")

定义有参有返回的函数

1
2
3
4
func sum(num1 : Int,num2 : Int) -> Int{
return num1 + num2
}
sum(num1: 30, num2: 30)

在swift4之后,调用函数的时候,能直观的看到参数。而在之前调用之时,只能看见第二个参数之后的名称,表达起来并不直观。如何解决这个问题呢?

可以采用给参数起别名的方式,在参数前面添加一个别名。

1
2
3
4
func sum(number1 num1: Int,number2 num2 : Int) -> Int{
return num1 + num2
}
sum(number1: 2, number2: 4)

默认参数

在swift中可以给方法的参数设置默认值。比如说买甜筒的时候,商店默认会给顾客准备原味冰淇淋。但是用户也可以选择指定口味。

1
2
3
4
5
func makeIceCream(flavor:String = "原味") -> String {
return "制作一个\(flavor)冰淇淋"
}
makeIceCream()
makeIceCream(flavor: "抹茶"

可变参数

有些时候,在创建方法的时候,并不确定参数的个数,于是swift推出了可变参数。参数的类型之后使用…表示多个参数。

1
2
3
4
5
6
7
8
func sum(num:Int...) -> Int {
var result = 0
for i in num {
result += i
}
return result
}
sum(num: 18,29,3)

引用传递

swift提供了关键字inout来声明数据地址传递,也被称之为引用传值。在swift3.0的时候,inout的位置发生了改变,被放置在标签位置。但是作用与之前相同。

1
2
3
4
5
6
7
func swapNum1( m : inout Int, n : inout Int) {
let tempNum = m
m = n
n = tempNum
}
swapNum1(m: &m, n: &n)
print("m:\(m),n:\(n)")

swift用关键字class来定义类。通常情况下,定义类时,让它继承自NSObject,若没有指定父类,那么该类就是rootClass。类的格式如下:

1
2
3
class 类名:SuperClass {
//定义属性和方法
}

定义存储属性和创建类对象

对象的属性必须要赋值,用解包的方式赋值为nil。

1
2
3
4
5
6
class Person : NSObject {
//定义存储属性
var age : Int = 0
var name : String? //对象的属性必须赋值,不赋值会报错的哦
}
let p = Person()

给类的属性赋值

可以直接赋值,也可以通过KVC进行赋值

1
2
3
4
5
p.age = 10
p.name = "llx"
if let name = p.name {
print(name)
}

定义方法

在swift中,如果使用当前某一对象的属性或者方法,可以直接使用,不需要加self

1
2
3
4
5
6
// 定义方法,返回平均成绩
func getAverage() -> Double {
return (mathScore + EnglishScore)*0.5
}

let average = p.getAverage()

定义计算属性

通过别的方式计算到结果的属性,称之为计算属性。

1
2
3
var averageS : Double {
return (mathScore + EnglishScore) * 0.5
}

定义类属性

类属性是和整个类相关的属性,用static修饰,作用域是整个类。通过类名进行访问。

1
static var courseCount : Int = 0

在类外通过类名访问类属性

1
Person.courseCount = 2

类的构造函数

构造函数类似于OC中的init方法。默认情况下创建一个类时,必定会调用一个构造函数。如果一个类继承自NSObjct,可以对父类的构造函数进行重写。

在构造函数中,如果没有明确super.init()。那么系统会默认调用super.init()。

1
2
3
4
5
6
7
8
9
class Person : NSObject {
var name : String?
var age : Int = 0

override init() {
print("hello world")
}
}
let p = Person()

自定义构造函数

自定义构造函数可以传入参数,做赋值操作时采用self调用属性以示区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person : NSObject {
var name : String?
var age : Int = 0

// 自定义构造函数
init(name:String,age:Int){
self.name = name
self.age = age
}
}
// 调用自定义的构造函数
let p1 = Person(name: "kaka", age: 12)
print(p1.age)

可以定义字典类型的构造函数。用KVC的方式将字典的值取出来,要调用系统的setValue方法就必须先调用系统的构造函数创建出对象。为了防止取出的对象没有属性而导致程序奔溃,需要重写系统的setValue方法。

如果用KVC的方式一定要先调用父类的构造函数。因为系统默认调用是放在方法最后面调用的。

由于swift与objective-c的编译方式不同,用KVC字典转模型构造函数时,需要在属性前面加上@objc。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person : NSObject {
@objc var name : String?
@objc var age : Int = 0

init(dict:[String : Any]) {
super.init()
// 要调用系统的`setValue`方法就必须先调用系统的构造函数创建出对象
setValuesForKeys(dict)
}
// 防止奔溃
override func setValue(_ value: Any?, forUndefinedKey key: String) {
}
}

let p2 = Person(dict:["name":"lala","age":18,"score":33])
p2.name
p2.age

类的属性监听器

在object-c中,我们可以重写set方法来监听属性的改变,而在swift中也可以通过属性观察者来监听和响应属性值的变化。通常用于监听存储属性和类属性的改变。对于计算属性则不需要定义属性观察者,因为我们可以在计算属性的setter中直接观察并响应这种值的变化。

可以通过设置以下观察方法并响应这种值的变化。

willSet:在属性值被存储之前设置,此时新属性值作为一个常量参数被传入。该参数名默认为newValue,开发者可以自己定义该参数名。

didSet:在新属性值被存储后立即调用,与willSet不同的是,此时传入的是属性的旧值,默认参数名为oldValue。

上面两个方法都只有在属性第一次被设置时才会调用,在初始化时,不会去调用这些监听方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person : NSObject {
//属性监听器
var name:String? {
willSet {
print(name as Any)
//如果想要查看接下来的新值,可以使用newValue
print(newValue as Any)
}
didSet {
print(name as Any)
}
}
}

let p = Person()
p.name = "llx"

闭包

闭包是swift中非常重要的一个知识点。类似于objective-c中的block,其实函数就相当于一个特殊的闭包。闭包需要提前写好,在适当的时候再执行。

定义闭包

闭包的格式是(参数列表)->(返回值类型) in 实现代码

举一个最简单的栗子

用常量记录一个代码块,按住option键就能看到,b1是一个闭包。再到适合的地方去调用它。

1
2
3
4
let b1 = {
print("干掉他们")
}
b1()

再来看一个带参数的闭包。在闭包中,参数、返回值和实现代码都是写在花括号里面的。in是用来定义分割和实现的。

1
2
3
4
5
let b2 = {
(x:String)->() in print(x)
}

b2("string")

闭包案例

这个案例要模拟封装一个网络请求的类。利用闭包将jsonData类型的数据传递给展示页面。

创建一个新的项目,选择swift语言
这个案例要模拟封装一个网络请求的类。利用闭包将jsonData类型的数据传递给展示页面。

创建一个新的项目,选择swift语言
用异步线程模拟网络数据请求,再回到主线程中回调闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class HttpTool: NSObject {
//闭包类型:(参数列表)->(返回值类型)

func loadData(callback:@escaping(_ jsonData : String)->()) {
DispatchQueue.global().async {
print("发生网络请求:\(Thread.current)")
}

DispatchQueue.main.async {
()->Void in
print("获取到数据,并且回调:\(Thread.current)")

callback("jsonData数据")
}
}
}

到需要接收数据的界面定义Httptool类的属性,设置一个初始化值,将初始值赋值给变量

在swift中是不需要引入头文件的,文件之间可共享

1
在swift中是不需要引入头文件的,文件之间可共享

尾随闭包

尾随闭包用于需要将一个很长的闭包表达式作为最后一个参数传递给函数。也就是说如果按时的最后一个参数是闭包,那么在调用它的时候就可以把这个闭包写在括号外面,并紧跟括号,函数的其他参数则仍然写在括号之中。

1
2
3
4
5
6
7
8
9
10
11
12
//这个函数接受一个String和一个闭包
//函数体内调用闭包,并且将String作为参数传递给闭包
func myFunc(strP:String,closeP:(String)->Void) {
closeP(strP)
}

//普通调用
myFunc(strP: "hello", closeP: {(string) in print(string)})
//尾随闭包
myFunc(strP: "hello") {
(string) in print(string)
}

逃逸闭包

当一个闭包作为参数传到一个函数中,但是该闭包要在函数返回之后才被执行,于是就称这样的闭包为逃逸闭包。也就是说闭包逃离了函数的作用域。写法是在这个闭包参数前加一个@escaping用来指明这个闭包是允许逃逸出该函数的。

声明一个方法,这个方法是一个逃逸闭包

该方法要做的事情,就是将闭包添加到数组中去

1
2
3
4
5
6
7
 //定义数组,里面的元素都是闭包类型的
var callBackArray : [()->Void] = []

//定义一个接收闭包的函数
func testEscapingClosure(callBack:@escaping ()-> Void) {
callBackArray.append(callBack)
}

当改变数组的时候,取第0个元素调用。此时就改变了变量x的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SomeClass {
var x = 10

func doSomething(){
testEscapingClosure {
self.x = 100
}
}
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
callBackArray.first?()
print(instance.x)

因为逃逸闭包是函数执行之后才会执行,所以可以这样理解:创建一个类的对象instance;在对象中初始化一个x=10;利用对象执行了函数doSomething;函数内部调用全局函数testEscapingClosure,期望修改instance对象的x值为100,但是此时并没有执行这个包含了赋值语句的闭包。

查找全局数组callBackArray,找到里面第一个元素,显然找到的是在testEscapingClosure函数中添加的闭包{self.x = 100},此时才通过全局数组的查询找出闭包并执行,于是x此时才被赋值为100。这就是在函数执行完毕后才执行闭包。刚好符合逃逸闭包的定义。

结论: 逃逸闭包将在函数执行之后执行,于是这段代码最后输出为100是因为闭包最后才被执行……

解决循环引用的三种方式

1.可以使用weak关键字将对象之间的联系变为弱引用

1
可以使用weak关键字将对象之间的联系变为弱引用

第一种方式的简化

1
[weak self]

使用unowned解决

1
[unowned self]

但是该方法十分危险,要确保数据一定有值。否则会发生奔溃。

weak 与unretained有何区别?

1
2
3
__weak修饰的弱引用,如果指向的对象被销毁,那么指针会立马指向nil

__unretained修饰的弱引用,如果指向的对象被销毁,它的指针依然会指向之前的内存地址,很容易产生野指针(僵尸对象)

关键字 Swift 4.0 中的 open,public,internal,fileprivate,private

  1. private
    private访问级别所修饰的属性或者方法只能在当前类里访问。

  2. fileprivate
    fileprivate访问级别所修饰的属性或者方法在当前的Swift源文件里可以访问。

  3. internal(默认访问级别,internal修饰符可写可不写)
    internal访问级别所修饰的属性或方法在源代码所在的整个模块都可以访问。
    如果是框架或者库代码,则在整个框架内部都可以访问,框架由外部代码所引用时,则不可以访问。
    如果是App代码,也是在整个App代码,也是在整个App内部可以访问。

  4. public
    可以被任何人访问。但其他module中不可以被override和继承,而在module内可以被override和继承。

  5. open
    可以被任何人使用,包括override和继承。

访问顺序:
现在的访问权限则依次为:open,public,internal,fileprivate,private。

swift中下划线的作用

格式化数字字面量

通过使用下划线可以提高数字字面量的可读性,例如:

1
2
let paddedDouble = 123.000_001     
let oneMillion = 1_000_000

忽略元组的元素值

当我们使用元组时,如果有的元素不需要使用,这时可以使用下划线将相应的元素进行忽略,例如:

1
2
let http404Error = (404, "Not Found")   
let (_, errorMessage) = http404Error

代码中,只关心http404Error中第二个元素的值,所以第一个元素可以使用下划线进行忽略。

忽略区间值

1
2
3
4
5
6
let base = 3   
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}

有时候我们并不关心区间内每一项的值,可以使用下划线来忽略这些值。

忽略外部参数名

忽略方法的默认外部参数名

在使用方法(类方法或者实例方法)时,方法的第二个参数名及后续的参数名,默认既是内部参数名,又是外部参数名,如果不想提供外部参数名,可以在参数名前添加下划线来忽略外部参数名。

1
2
3
4
5
6
class Counter {   
var count: Int = 0
func incrementBy(amount: Int, numberOfTimes: Int) {
count += amount * numberOfTimes
}
}

在上面的代码中,方法incrementBy()中的numberOfTimes具有默认的外部参数名:numberOfTimes,如果不想使用外部参数名可以使用下划线进行忽略,代码可以写为(不过为了提高代码的可读性,一般不进行忽略):

1
2
3
4
5
6
class Counter {   
var count: Int = 0
func incrementBy(amount: Int, _ numberOfTimes: Int) {
count += amount * numberOfTimes
}
}
忽略具有默认值的参数的外部参数名

当函数(或者方法)的参数具有默认值时,Swift自动为该参数提供与参数名一致的默认外部参数名,因此在进行函数调用的时候,要提供默认参数名,可以使用下划线进行忽略默认外部参数名(但是不推荐忽略外部参数名,这主要是为了调用的时候可以方便地知道每个参数的含义)。

1
2
3
4
5
func join(s1: String, s2: String, joiner: String = " ") -> String {   
return s1 + joiner + s2
}
// call the function.
join("hello", "world", joiner: "-")

如果不想使用默认外部参数名,可以进行如下修改:

1
2
3
4
5
func join(s1: String, s2: String, _ joiner: String = " ") -> String {     
return s1 + joiner + s2
}
// call the function.
join("hello", "world", "-")

tableView的用法

1、 懒加载

swift中也有懒加载的方式,并且在swift中有专门的关键字lazy来实现某一个属性实现懒加载。

格式:lazy var 变量:类型 = {创建变量代码}()

懒加载的本质在第一次使用的时候执行闭包,将闭包的返回值赋值给属性,并且只会赋值一次。

1
2
3
4
5
6
7
//懒加载只能用于结构体或者类的成员变量中
class Person:NSObject {
lazy var array : [String] = {
()->[String] in
return ["llx","lll"]
}()
}

2. tableView的使用

使用步骤如下:

创建tableView对象

使用懒加载的方式,到需要用到的时候再创建tableView。将tableView添加到控制器上的View。

1
2
3
4
5
6
7
8
9
class ViewController: UIViewController {

lazy var tableView:UITableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)

}
}

设置tableView的frame

1
tableView.frame = view.bounds

设置数据源和代理
实现UITableView的协议,并为tableView设置数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
class ViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate{

lazy var tableView:UITableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()

view.addSubview(tableView)
tableView.frame = view.bounds
//设置数据源
tableView.dataSource = self
tableView.delegate = self
}
}

实现代理方法

1
2
3
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}

创建cell。因为cell是个可选类型,有可能有值,也可能为nil。所以要进行判断。给cell设置数据的时候,选择textLabel点击option会发现textLabel也是可选类型。

在最后返回cell的时候,对cell进行强制解包。因为之前已经做过判断,所以不会出现程序奔溃的问题。

1
2
3
4
5
6
7
8
9
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let CellID = "CellID"
var cell = tableView.dequeueReusableCell(withIdentifier: CellID)
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: CellID)
}
cell?.textLabel?.text = "测试数据:\(indexPath.row)"
return cell!
}

实现点击的代理方法

1
2
3
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("点击了:\(indexPath.row)")
}

swift中的注释

在swift中,类似于paramg –mark的写法是不可行的。

它是如下两种形式

//MARK:- 要写的内容 用于分组

1
2
3
4
5
6
7
8
class ViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate{
// MARK:- 懒加载
lazy var tableView:UITableView = UITableView()
// MARK:- 系统回调函数
override func viewDidLoad() {
super.viewDidLoad()
}
}

/// 提示信息 用于提示
若在tableView系列的某个方法上面写上///提示,到其他地方调用该方法时,会出现前面写的注释信息。

枚举

定义

在swift中,枚举使用的是由enum关键字来创建的枚举,枚举的所有成员都放在一对大括号里面。它为一组相关的值定义一个共同的类型。使用case关键字来定义一个新的枚举成员值。

1
2
3
4
5
6
7
enum SomeEnum {
// 在这里定义枚举
case north
case south
case east
case west
}

上面这个枚举定义的东南西北四个值就是这个枚举的成员值。与C语言和objective-c不同的是,swift的枚举成员值在创建的时候并不会被赋予一个默认的整形值。这些值的类型就是刚刚定义好的枚举的名字SomeEnum。
如果希望多个成员值要写在同一行中,可以使用逗号将他们分割开。

1
2
3
enum Plant {
case mercury,earth,mars
}

每个枚举都定义了一个新的类型,就像swift中的其他类型一样。此时可以把它赋值给一个变量,而且可以用点语法这种形式调用。

1
2
3
var directionT = SomeEnumeration.west

directionT = .east

注意:在switch中使用枚举值的时候,一定要穷举出所有的情况,如果忽略其中的一个,代码都无法编译通过。因为它没有考虑到枚举类的全部成员。如果说不需要匹配所有的枚举成员,可以提供一个default分支来涵盖其他未明确处理的枚举成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person:NSObject{
var directionT = SomeEnum.west

func direc() {
switch directionT {
case .north:
print("north")
case .east:
print("east")
default:
print("没有方向")
}
}
}

关联值

可以定义swift的枚举类存储任意类型的关联值,而且每个枚举成员的关联值类型都可以不相同。比如说,来创建一个条形码类型。类似于库存,可以有不同类型的条形码去识别商品,比如说通过数字,或者根据产品代码来识别。

1
2
3
4
enum BarCode {
case upc(Int,Int,Int,Int)
case qrCode(String)
}

上面代码可以理解为定义一个名为BarCode的枚举类型。它的一个成员值是一个具有(Int,Int,Int,Int)类型关联值的upc,另一个成员值是具有String类型的qrCode

之后可以使用任意的条形码类型去创建新的条形码

1
2
3
4
5
6
7
class Person:NSObject {
// 创建一个名为pBar变量,并将Barcode.upc赋值给它。
func function() {
var pBar = BarCode.upc(9, 0, 3, 3)
pBar = .qrCode("ABCD")
}
}

这个时候原来的barcode.upc和其整数关联值被新的Barcode.qrCode和其字符串关联值所替代了。

枚举的原始值

枚举的原始值就是枚举的默认值,这些原始值的类型必须相同。在定义枚举的时候必须给出类型。

1
2
3
4
5
enum ASCIICHAR : Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}

在使用原始值为整数或者字符串类型的枚举时,不需要显式的为每一个枚举成员设置原始值,swift将会自动未它们赋值。

1
2
3
enum Planet : Int {
case mercury = 1, venus,earth,mars
}

上面这个例子,Planet.mercury原始值是1,那么后面的venus就是2,之后以此类推。

可以通过rawValue属性来访问枚举变量的原始值.

1
let earthsOrder = Planet.earth.rawValue

枚举递归

枚举成员的关联值为当前枚举类型时称为递归枚举。那我们可以通过使用indirect修饰枚举变量。indirect修饰整个枚举时,所有成员均可递归(也可不递归)

1
2
3
4
5
indirect enum Ari {
case number(Int)
case addition(Ari,Ari)
case multi(Ari,Ari)
}

上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。

1
2
3
4
let five = Ari.number(5)
let four = Ari.number(4)
let sum = Ari.addition(five, four)
let product = Ari.multi(sum, Ari.number(2))

通过枚举递归,就成功的创建了一个(5+4)*2的式子。

结构体

结构体通过struct去声明。在swift中,用到了大量的结构体,比如说基本的数据类型都是结构体而不是类。这意味着它们被赋值给新的常量或者变量,或者被传入函数或方法中时,值会被拷贝。

1
2
3
4
struct teacher {
var name : String = ""
var age : Int = 30
}

swift中的结构体和类的区别

在swift中类和结构体没有太多的区别,有很多的相似之处。

共同点

1
2
3
4
5
6
定义属性用来存储值;
定义方法用于提供功能;
定义下标脚本用来允许使用下标语法访问值;
定义初始化器用于初始化状态;
可以被扩展来默认所没有的功能;
遵循协议来针对特定类型提供标准功能。

最重要的两个不同之处,必须记住:

1
2
结构体是值类型,这也是swift推崇的。Swift 中所有的基本类型——整数,浮点数,布尔量,字符串,数组和字典——都是值类型,并且都以结构体的形式在后台实现。值类型就意味着拷贝。而类是引用类型,都指向同一个实例。如果一个属性发生变化,那另一个也会跟着变化。
结构体是没有继承的。类是可以继承。

swift - associatedtype的妙用

associatedtype
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型(任意类型)提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 associatedtype 关键字来指定关联类型,当然你也可以用来设计api用来构建统一的处理结构

swift property的set 、get

swift中重写set、get方法。先定义一个内部变量,当调用set的时候,系统会有一个newValue,将newValue赋值给定义的变量,然后get返回定义的变量。不过在swift中一般重写比较少。

1
2
3
4
5
private var _title : String?     
var title : String? {
set {_title = newValue}
get { return _title}
}

如果只重写 get ,会默认为 readOnly。可以通过上述代码方式,对propety进行赋值。
上述代码使用 private修饰,为了防止 instance._title的调用。
在swift中,使用 willSet 、 didSet 两个特性来监视property的初始化之外的属性值变化

1
2
3
4
var name:String?{
willSet { // }
didSet { // }
}

在使用时,计算型属性和懒加载的区别

1
2
计算型属性,本身不储存内容,都是通过计算获得结果,类似于一个函数,没有参数,有返回值。
懒加载会在第一次访问执行,闭包结束后,会把结果存在属性中,后续调用,直接返回属性的内容,懒加载的属性会分配空间存储值。

扩展

扩展 (Extension)可以做到无需修改原本的代码就直接把想要的功能实现。

1
2
3
extension 某个现有的class {
//添加新功能
}

限制:

不能添加任何已存在的 法或是属性

添加的属性不能是存储属性,只能是计算属性

1、扩展在方法中的应用

1
2
3
4
5
extension String {
func sayHello() {
print("Hello from extension")
}
}

上面这段代码是对String做了一个扩展。之后声明一个变量调用扩展方法。

1
2
var hello = "hi"
hello.sayHello()

此后,任何String类型都可以调用该扩展方法。

2、用扩展进行计算

1
2
3
4
5
extension Int {
var squared : Int {
return (self * self)
}
}

上面这段代码对Int扩展了一个属性,让它计算一个数字的平方值。

1
2
3
var newInt = 30
newInt.squared
999.squared

3. 扩展类或结构体

创建一个普通类

1
2
3
class Lisa {
var lisa = "半边天使"
}

对类扩展,新增一个方法,使其能做自我介绍

1
2
3
4
5
extension Lisa {
func describe() -> String {
return "我可是会傲娇的"
}
}

泛型

泛型可以让开发者写出灵活可重复使用的方法跟结构。

1
2
3
var stringArray = ["Hi", "Hello", "Bye"]
var intArray = [1,2,3]
var doubleArray = [1.1,2.2,3.3]

上面创建了三个不同类型的数组,若是要求打印所有数组中的元素,通常会怎么做呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func printStringFromArray(a: [String]) {
for s in a {
print(s) }
}
func printIntFromArray(a: [Int]){
for i in a {
print(i) }
}
func printdoubleFromArray(a:[Double]) {
for d in a {
print(d) }
}
printStringFromArray(a: stringArray)
printIntFromArray(a: intArray)
printdoubleFromArray(a: doubleArray)

上面这段冗长的代码实在让人不忍直视。而泛型的出现正好可以解决这一问题。

1
2
3
4
5
func printEelementFormArray(a:[T]){
for element in a {
print(element)
}
}

这段代码中的T代表了任意的元素。无论上面类型的数据都能放入其中。之后只要调用者一个方法,传入不同的数组就能将不同类型的元素打印出来。

协议

1、对面向对象语言的吐槽

  • 使用子类时,协议继承父类的属性和方法。其中某些方法或属性并不是开发者所需要的。这会让代码变得异常的臃肿。

  • 若一个类拥有很多父类,会让开发者很难找到每个类中的问题并进行修改。

  • 对象引用到内存的同一地方,若是发生改变,可能会造成代码混乱的现象。
    而swift是一种面向协议的语言。协议其实就像篮球教练,会告诉选手如何去训练,但是教练本身并不会出现在球场。Swift中的protocol不仅能定义方法还能定义属性,配合extension扩展的使用还能提供一些方法的默认实现,而且不仅类可以遵循协议,现在的枚举和结构体也能遵循协议了。

    2、一个简单的协议案例

    创建一个简单的协议,并让一个结构体去遵循

遵循协议的方法与继承类似。

1
2
3
4
5
protocol People {    
}

struct Lisa: People {
}

完善协议

给协议添加一些属性和方法,用get set 设定协议的状态。遵循协议时要了解变量是否能读取或赋值。

1
2
3
4
5
protocol People {
var name: String {get set}
var race: String {get set}
func sayHi()
}

在结构体中实现协议的方法和变量

1
2
3
4
5
6
7
struct Lisa: People {
var name: String = "Lisa"
var race: String = "Asian"
func sayHi() {
print("Hi~, I'm \(name)")
}
}

3、协议的继承

创建一个协议,让该协议继承自之前创建的People协议

1
2
3
4
5
6
7
8
9
protocol superman {
var canFly: Bool {get set}
func punch()
}

protocol superman: People {
var canFly: Bool {get set}
func punch()
}

调用

1
2
3
4
5
6
7
8
9
10
11
struct AngleLisa: superman {
var name: String = "Lisa"
var race: String = "Asian"
func sayHi() {
print("Hi, I'm \(name)")
}
var canFly: Bool = true
func punch() {
print("punch Vergil")
}
}

由此可知,一旦协议进行了继承,不但要实现本协议中所声明的方法和属性,连协议父类的方法和属性也不能落下。

微信打赏

赞赏是不耍流氓的鼓励