一: 可选链
之前写的代码都是这样调用:
如果person
是可选类型,就会报错:
解决这种报错有两种方式,一种是在person
后面加感叹号!
,进行强制解包:
使用感叹号!
强制解包这种做法不安全,一旦person
对象为nil
,程序就会崩溃.
另一种是加问号?
:
加?
的意思时会先判断person
对象是否为nil
,如果为nil
,就不会再调用age
,直接返回nil
;如果person
对象不为nil
,才会调用age
,正常返回;那什么类型既能接受nil
,又能接受Int
呢?当然是可选项类型.
所以,只要通过person?
调用的属性,方法,下标等等,它们的结果要么是nil
,要么就被包装成可选项类型:
class Dog{
var name: String = "大黄"
}
class Cat{
var color: String = "橘色"
}
class Person{
var name: String = ""
var age: Int = 0
var dog: Dog = Dog()
var cat: Cat? = Cat()
func eat(){
print("eat")
}
func height() -> Int{
180
}
subscript(index: Int) -> Int{
index
}
}
var person: Person? = nil
var name = person?.name //String?
var age = person?.age//Int?
var color = person?.cat?.color //String?
var height = person?.height()// Int?
var index = person?[3] // Int?
上面代码person?.cat?.color
使用多个?
链接在一起就被称为可选链
.如果可选链的任何一个节点为nil
,整个调用就会失败.
所以我们拿到调用结果使用的时候就要解包了,一般用可选项绑定判断一下:
if let a = age{
print("age 的值为",a)
}else{
print("age 为nil")
}
同样也可以判断方法调用是否成功:
if let result = person?.eat(){
print("调用成功",result)
}else{
print("调用失败")
}
eat()
方法并没有返回值,为什么也可以用可选项绑定来判断呢.因为无返回值就是Void
,而Void
的本质就是空元组.所以如果eat()
调用成功就会返回一个空元组,调用失败就返回nil
.所以可以使用可选项绑定判断.
思考一下下面代码的打印结果:
var a: Int? = 10
a? = 5
print("a",a)
var b: Int? = nil
b? = 5
print("b",b)
打印结果为a Optional(5)
, b nil
;因为b?
会先判断b
是否为nil
,如果为nil
就不会执行= 5
的赋值运算.
二: 协议(Protocol)
swift 中的协议可以定义属性 , 方法 , 下标
的声明,协议可以被结构体,枚举,类遵守.
我们在定义协议时要遵守以下规则:
协议定义属性时只能用 var , 不能用 let
实现协议时的属性权限要不小于协议中定义的属性权限
如上图所示,协议中定义的slogan
属性权限是set , get
.而Person
实现协议时的属性权限只有get
,就会报错.
3.协议中的类型属性,类型方法,类型下标只能用 static 定义,不能使用 class
我们知道class
和static
都可以用来定义类型计算属性,类型方法,类型下标.但是如果要在协议中定义类型方法,类型属性只能用static
.因为协议可以被类,结构体,枚举遵守.而class
只能在类中使用.
- 如果要在值类型
(结构体 , 枚举)
实现的协议方法中修改自身内存,必须在定义协议方法前加上mutating
关键字.不能在实现协议方法时添加,那样无效.
- 协议中定义的初始化器
init
,非final
类实现协议时必须加上required
.
这样做是为了保证所有的子类都有此初始化器.
协议的继承
协议之间可以彼此继承,一个协议可以继承另一个协议.
协议的组合
可以通过不同的协议组合来规范参数类型或者返回值类型,也可以和类组合使用,但最多组合一个类
protocol Study {
func read()
}
protocol Sport{
func run()
}
class Person: Sport{
func run() {
}
func read() {
}
}
func test1(obj: Study){
//接受遵守Study协议的实例
}
func test2(obj: Study & Sport){
//接受遵守Study 和 Sport协议的实例
}
func test3(obj: Person & Study & Sport){
//接受遵守Study 和 Sport协议,并且是Person或者其子类的实例
}
注意: 只能用 & 组合协议,不能使用 | 组合协议,因为使用 | 组合还要单独区分具体是哪个类,这样做毫无意义.
Any , AnyObject
Swift 中提供了两种特殊的类型Any
,AnyObject
Any
:可以代表任意类型(枚举, 结构体,类,函数类型
)
AnyObject
:代表任意类类型(也就是使用class
创建的任意类型)
例如创建一个可以添加任意类型的数组:
//添加任意类型的数组
var array = Array<Any>()
array.append(1) // Int
array.append("2")// 字符串
array.append(Person())// 类对象
array.append(Season.spring) // 枚举
创建数组还有另一种写法:
var array2 = [Any]()
array2.append("hello")
注意: 如果在协议后面加上 AnyObject 或者 class , 代表只有类类型才能遵守协议
is , as , as? , as!
is
: 判断是否为某种类型
as
: 用来做强制转换
如果使用as!
强制解包,没有值的情况下会直接崩溃,所以我们解包的时候建议还是使用as?
as
: 如果能确定百分百转换成功的,可以不加符号,直接使用as
,比如:
var num1 = 10 as Float
var num2 = num1 as Any
注意: 任何类型都可以使用 as 转换为 Any 类型,但是不能使用 as 转换回去.必须使用 as? , as! 转换回去.
比如:
X.self. , X.Type , AnyClass(X
表示的是类)
如下所示代码:
class Person{
}
var p = Person()
它的内存分布如下:
全局变量p
存储的是Person
实例对象的前8个字节的地址.这8个字节就是Person
元类型地址.
X.self
:是一个元类型(metadata)
的地址,metadata
中存放的是类型相关的信息.
也就是说X.self
返回的是Person
实例对象的前8个字节中存放的内存地址,我们将通过汇编证明这一点.
实例代码:
class Person{
}
var p = Person()
var personType = Person.self
以上代码的汇编如下:
从上图可以看到,personType
中存放的内容和Person
对象的前8个字节的内容相同.
X.self
是X.Type
类型的:
所以,也可以这样定义:
元类型的继承
元类型同样也支持继承,父类metatype
可以指向子类metatype
:
AnyObject.Type
: 上面讲过AnyObject
是指任意类类型,所以AnyObject.Type
表示任意类类型的metadata
.
在 Swift 有这样的定义:
public typealias AnyClass = AnyObject.Type
即AnyClass
也表示任意类类型的metaclass
type(of: <T>)
:传入一个实例对象,返回这个实例对象的元类型.
type(of:)
表面上看起来很像一个函数,但是我们窥探它的汇编发现它的本质就是直接取出实例对象的前8个字节,并没有函数调用:
元类型的应用
一: 通过类名创建控制器实例,类似于 OC 中的 xx.class
override func viewDidLoad() {
super.viewDidLoad()
var vcs = [UIViewController.Type]()
vcs.append(HomeViewController.self)
vcs.append(MineViewController.self)
for vc in vcs{
//通过元类型创建视图控制器
let childVC = vc.init()
self.addChild(childVC)
}
}
二: 通过父类元类型,创建子类实例.注意:父类中的 init() 方法必须加上 required ,因为必须要确保子类都有 init() 方法的实现.
class Person{
required init(){
}
}
class Student: Person {}
class Teacher: Person {}
class Driver: Person {}
func createPerson(pTypes: [Person.Type]) -> [Person]{
var persons = [Person]()
for pType in pTypes{
persons.append(pType.init())
}
return persons
}
Self:一般用作方法返回值,用来限定方法返回值和方法调用者必须是同一类型,类似OC
中的instancetype
如上图所示,test
是Person
类中的实例方法,返回值是Self
.即方法调用者和返回值必须是同一类型
.为什么返回Person
实例对象报错了呢?因为 Person 可能也会有子类,所以不能写死
所以要这样写:
但是通过元类型创建实例对象必须要加上required
:
Self
也可表示当前类型:
比如说下面代码:
class Person{
var age = 0
static var name = "佚名"
func makeOneSelf(){
print("my name is \(Self.name) , age is \(self.age)")
}
}
Self
代表当前类型,也就是Person
类型,所以能调用类型属性;
self
表示调用方法的当前实例对象.