【Swift】基础(5)—— 属性、泛型、扩展与协议

797 阅读9分钟

属性

Swift中的属性分为 存储属性计算属性

存储属性

存储属性是存储在特定类或结构体的实例里的一个常量或变(可以理解为OC中的成员变量)。

可以使用变量和常量作为存储属性,有以下的特性:

  • 可以在定义存储属性的时候指定默认值
  • 可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值

延迟存储属性

延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性,类似OC中常用的懒加载的形式。

使用lazy关键字来标识一个延迟存储属性。

必须将延迟存储属性声明成变量(使用var关键字),因为属性的值在实例构造完成 之前可能无法得到。

常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

例:

class sample {
    lazy var no = number() // `var` 关键字是必须的
}

class number {
    var name = "Swift"
}

var firstsample = sample()
print(firstsample.no.name)

计算属性

类、结构体和枚举可以定义计算属性,计算属性不直接存储值,而是提供一个 getter 来获取值,一个可选的 setter 来间接设置其他属性或变量的值(OC中直接把属性的getter和setter方法重写也能达到这一效果)。

例:

class sample {
    var no1 = 0.0, no2 = 0.0
    var length = 300.0, breadth = 150.0
    
    // 计算变量middle
    var middle: (Double, Double) {
        get{
            return (length / 2, breadth / 2)
        }
        set(axis){
            no1 = axis.0 - (length / 2)
            no2 = axis.1 - (breadth / 2)
        }
    }
}

var result = sample()
// 调用到get方法,输出的值为(150.0,75.0)
print(result.middle)
// 调用到set方法,修改了no1和no2的值
result.middle = (0.0, 10.0)
print(result.no1)
print(result.no2)

只读计算属性

只有 getter 没有 setter 的计算属性就是只读计算属性。

属性观察器

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外(与KVO不同的是,它是在属性的定义处添加的)。

使用方式

添加willSetdidSet函数实现为属性添加观察器:

  • willSet在设置新的值之前调用
  • didSet在新的值被设置之后立即调用

willSet观察器会将新的属性值(默认名为newValue)作为固定参数传入,也可以手动指定参数名称。类似的,didSet观察器会将旧的值(默认名为oldValue)作为参数传入。

willSet和didSet观察器在属性初始化过程中不会被调用,它们只会当属性的值在初始化之外的地方被设置时被调用。

例:

class Student{
    var score:Int = 0{
        willSet{
           print("新的分数 \(newValue)")
        }
        didSet{
            if score > oldValue  {
                print("这次分数进步了 \(score - oldValue) scores")
            }
        }
    }
}
let student = Student()
student.score = 80
student.score = 90
student.score = 100

应用场景

1、可以对属性赋值的合法性进行校验,对不合法的数据进行处理,例如:

class LightBulb {
    // 限制最大值为30
    static let maxCurrent = 30
    var current: Int = 0 {
        // 在didSet中校验是否小于最大值
        // 演示使用自定义的旧值名称
        didSet(oldCurrent) {
            if current > LightBulb.maxCurrent {
                print("设置的值超过的最大值,将默认设置为最大值。")
                current = oldCurrent
            }
            print("当前的值为 \(current).")
        }
    } 
}

let bulb = LightBulb()
bulb.current = 20
bulb.current = 40

2、属性赋值时联动其他属性,例如:

class UI {
    var fontColor: UIColor!
    var backColor: UIColor!
    var theme: Theme {        
        didSet {
            // 设置风格时,联动设置字体色与背景色
            self.changeMode(theme: theme)
        }
    }
    
    init(theme: Theme) {
        self.theme = theme
        self.changeMode(theme: theme)
    }
    
    func changeMode(theme: Theme) {
        switch (theme) {
        case .DayMode:
            fontColor = UIColor.black
            backColor = UIColor.white
        case .NightMode:
            fontColor = UIColor.white
            backColor = UIColor.black
        }
    }    
}

let ui = UI(theme: .DayMode)
ui.theme = .NightMode

类型属性

结构体和枚举中的类型属性用static关键字标识。

类中的类型属性用class关键字标识。

类型属性与实例属性相似,可以通过类型本身来获取和设置,例如:

struct StudMarks {
   static let markCount = 97
   static var totalCount = 0
   var InternalMarks: Int = 0 {
      didSet {
         if InternalMarks > StudMarks.markCount {
            InternalMarks = StudMarks.markCount
         }
         if InternalMarks > StudMarks.totalCount {
            StudMarks.totalCount = InternalMarks
         }
      }
   }
}

var stud1Mark = StudMarks()

// 使用实例属性设置
stud1Mark.InternalMarks = 98
print(StudMarks.totalCount) 

// 使用类型属性设置
StudMarks.totalCount = 100
print(StudMarks.totalCount)

泛型

Swift中的泛型与Java中的泛型是比较类似的(语法上也很像)。

泛型是把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型。

泛型可以让函数和类型更加灵活且可重用。

泛型方法

在方法定义时可以使用占位类型名(在这里用字母 T 来表示,可以多个)来代替实际类型名(例如 Int、String 或 Double),从而避免重复编写功能代码相同的逻辑,例如:

// 定义一个交换两个变量的函数
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var numb1 = 100
var numb2 = 200
 
print("交换前数据:  \(numb1) 和 \(numb2)")
swapTwoValues(&numb1, &numb2)
print("交换后数据: \(numb1) 和 \(numb2)")
 
var str1 = "A"
var str2 = "B"
 
print("交换前数据:  \(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交换后数据: \(str1) 和 \(str2)")

泛型类型

泛型类型就是把泛型定义在类、结构体和枚举上,用户使用该类的时候,才把类型明确下来,用户在使用的时候就不用担心强转的问题了,例如:

// 定义泛型类型 栈,包含了push和pop方法以及元素存储的数组
struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

// 初始化时指定元素类型,即可使用
var stackOfStrings = Stack<String>()
// 字符串元素入栈
stackOfStrings.push("google")
stackOfStrings.push("runoob")
print(stackOfStrings.items)
// 出栈
stackOfStrings.pop()

类型约束

类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。

语法

在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。基础语法如下所示(和泛型类型的语法相同):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}

class 

扩展

扩展就是向一个已有的类、结构体或枚举类型添加新功能。

可以对一个类型添加新的功能,让其适配一个或多个协议,但是不能重写已有的功能。

语法

扩展声明使用关键字 extension,如:

extension SomeType {
    // 加到SomeType的新功能写到这里
}

extension SomeType: SomeProtocol, AnotherProctocol {
    // 协议实现写到这里
}

添加属性与方法

  • 在扩展中添加 计算属性 与 方法 的方式和原有类型中添加属性与方法的写法相同。
  • 在扩展中添加 存储属性 与OC类似,需要使用到runtime的方法,如:
var myNameKey = 10
extension UIView {
    var myName: String {
        set {
            objc_setAssociatedObject(self, &myNameKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        get {
            if let rs = objc_getAssociatedObject(self, &myNameKey) as? String {
                return rs
            }
            return ""
        }
    }
}

协议

协议规定了用来实现某一特定功能所必需的方法和属性,任意能够满足协议要求的类型被称为遵循(conform)这个协议。

协议中,可以定义需要遵循的属性和函数类型。

类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。

语法

协议的语法格式如下:

protocol SomeProtocol {
    // 协议内容
}

要遵循某个协议,需要在类型名称后加上协议名称,中间以:分隔。

遵循多个协议时,各协议之间用,分隔。

类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以,分隔。

如:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 类的内容
}

对属性的规定

协议中声明需要某个属性时,必须指明是只读的还是可读写的。 如:

protocol classa {
    var marks: Int { get set }
    var result: Bool { get }
}

关联类

在协议中使用associatedtype来设置关联类型,可以看做是协议中对泛型的支持方式,例如:

protocol Container {
   // 使用ItemType作为关联类型
   associatedtype ItemType
   
   // 将关联类型的实例对象作为参数传入
   mutating func append(item: ItemType)
   
   // 将关联类型的实例对象作为返回值
   subscript(i: Int) -> ItemType { get }
}

Mutating方法

有时需要在方法中改变它的实例。

例如,值类型(结构体,枚举)的实例方法中,将mutating关键字作为函数的前缀,写在func之前,表示可以在该方法中修改它所属的实例及其实例属性的值:

protocol someChange {
    mutating func change()
    func getValue() -> String?
}

enum days: daysofaweek {
    case a, b, c
    mutating func change() {
        switch self {
            case .a:
                self = .b
                print("A -> B")
            case .b:
                self = .c
                print("B -> c")
            case .c:
                self = .a
                print("C -> A")
            default:
                print("NO Such Value")
        }
    }
    
    func getValue() {
        switch self {
            case .a:
                return "A"
            case .b:
                return "B"
            case .c:
                return "C"
            default:
                return nil
        }
    }
}

规定构造器的格式

协议可以要求它的遵循者实现指定的构造器。

语法如下:

protocol SomeProtocol {
   init(someParameter: Int)
}

在实现规定了构造器格式的协议时,需要在构造器实现加上required标识:

class SomeClass: SomeProtocol {
   required init(someParameter: Int) {
      // 构造器实现
   }
}

如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示required和override修饰符:

protocol tcpprotocol {
    init(no1: Int)
}

class mainClass {
    var no1: Int // 局部变量
    init(no1: Int) {
        self.no1 = no1 // 初始化
    }
}

class subClass: mainClass, tcpprotocol {
    var no2: Int
    init(no1: Int, no2 : Int) {
        self.no2 = no2
        super.init(no1:no1)
    }
    // 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override"
    required override convenience init(no1: Int)  {
        self.init(no1:no1, no2:0)
    }
}

协议类型

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用(类似于OC中的id,简写为直接使用协议作为值的类型)。

协议可以像其他普通类型一样使用,使用场景:

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中的元素类型

如:

protocol SomeProtocol {
}

class SomeClass {
   var someParam: SomeProtocol
   init(someParam: SomeProtocol) {
        self.someParam = someParam
    }
}

协议的继承

协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。

协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 协议定义
}

类专属协议

在协议的继承列表中,通过添加class关键字,限制协议只能适配到类(class)类型。

class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。

格式为:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 协议定义
}

如果还想限制遵守的类为特定的类,比如只允许UITableViewController类去遵守FooDelegate,可以这么写:

protocol FooDelegate where Self: UITableViewController {
}

检验协议的一致性

可以使用isas操作符来检查是否遵循某一协议或强制转化为某一类型。

  • is操作符用来检查实例是否遵循了某个协议。
  • as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil。
  • as用以强制向下转型,如果强转失败,会引起运行时错误。

参考文章

Swift 属性

Swift 泛型

Swift 扩展

Swift 协议