首页投稿(暂停使用,暂停投稿)iOS Developer

Swift复习系列:属性

2017-09-12  本文已影响45人  ZYiDa

先来看一下百度百科关于属性的解释

属性就是对于一个对象的抽象刻画
一个具体事物,总是有许许多多的性质与关系,我们把一个事物的性质与关系,都叫做事物的性。
事物与属性是不可分的,事物都是有属性的事物,属性也都是事物的属性。
一个事物与另一个事物的相同或相异,也就是一个事物的属性与另一事物的属性的相同或相异。
由于事物属性的相同或相异,客观世界中就形成了许多不同的事物类。具有相同属性的事物就形成一类,具有不同属性的事物就分别地形成不同的类。

Swift中,属性将值和特定的类、结构体或者枚举关联起来。它们分为三种:存储属性、计算属性和类型属性。

存储属性

存储属性就是存储在特定类或结构体实例里面的一个常量或者变量,存储属性可以使常量存储属性也可以是变量存储属性,分别用letvar来定义。我们可以在定义存储属性的时候指定默认的值,也可以在构造过程中设置或修改存储属性的值,同时也可以修改常量存储属性的值(这里需要注意)

struct ValueRange
{
    var startValue:Int//变量存储属性
    let length:Int//常量存储属性
}


        //存储属性
        var testStoreProperty = ValueRange.init(startValue: 128, length: 128)
        print("-01-结构体ValueRange=\(testStoreProperty)")
        //-01-结构体ValueRange=ValueRange(startValue: 128, length: 128)

        //修改它的变量存储属性值
        testStoreProperty.startValue = 1024
        print("-02-结构体ValueRange=\(testStoreProperty)")
        //-02-结构体ValueRange=ValueRange(startValue: 1024, length: 128)

在上边,我们声明了一个ValueRange的结构体,里面包含startValue的变量存储属性和length的常量存储属性。然后我们用var来实例化一个结构体对象,在赋值后,我们还可以通过.语法来访问startValue的变量存储属性,并修改值,但是却不能修改length的常量存储属性的值。
但是,如果我们用let来实例化ValueRange,那又会有什么情况呢?

let testLetStoreProperty = ValueRange.init(startValue: 128, length: 256)
testLetStoreProperty.startValue = 768

如上,虽然startValue是变量存储属性,但还是会报错为:Cannot assign to property: 'testLetStoreProperty' is a 'let' constant。这是因为Struct结构体是值类型的,当值类型被声明为常量类型时,它下面额所有属性则都变成了常量类型。
但是类(class)却不一样,类属于引用类型,当被实例化为一个常量时,我们仍可以修改它下面的值。

存储属性的延迟存储属性Objective-C中的懒加载很相似,它在第一次被调用的时候才会初始化其属性值,我们可以在属性声明的前面使用lazy关键字来表示延时存储属性,但是,属性声明必须使用var来声明为变量存储属性,这是因为属性的初始值可能在构造完成之后才会被获得,而常量属性必须在构造完成之前必须要有初始值。如下

class Test01
{
    var testFileName = "MyTestFileName"

}

class Test02
{
    lazy var test01 = Test01()
    var myTestString = String.init()
}

let test02:Test02 = Test02()
test02.myTestString += "hahahahahahahahahahahhaha"
test02.test01.testFileName += ".docx"//这个时候才开始获取Test01的实例化对象test01的初始值
print("\(test02.test01.testFileName)\n\(test02.myTestString)")

如上,Test01的实例化对象test01直到test02.test01.testFileName += ".docx"时才获取到初始值。

但是,一个延时存储的属性在没有初始化时就被多个线程同时访问,我们则无法确保该属性是否会被只初始化一次
Swift中的属性没有对应的实例变量,属性的后端存储也无法 直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息包括命名、类型和内存管理特征都在唯一一个地方(类型定义中)定义。

计算属性

除了存储属性之外,类、结构体和枚举也可以定义计算属性,计算属性不直接存储值,它通过一个getter可选的setter来简介获取和设置其它变量或者属性的值。如下代码,

struct Point
{
    var x = 0.0
    var y = 0.0
}

struct Size
{
    var Width = 0.0
    var Height = 0.0
}


struct Rect
{
    var origin = Point()
    var size = Size()
    var center:Point
    {
        get
        {
            let centerX = origin.x + (size.Width/2)
            let centerY = origin.y + (size.Height/2)
            return Point(x:centerX,y:centerY)
        }
        set(newPoint)
        {
            origin.x = newPoint.x - (size.Width/2)
            origin.y = newPoint.y - (size.Height/2)
        }
    }
}
        var square = Rect.init(origin: Point.init(x: 0, y: 0), size: Size.init(Width: 100, Height: 100))
        let squareCenter = square.center
        square.center = Point.init(x: 15, y: 15)
        print("SquareOrigin = \(square.origin),SquareCenter = \(squareCenter)")
        //SquareOrigin = Point(x: -35.0, y: -35.0),SquareCenter = Point(x: 50.0, y: 50.0)

Rect提供了一个名为center的计算属性。一个矩形的中心店可以从原点origin和大小size计算出,所以不需要像SizePoint那样显示声明来保存。

计算属性center提供了自定义的gettersetter来获取和设置矩形的中心点和原点。

简化setter声明

如果计算属性squaresetter没有定义如上的newPoint来表示新值的参数名,则可以使用newValue来作为默认名称。如下

struct Rect
{
    var origin = Point()
    var size = Size()
    var center:Point
    {
        get
        {
            let centerX = origin.x + (size.Width/2)
            let centerY = origin.y + (size.Height/2)
            return Point(x:centerX,y:centerY)
        }
        set
        {
            origin.x = newValue.x - (size.Width/2)
            origin.y = newValue.y - (size.Height/2)
        }
    }
}
只读计算属性

只有getter没有setter的计算属性就是只读计算属性。只读计算属性可以通过.语法来获取需要的值,但不能设置新的值。
定义计算属性,必须使用var关键字,因为它们的值都不是固定的,同时我们还可以去掉get关键字,如下

struct SquareRect
{
    var width = 0.0
    var height = 0.0
    var area:Double
    {
        return (width * height)
    }
}

属性观察器

属性观察器的作用:

监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前的值相同也会执行该操作。

我们可以为除延迟属性之外的其它存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(这时为存储属性计算属性)添加属性属性观察器。我们不必为非重写的计算属性添加属性观察器,因为通过它的setter可以直接监控和响应值的变化。
我们可以为属性添加下面的一个或全部的观察器:

还有一点需要注意:

父类的属性在子类的构造器中被赋值时,会先调用它在父类中的willSetdidSet观察器,随后才会调用子类的观察器。在父类初始化方法调用之前,子类给属性赋值时,观察期不会被调用。

请看下面的例子

class BlogsCounter
{
    var totalBlogs:Int = 0{
        willSet(newBlogs)
        {
            print("All blogs is \(newBlogs)")
        }
        didSet
        {
            if totalBlogs > oldValue
            {
                print("The increrased blogs = \(totalBlogs - oldValue)")
            }
        }
    }
}

let blogsCounter = BlogsCounter()
blogsCounter.totalBlogs = 128
blogsCounter.totalBlogs = 256
blogsCounter.totalBlogs = 768
blogsCounter.totalBlogs = 1024

输出结果

All blogs is 128
The increrased blogs = 128
All blogs is 256
The increrased blogs = 128
All blogs is 768
The increrased blogs = 512
All blogs is 1024
The increrased blogs = 256
Program ended with exit code: 0

willSet在上面的例子只是负责输出新的值,参数名被自定义为newBlogs,也可以使用默认的参数名newValue
didSet观察器在totalBlogs的值改变后被调用,它将新值和旧值比对,然后做相应的处理。

注意

如果将属性通过 in-out方式传入函数,和也会调用。这是因为in-out 参数采用了拷入 拷出模式:即在函数内部使用的是参数的copy ,函数结束后,又对参数重新赋值。

全局变量和局部变量

类型属性

关于实例属性的类型属性的理解定义

类型属性的作用

类型属性用于某个类型所有实例共享的数据。eg.所有实例都会用到的一个常量(类似于C语言中的静态常量),或者所有实例都能访问的一个变量(类似C语言中的静态变量)。

注意

  1. 存储型类型属性可以是变量或者常量,计算型类型属性就和实例的计算型属性一样,只能是变量。
  2. 和实例的存储型属性不同,必须在初始化时给存储型类型属性一个默认值。因为类型没有构造器,所以也就无法初始化的过程中使用构造器给类型属性赋值。
  3. 存储型类型属性在是延时初始化的,只有第一次访问到它的时候才会被初始化,即使有多个线程同时对它进行访问,系统也会保证只对它进行一次初始化,并且它也不需要lazy修饰符。
类型属性的语法以及获取和设置类型属性的值

CObjective-C中,与某个类型关联的静态常量和静态变量,是作为全局(global)静态变量定义的。但是在Swift中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内
我们可以使用关键字static来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 class来支持子类对父 类的实现进行重写。
如下

struct SomeStructure
{
    static var storedTypeProperty = "Test string value"
    static var computedTypeProperty:Int
    {
        return 128
    }
}

enum SomeEnumeration
{
    static var storedTypeProperty = "Test string value"
    static var computedTypeProperty:Int
    {
        return 256
    }
}

class SomeMyClass
{
    static var storedTypeProperty = "Test string value"
    static var computedTypeProperty:Int
    {
        return 512
    }
    class var overrideableComputedTypeProperty:Int
    {
        return 1024
    }
}

print(SomeStructure.storedTypeProperty)
print(SomeStructure.computedTypeProperty)

print(SomeEnumeration.storedTypeProperty)
print(SomeEnumeration.computedTypeProperty)

print(SomeMyClass.storedTypeProperty)
print(SomeMyClass.computedTypeProperty)
print(SomeMyClass.overrideableComputedTypeProperty)

输出结果

Test string value
128
Test string value
256
Test string value
512
1024
Program ended with exit code: 0

在上面的例子中,计算型类型属性都是只读的,当然你也可以设置为可读可写的,请参考上面的计算型属性
和实例属性一样,我们都是用.语法来访问,但不同的是类型属性使用类型名而不是类型对象名来访问。

接下来请看一个详细的例子,然后我们再具体讲解一下。


struct AudioChannel
{
    static let thresholdLevel = 10//声道的音量上限值
    static var maxInputLevelForAllChannels = 0//所有的声道中的最大值
    var currentLevel:Int = 0
    {
        didSet
        {
            //如果当前音量大于上限值,会被修正为上限值之内
            if currentLevel > AudioChannel.thresholdLevel
            {
                currentLevel = AudioChannel.thresholdLevel
            }

            //将当前音量作为新的最大输入音量
            if currentLevel > AudioChannel.maxInputLevelForAllChannels
            {
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}

var leftChannel  = AudioChannel.init()//左声道对象
var rigntChannel = AudioChannel.init()//右声道对象

leftChannel.currentLevel = 9
print("maxInputLevelForAllChannels = \(AudioChannel.maxInputLevelForAllChannels)")

rigntChannel.currentLevel = 12
print("maxInputLevelForAllChannels = \(AudioChannel.maxInputLevelForAllChannels) \ncurrentLevel = \(rigntChannel.currentLevel)")

运行结果

maxInputLevelForAllChannels = 9
maxInputLevelForAllChannels = 10 
currentLevel = 10
Program ended with exit code: 0

在上面的例子中,我们定义了一个结构体,使用两个存储型类型属性来表示左声道和右声道的音量,每个声道的音量范围是0-10.

在结构体AudioChannel

在存储型实例属性currentLevel中包含didSet属性观察器来检查每次设置后的属性值,它会做如下处理

上一篇下一篇

猜你喜欢

热点阅读