任何一门语言都有属性的概念。Swift中的属性是怎么的呢?

一、属性

Swift中跟实例相关的属性可以分为2大类:存储属性和计算属性。

1.1. 存储属性(Stored Property

特点:

  • 类似于成员变量的概念;
  • 存储在实例的内存中;
  • 结构体、类可以定义存储属性;
  • 枚举不可以定义存储属性。

示例代码:

struct Circle {
var radius: Double
} class classCircle {
var radius: Double
}

关于存储属性,Swift有个明确的规定:

在创建类或结构体的实例时,必须为所有的存储属性设置一个合适的初始值。

  • 可以在初始化器里为存储属性设置一个初始值;
  • 可以分配一个默认的属性值作为属性定义的一部分。

1.2. 计算属性(Computed Property

特点:

  • 本质就是方法(函数);
  • 不占用实例的内存;
  • 枚举、结构体、类都可以定义计算属性。

示例代码:

struct Circle {
// 存储属性
var radius: Double
// 计算属性
var diameter: Double {
set {
print("set")
radius = newValue / 2
}
get {
print("get")
return radius * 2
}
}
}
var c = Circle(radius: 10)
c.radius = 11 print("--1--")
c.diameter = 40
print("--2--")
print(c.diameter)
/*
输出:
set
get
20.0
*/

输出分析: 上面代码如果执行c.diameter = 40radius的值就会变为20。因为这样会执行diameterset方法(40作为参数),上面的案例看到变量newValue,但是代码中没有定义这个变量,其实newValueset方法提供的形参,只不过省略没有写而已,完整的set方法代码应该是set(newValue) {...}newValue是默认值,可以按照自己的规范修改(建议使用默认的形参命名)。c.diameter调用的是diameterget方法。

内存分析:

上面示例代码中结构体Circle占用多少内存呢?

print(MemoryLayout<circle>.stride)
// 输出:8

结果显示占用8个字节。因为计算属性的本质是方法。

补充说明:

  1. set传入的新值默认叫做newValue,也可以自定义。

    struct Circle {
    var radius: Double
    var diameter: Double {
    set(newDiameter) {
    radius = newDiameter / 2
    }
    get {
    return radius * 2
    }
    }
    }
  2. 只读计算属性:只有get,没有set

    如果是只读属性,get可以省略不写:

    struct Circle {
    var radius: Double
    var diameter: Double { radius * 2 }
    }
  3. 定义计算属性只能用var,不能用let

  4. set就必须有get

扩展: 枚举rawValue的本质就是只读的计算属性。

1.3. 属性观察器(Property Observer)

通过名字就可以联想到OC中的KVO,是的,两者确实有相似之处。在Swift中可以为非lazyvar存储属性 设置属性观察器。

示例代码:

struct Circle {
var radius: Double {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init() {
self.radius = 2.0
print("Circle Init")
}
}
var c = Circle()
// 输出:Circle Init
c.radius = 3.0
/*
输出:
willSet 3.0
didSet 2.0 3.0
*/

分析:

  • willSet会传递新值,默认叫做newValue
  • didSet会传递旧值,默认叫做oldValue
  • 在初始化器中设置属性值不会触发willSetdidSet。同样在属性定义时设置初始值也不会触发。

二、延迟存储属性(Lazy Stored Property)

使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化。

特点:

  • lazy属性必须是var,不能是let(let必须在实例的初始化方法完成之前就拥有值);
  • 如果多条线程同时第一次访问lazy属性,无法保证属性只被初始化1次(非线程安全)。

示例代码:

class Car {
init() {
print("Car init")
}
func run() {
print("car run")
}
} class Person {
lazy var car = Car()
init() {
print("Person init")
}
func goOut() {
print("Person goOut")
car.run()
}
}
var p = Person()
// 输出:Person init
p.goOut()
/*
输出:
Person goOut
Car init
car run
*/

分析: 如果Person中的存储属性car没有lazy修饰,在创建Person对象p的时候就会调用存储属性car的初始化方法。添加lazy修饰后,只会在第一次使用car属性(对象)时进行初始化。

注意点: 当结构体包含一个延迟存储属性时,只有var才能访问延迟存储属性。因为延迟属性初始化时需要改变结构体的内存,而结构体如果使用let修饰后就不能修改所在内存。

三、类型属性(Type Property)

严格来说,属性可以分为:

  • 实例属性(Instance Property):只能通过实例去访问

    • 存储实例属性(Stored Instance Property):存储在实例的内存中,每个实例都有1份;
    • 计算实例属性(Computed Instance Property)
  • 类型属性(Type Property):只能通过类型去访问

    • 存储类型属性(Stored Type Property):整个程序运行过程中,就只有1份内存(类似于全局变量)
    • 计算实例属性(Computed Type Property)

可以通过static定义类型属性。如果是类,也可以用关键字class

示例代码:

struct Shape {
var width: Int
static var count: Int = 30
}
var s = Shape(width: 10)
s.width = 20
print("before count:\(Shape.count)") // 输出:before count:30
Shape.count = 40
print("after count:\(Shape.count)") // 输出:after count:40

3.1. 类型属性细节

  1. 不同于存储实例属性,存储类型属性必须进行初始化,否则报错(因为类型没有像实例那样的init初始化器来初始化存储属性):

  2. 存储类型属性默认就是lazy,会在第一次使用的时候才初始化,就算被多个线程同时访问,保证只会初始化一次(线程安全)。

  3. 存储类型属性可以是let

  4. 枚举类型也可以定义类型属性(存储类型属性,计算类型属性)。

3.2. 单例模式

使用类型属性可以创建单例模式。

示例代码:

class FileManager {
public static let shared = FileHandle()
private init() {}
}
var f1 = FileManager.shared;

把初始化器设为private,这样就无法让外界使用init创建实例。把类型属性设为public,在其他文件中也可以访问,存储类型属性再用let修饰,这样就能保证实例只能指向一块固定内存。

3.2. 类型存储属性的本质

第一步:示例代码

第二步:查看全局变量内存地址

分析:

num1内存地址:0x1000013f1 + 0x5df7 = 0x1000071E8

num2内存地址:0x1000013fc + 0x5df4 = 0x1000071F0

num3内存地址:0x100001407 + 0x5df1 = 0x1000071F8

结论:

num1,num2,num3三个变量的内存地址是连续的。

第三步:查看类型存储属性地址

分析:

num1内存地址:0x100001013 + 0x631d = 0x100007330

Car.count内存地址:0x100007338

num3内存地址:0x10000105c + 0x62e4 = 0x100007340

结论:

num1,Car.count,num3三个变量的内存地址是连续的。

从内寸角度看,类型存储属性写在外面和里面没有什么区别,写在类里面只是代表该属性有一定访问权限。

类型存储属性默认是lazy,所以在第一次访问的时候做了很多操作。而且只被初始化一次。

通过汇编查看类型存储属性初始化:

发现,类型属性初始化最终调用的是GCD中的dispatch_once,这样就保证了属性只被初始化一次。

Swift系列九 - 属性的更多相关文章

  1. struts2官方 中文教程 系列九:Debugging Struts

    介绍 在Struts 2 web应用程序的开发过程中,您可能希望查看由Struts 2框架管理的信息.本教程将介绍两种工具,您可以使用它们来查看.一个工具是Struts 2的配置插件,另一个是调试拦截 ...

  2. Bing Maps进阶系列九:使用MapCruncher进行地图切片并集成进Bing Maps

    Bing Maps进阶系列九:使用MapCruncher进行地图切片并集成进Bing Maps 在Bing Maps开发中,由于各种应用功能的不同,更多的时候用户可能需要将自己的一部分图片数据作为地图 ...

  3. XAML实例教程系列 - 依赖属性和附加属性(四)

    XAML实例教程系列 - 依赖属性和附加属性 2012-06-07 13:11 by jv9, 1479 阅读, 5 评论, 收藏, 编辑 微软发布Visual Studio 2012 RC和Wind ...

  4. 爬虫系列(九) xpath的基本使用

    一.xpath 简介 究竟什么是 xpath 呢?简单来说,xpath 就是一种在 XML 文档中查找信息的语言 而 XML 文档就是由一系列节点构成的树,例如,下面是一份简单的 XML 文档: &l ...

  5. Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  6. 窥探Swift系列博客说明及其Swift版本间更新

    Swift到目前为止仍在更新,每次更新都会推陈出新,一些Swift旧版本中的东西在新Swift中并不适用,而且新版本的Swift会添加新的功能.到目前为止,Swift为2.1版本.去年翻译的Swift ...

  7. 李洪强iOS开发Swift篇—09_属性

    李洪强iOS开发Swift篇—09_属性 一.类的定义 Swift与Objective-C定义类的区别 Objective-C:一般需要2个文件,1个.h声明文件和1个.m实现文件 Swift:只需要 ...

  8. java基础解析系列(九)---String不可变性分析

    java基础解析系列(九)---String不可变性分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---In ...

  9. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

随机推荐

  1. Java中常见方法详解合集(方法的定义及语法结构)

    Java的方法定义 1.方法的定义 方法是控制对象的动作行为方式与准则,在Java中方法位于类体下又有另一种含义. 普通的方法在类中称为"实例方法",因为方法的调用需要创建对象,而 ...

  2. Rsync多模块复制、排除指定文件及目录以及数据无差异复制的应用实例

    在我的博客<Rsync 数据复制软件应用>中,拉取数据访问的都是服务器端的/backup 目录,当然我们在其他目录下拉取数据.而实现这种操作就是指多模块复制. 要实现多模块复制首先需要修改 ...

  3. ART模式下基于Xposed Hook开发脱壳工具

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78092365 Dalvik模式下的Android加固技术已经很成熟了,Dalvik ...

  4. Average Score39届亚洲赛牡丹江站A题

    题意:       A班有n个人,B班有m个人,然后现在给你n-1个A班人的成绩,和m个B班人的成绩,然后题目要求求出A班剩下的没给成绩那个人的成绩范围,要求是这个人从A班转到B班后能使A,B的平均分 ...

  5. 绕过网站WAF(图片绕过)

    当我们在渗透一个网站的时候,很多时候,会遇到下面这种情况.网站装有WAF,把我们的SQL注入语句给拦截了. 这就是网站的安全狗 此时,我们的渗透会陷入僵局.到底应该如何才能让我们的语句绕过安全狗的检查 ...

  6. 复现Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437)

    靶机IP(Ubuntu):192.168.43.185 攻击IP(kali):192.168.43.37 一.docker环境搭建 打开vulhub靶机,目录定位到vulhub-master/shir ...

  7. 表单模块 layui-form

    使用 layui针对各种表单元素做了比较全面的Ui支持,在Ui渲染只要求一点.,在表单体所在父元素加上class="layui-form" 监听事件 提交按钮监听,注意需要加·la ...

  8. java.lang.NoSuchMethodError: org.springframework.util.Assert.state(ZLjava/util/function/Supplier;)V

    更多精彩见微信公众号 at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedCo ...

  9. 运维告诉我CPU飙升300%,为什么我的程序上线就奔溃了

    线上服务CPU飙升 前言 功能开发完成仅仅是项目周期中的第一步,一个完美的项目是在运行期体现的 今天我们就来看看笔者之前遇到的一个问题CPU飙升的问题. 代码层面从功能上看没有任何问题但是投入使用后却 ...

  10. 一些代码小技巧&经典代码

    请说明逻辑与(&&)在下边表达式中起到的重要作用 count != 0 && sum/count 答:该表达式使用逻辑与(&&)来确保 sum/coun ...