六、类与结构体
结构体
swift中结构体和类十分相似,即可定义属性,又可以定义方法,但不具有继承的特性。使用struct定义结构体,结构体中声明变量或者常量作为结构体属性,可以创建函数作为结构体的方法,用点语法调用属性和方法。
struct Car {
//价格
var price:NSInteger
//品牌
var brand:String
//油量
var petrol:NSInteger
//提供一个驾驶方法
mutating func drive(){
if petrol > 0{
petrol -= 1
print(petrol)
}
}
//提供一个加油方法
mutating func addPetrol(){
petrol += 10
print("加了10升油")
}
}
func testStructAndClass(){
var car = Car(price: 300000, brand: "奔驰", petrol: 60)
print("这辆:\(car.brand) 价格:\(car.price) 油量:\(car.petrol)”)
var car1:Car = car
car1.drive();
print(car.petrol)
print(car1.petrol)
}//打印60 59
类是基本数据类型,car1和car是两块数据。
struct PointStruct {
var x:Double
var y:Double
//结构体中,如果修改值类型的变量需要用mutating关键字修饰
mutating func move(x:Double, y:Double){
self.x += x
self.y += y
}
}
2.类
类和结构体在属性和方法声明上都一样,不同的是,结构体中不提供构造方法,结构体会根据属性自动生成一个构造方法,而类需要开发者自己提供构造方法,在init()构造方法中需要完成对类属性的赋值操作。
class ClassCar{
//价格
var price:NSInteger
//品牌
var brand:String
//油量
var petrol:NSInteger
//提供一个驾驶方法
func drive(){
if petrol > 0{
petrol -= 1
print(petrol)
}
}
//提供一个加油方法
func addPetrol(){
petrol += 10
print("加了10升油")
}
//类比结构体多了个构造方法
init(price:NSInteger, brand:String, petrol:NSInteger) {
self.price = price
self.brand = brand
self.petrol = petrol
}
}
类是引用类型,对类实例进行数据传递时不会产生复制行为。
var car3:ClassCar = ClassCar.init(price: 300000, brand: "宝马", petrol: 55)
var car4:ClassCar = car3
car3.drive();
print(car3.petrol)
print(car4.petrol)
//打印54 car3 car4指向同一个对象
3.类的继承
子类继承父类如果要重写父类的方法,用关键字override,可以调用父类的此方法。
class BMWCar: ClassCar {
override func drive() {
super.drive()
petrol -= 1
}
override init(price:NSInteger, brand:String="宝马", petrol:NSInteger) {
super.init(price: price, brand: brand, petrol: petrol)
}
}
关键字final修饰不可被重写的属性或者方法。
final func addPetrol(){
petrol += 10
print("加了10升油")
}
4.容器的实现
swift中Array,String,Dictionary,Set都是采用结构体实现的,这点和oc有很大区别,因此上述实例在数据传递时会发生复制,深拷贝。
var arr1:Array = [0,1,2]
var arr2:Array = arr1
arr1.append(3)
print(arr1)
print(arr2)
//打印
[0, 1, 2, 3]
[0, 1, 2]
5.属性初值
可以在属性声明时提供一个初值
class BMWCar: ClassCar {
var color: String = "蓝色"
}
6.延时存储属性(懒加载属性)
延时存储属性用lazy修饰,类构造时候,延时存储属性并不进行构造初始化,只有当开发者调用到类实例的这个属性时,此属性才完成构造或者初始化,延时存储属性可以减少类实例的构造时间。
class Wheel{
var name:String
var bar:Float
init(name:String, bar:Float) {
self.name = name
self.bar = bar
}
}
class ClassCar{
lazy var wheel:Wheel = Wheel(name: "左前轮", bar: 2.5)
}
延时构造属性不是线程安全的,如果在多个线程中对延时存储属性进行调用,不能保证只被构造一次。
7.get、set方法
class RectClass {
var width:Float
var height:Float
var area:Float{
get{//定义面积长x宽
return width*height
}
set{//计算宽度=面积/高 newValue是set方法固定值
width = newValue/height
}
}
init(width:Float, height:Float) {
self.width = width
self.height = height
}
}
var rect:RectClass = RectClass.init(width: 5, height: 10)
print(rect.width, rect.height, rect.area)
rect.area = 100
print(rect.width, rect.height, rect.area)
//输出5.0 10.0 50.0
//10.0 10.0 100.0
8.属性监听器
进行属性初始化时,无论通过构造方法设置属性或者通过属性设置默认值都不会调用属性监听器的方法,初始化之后第二次为属性赋值开始,属性监听器才会被调用。
class RectClass {
var width:Float{
willSet{
print("将要设置的值:\(newValue)")
}
didSet{
print("旧的值:\(oldValue)")
}
}
var height:Float
var area:Float{
get{//定义面积长x宽
return width*height
}
set{//计算宽度=面积/高 newValue是set方法固定值
width = newValue/height
}
}
init(width:Float, height:Float) {
self.width = width
self.height = height
}
}
//初始化时候width是5
var rect:RectClass = RectClass.init(width: 5, height: 10)
print(rect.width, rect.height, rect.area)
//间接调用了width的set,新值为10
rect.area = 100
print(rect.width, rect.height, rect.area)
//输出将要设置的值:10.0
//旧的值:5.0
//10.0 10.0 100.0
只有存储属性可以设置属性监听器(width、height),而计算属性不能设置属性监听。
9.类属性与类方法
类属性由static或者class关键字声明,如果希望子类可以对计算方法可以重写,需要用class关键字声明。
static定义的类方法也不能被子类重写,class定义的类方法可以被子类重写。
class SomeClass{
static var className:String = "class name"
class var subName:String{
return "sub"+className
}
}
10.下标方法
下标使用subscript关键字定义,参数和返回值分别作为下标和通过下标所取的值,subscript的实现与计算属性类似,必须实现一个get,可选实现set,get用于取值,set用于赋值。
下标访问不局限于一维下标,可以实现任意维度的下标访问功能。
class MyArray{
var array:Array
init(param:Array
array = param
}
subscript(index1:NSInteger, index2:NSInteger)->NSInteger{
set{
array[index1][index2] = newValue
}
get{
let temp = array[index1]
return temp[index2]
}
}
}
var array = MyArray(param: [1,2,3],[4,5,6],[7,8,9])
print(array[1, 1])
array[1, 1] = 12
print(array[1,1])
//打印5
//12
11.结构体和类在构造方法结束前完成其中存储属性的构造(延时存储属性除外)。
两种初始化方法
1.存储属性声明时直接为其设置初始默认值
2.在构造方法里对存储属性构造设默认值
如果某个属性逻辑上允许为nil,开发者可以将其声明为optional可选值类型,对于optional类型的属性,如果在构造方法中不进行赋值,就会被默认赋值为nil。
class Wheel{
var name:String?//如果不进行可选值类型的声明就必须初始化时候设默认值或者init里面构造,否则编译出错。
var bar:Float
init(name:String, bar:Float) {
// self.name = name
self.bar = bar
}
}
如果类或者结构体所有的存储属性都有初始默认值,开发者不显示的提供任何构造方法,编译期也会默认生成一个无参的构造方法init(),在进行类型的实例化时,构造出来的实例所有属性值都是默认的初始值。
class Wheel{
var name:String = "左前轮"
var bar:Float = 2.5
}
11. 指定构造方法和便利构造方法
构造方法分为指定构造方法和便利构造方法
指定构造方法名称为designated,是基础构造方法,任何类至少有一个指定构造方法。
便利构造方法使用convenience关键字来修饰,是为了方便开发者使用,为类额外添加的构造方法。便利构造方法最终也要调用到指定构造方法。
原则:
1.子类的指定构造方法必须调用父类的指定构造方法
2.便利构造方法中必须调用当前类的其它构造方法
3.便利构造方法最终要调用到某个指定构造方法
class BaseClass{
init() {指定构造方法
print("指定构造方法")
}
convenience init(param:String) {//便利构造方法
print("便利构造方法")
self.init()
}
}
class subClass: BaseClass {
override init() {//指定构造方法
super.init()
}
convenience init(param:String) {//便利构造方法
self.init()
}
convenience init(param:Int) {//便利构造方法
self.init(param:"hhhh")
}
}
12. 构造方法的继承
在继承关系中,如果子类没有重写任何指定构造方法,则默认子类会继承父类的所有指定构造方法,如果子类定义了自己的指定构造方法,或者重写了父类的某个指定构造方法,则子类不再继承父类的所有指定构造方法。
如果子类提供了父类所有的指定构造方法(无论是继承还是重写),则子类会默认继承父类的便利构造方法。
class BaseClass{
init() {//指定构造方法
print("指定构造方法")
}
init(param:Int) {
print("指定构造方法param")
}
convenience init(param:String) {//便利构造方法
print("便利构造方法")
self.init()
}
}
//此类中不进行任何构造方法的定义,默认继承父类的所有构造方法
class subClass1: BaseClass {
}
//对无参的init指定构造方法进行重写,则不再继承父类的其他构造方法
class subClass2: BaseClass {
override init() {
super.init()
}
}
//没有重写父类的构造方法,但是重载定义了自己的构造方法,则不再继承父类的其他构造方法
class subClass3: BaseClass {
init(param:Bool) {
super.init()
}
}
//此类中不进行任何构造方法的定义,默认继承父类的所有构造方法
class subClass4: BaseClass {
override init() {
super.init()
}
override init(param: Int) {
super.init(param: param)
}
}
13. 构造方法的安全性检查
1.子类的指定构造方法中,必须完成当前类所有的存储属性的构造,才能调用父类的指定构造方法,此检查可以保证:在构造完从父类继承下来的所有存储属性前,本身定义的所有属性也已经构造完成。
2.子类如果要定义父类中存储属性的值,必须在调用父类构造方法之后进行设置,此检查可以保证:子类在设置从父类继承下来的存储属性时,此属性已经完成构造。
3.如果便利构造方法中需要重新设置某些存储属性的值,必须在调用指定构造方法之后进行设置,此检查可以保证:便利构造方法中对存储属性值的设置不会被指定的构造方法中的设置覆盖。
4.子类在调用父类的构造方法之前,不能使用self来引用属性,此检查可以保证:使用self调用实例本身时,实例已经构造完成。
class BaseClassA{
var property:Int
init(param:Int) {
self.property = param
}
}
class SubClassA: BaseClassA {
var subProperty:Int
init() {
//检查原则1:必须在调用父类构造方法前完成本身属性的赋值
subProperty = 1
super.init(param: 0)
//检查原则2:如果要重新赋值父类继承来的某个属性,必须在调用父类的指定构造方法后
property = 2
//检查原则4:在完成父类的构造方法之后,才能用self关键字
}
convenience init(param1:Int, param2:Int) {
self.init()
//检查原则3:遍历构造方法重要修改属性的值,必须在调用指定的构造方法之后
subProperty = param1
property = param2
}
}
14. 可失败的构造方法与析构方法
swift对于处理某些可能为空的值引入了可选值类型,对于类的构造方法,实际开发中可能遇到构造失败的情况,例如需要一些特定的参数,但是参数不符合要求,构造失败时返回nil。可失败的构造方法用init?()
class BaseClassA{
var property:Int
init(param:Int) {
self.property = param
}
//可失败构造方法
init?(param: Bool) {
guard param else {
return nil
}
property = 1
}
//析构方法
deinit {
}
}
七、内存管理
弱引用
class ClassOne{
//用weak进行弱引用避免循环引用
weak var two:ClassTwo?
deinit {
print("ClassOne deinit")
}
}
class ClassTwo{
var one:ClassOne?
init(one:ClassOne?) {
self.one = one
}
deinit {
print("ClassTwo deinit")
}
}
func memoryTest(){
var one:ClassOne? = ClassOne.init()
var two:ClassTwo? = ClassTwo.init(one: one)
one?.two = two
one =nil
two =nil
}
2.无主引用
类似oc的assign,swift提供无主引用,用unowned修饰,被引用对象释放时候引用不会置nil
class ClassThree{
//无主引用
unowned var four:ClassFour?
deinit {
print("ClassThree deinit")
}
}
class ClassFour{
var three:ClassThree?
init(three:ClassThree?) {
self.three = three
}
deinit {
print("ClassFour deinit")
}
}
var three:ClassThree? = ClassThree.init()
var four:ClassFour? = ClassFour.init(three: three)
three?.four = four
four =nil
three?.four//crash Fatal error: Attempted to read an unowned reference but object 0x6000036a9580 was already deallocated2020-04-09 14:09:46.214608+0800 LearnSwift[53426:10252006] Fatal error: Attempted to read an unowned reference but object 0x6000036a9580 was already deallocated
无主引用与拆包结合可以使两个类的相互引用属性都是非可选值属性(非optional),这也是无主引用的最佳场景
class ClassThree{
//无主引用
unowned var four:ClassFour?
init(four:ClassFour) {
self.four = four
}
deinit {
print("ClassThree deinit")
}
}
class ClassFour{
//使用隐式拆包
var three:ClassThree!
init() {
/*在创建three属性的时候将当前类实例本身作为参数传入
有构造方法的原则可知在three属性创建完成之前,不可以用self属性
对于隐式解析类的属性,上述原则可以忽略,其告诉编译器默认此属性是构造完成的
*/
three = ClassThree(four: self)
}
deinit {
print("ClassFour deinit")
}
}
var obj5:ClassFour? = ClassFour()
obj5 = nil
3.block中的循环引用
class ClassSix{
var name:String = "hhhh"
lazy var close:()->Void = {
print(self.name)
//self和block产生循环引用
}
deinit {
print("ClassSix deinit")
}
}
var obj6:ClassSix? = ClassSix()
obj6?.close()
obj6 =nil
//执行完obj6不释放
class ClassSix{
var name:String = "hhhh"
lazy var close:()->Void = {
//使用捕获列表对block中的self进行无主引用的转换
[unowned self]()->Void in
print(self.name)
}
deinit {
print("ClassSix deinit")
}
}
var obj6:ClassSix? = ClassSix()
obj6?.close()
obj6 =nil
//执行完obj6可以释放
3.异常的捕获
enum MyError:Error {
case DseToryError
case NormarlError
case SimpleError
}
func myFunc(param:Bool) throws -> Void {
if param {
print("success")
}else{
throw MyError.NormarlError
}
}
//使用do-catch进行异常的捕获与处理
do {
try myFunc(param: false)
}catch MyError.SimpleError {
print("SimpleError")
}catch MyError.NormarlError {
print("NormarlError")
}catch MyError.DseToryError {
print("DseToryError")
}catch{
print("other Error")
}
上述结构可以根据异常类型进行分类处理方案,保证代码健壮性。
swift还有一种方法,可以将异常映射为optional值,如果函数没有异常正常返回,如果执行出错,抛出异常,则会返回optional的nil值。使用try?来调用函数可以将异常映射为optional值。
let temp = try? myFunc(param: false)
if temp == nil {
print("执行失败")
}else{
print("执行成功")
}
4.延时执行结构
使用延时执行语句可以保证无论函数因为何种原因结束,在结束前都会执行延时执行结构块中的代码。
func temFun() {
defer {
print("finish")
}
print("handel")
}
temFun()
//打印handel
//finish
七、类型转换、泛型、扩展、协议
类型检查
判断某个实例是否属于某个类型用关键字is,返回一个bool值。
var str = "hhh"
if str is String {
print("str是string类型")
}
类型转换
swift类型转换用as关键字
class MyBaseClass{
var name:String?
}
class MySubClassA: MyBaseClass {
var count:Int?
}
class MySubClassB: MyBaseClass {
var isBiger:Bool?
}
let obj1:MyBaseClass = MyBaseClass()
obj1.name = "hhhhh"
let obj2:MySubClassA = MySubClassA()
obj2.count = 3
let obj3:MySubClassB = MySubClassB()
obj3.isBiger = true
let array:Array
for obj in array {
if obj is MySubClassA {
print((obj as! MySubClassA).count!)
}else if obj is MySubClassB {
print((obj as! MySubClassB).isBiger!)
}else if obj is MyBaseClass {
print(obj.name!)
}
}//打印hhhhh
//3
//true
使用类型转换换时可以使用as?或者as!。as?是一种安全的转换方式,会将值类型转后的结果映射为optional类型,如果类型转换成功,则值为原实例,如果类型转换失败,则会返回nil。
as!是强制转换方式,默认转换完成后一定会成功,如果转换失败,会产生运行时错误崩溃。使用as!转换时,必须保证实例的真实类型和要转换的类型一致。
Any、AnyObject
AnyObject可以表示通用的对象类型,不能用来描述值类型。
Any可以表示任何类型,包含值类型和引用类型。
class MySubClassC {
}
class MySubClassD {
}
class MySubClassE {
}
let obj4 = MySubClassC()
let obj5 = MySubClassD()
let obj6 = MySubClassE()
let array2:Array
let array3:Array<Any> = [obj4, obj5, obj6, "hhhh"]
for obj in array3 {
if obj is MySubClassC {
print("MySubClassC")
}else if obj is MySubClassD{
print("MySubClassD")
}else if obj is MySubClassE{
print("MySubClassE")
}else{
print("other")
}
}
泛型
泛型用来表达一种未定的数据类型,泛型可以作为函数参数。参数列表前用尖括号定义泛型,如果要定义多个泛型,用逗号分隔。作用域是函数部分。
func exchange
let tmp = param1
param1 = param2
param2 = tmp
}
struct stack
var items:Array
mutating func push(param:ItemType){
self.items.append(param)
}
mutating func pop()->ItemType{
return self.items.removeLast()
}
}
扩展
特性同oc
class MyBaseClass{
var name:String
var age:NSInteger
init() {
name = "hhhhh"
age = 20
}
}
extension MyBaseClass{
var nameAndAge:String{
return "\(name)\(age)"
}
}
协议
protocol MyProtocol {
var name:String{get set}
func printName()
static func logClassName()
}
class MyClass12: MyProtocol {
var name: String
init() {
name = "hhhh"
}
func printName() {
print(name)
}
static func logClassName() {
print("MyClass12")
}
}