Swift属性

文章搬运来源:https://juejin.cn/post/6921143080963801101
作者:Bel李玉(如有侵权,联系作者立马删除)

对iOS开发感兴趣,可以看一下作者的iOS交流群:812157648,大家可以在里面吹水、交流相关方面的知识,群里还有我整理的有关于面试的一些资料,欢迎大家加群,大家一起开车

Swift属性

存储属性

我们先创建一个LYPerson对象,并声明两个存储属性

class LYPerson {
    var age : Int = 19
    var height: Int = 188
}

let p = LYPerson()
复制代码

我们通过lldb指令来分析p的属性分布 首先,我们先获取对象p的内存地址

(lldb) po p
<LYPerson: 0x10062fcc0>
复制代码

在这里 0x10062fcc0就是我们 实例对象p的HeapObject的地址。 我们来读取 HeapObject的内存分布

(lldb) x/8gx 0x10062fcc0
0x10062fcc0: 0x0000000100008170 0x0000000200000003
0x10062fcd0: 0x0000000000000013 0x0000000000000014 // 19 , 20
0x10062fce0: 0x0000000000000000 0x0000000000000000
0x10062fcf0: 0x0000000000000006 0x0000000000000000
复制代码

我们可以得出其内存分布如下

image

存储属性是会占用当前实例对象的内存。

计算属性

我们先来看如下代码

class Square {
    var width: Double = 8.0
    var area: Double {
        get {
            return width * width
        }

        set (newValue){
            width = sqrt(newValue)
        }
    }
}

let s = Square()

print(s.area)
复制代码

Square实例对象占用的内存空间大小为24,

class_getInstanceSize(Square.self) // 24
复制代码

我们知道,swift对象默认的大小为16,在Square类中,width属性为Int类型为8字节,对于计算属性来说,它是不占用实例对象内存空间的。

计算属性既然不占用实例对象的内存空间,那计算属性存放在哪里呢? 我们通过它的SIL文件来看下

swiftc -emit-sil main.swift > ./main.sil && open main.sil
复制代码
class Square {
  @_hasStorage @_hasInitialValue var width: Double { get set }
  var area: Double { get set }
  @objc deinit
  init()
}
复制代码

对于 计算属性 area 来说,它的本质是 getset方法,存放在元数据(Metadata)中 ,不占用实例对象的内存空间。

属性观察者 willset didset

属性观察者可以监听属性值改变时候的值变化

class Square {
    var area: Double = 0.0 {
        willSet {
            print("newValue -- \(newValue) , value --\(area)")
        }

        didSet {
            print("oldValue -- \(oldValue), value --\(area)")
        }
    }
}

let s = Square()
s.area = 18

newValue -- 18.0 , value --0.0
oldValue -- 0.0, value --18.0

复制代码

延迟存储属性 lazy

初始值

使用lazy修饰的变量,必须有一个默认的初始值,否则,编译会报Lazy properties must have an initializer错。

image

赋值时机

接下来,我们来观察 lazy 属性的值变化 image

(lldb) po p
<Person: 0x104004240> // 1
(lldb) x/4gx 0x104004240 // 2
0x104004240: 0x0000000100008160 0x0000000200000003
0x104004250: 0x0000000000000000 0x0000000000000000
(lldb) x/4gx 0x104004240 // 3
0x104004240: 0x0000000100008160 0x0000000200000003
0x104004250: 0x0000000000004c4c 0xe200000000000000
复制代码
  • 1,变量p的内存地址
  • 2,查看name属性未赋值时,p的内存空间。name 值为0000。
  • 3,当第一次获取name属性后,p的内存空间,可以看到 name的值为 4C4CLL

从上面我们可以看出,延迟存储在第一次访问的时候才被赋值

对对象大小的影响

class Person {
     lazy var age: Int = 0
}

class Man {
    var age: Int = 0
}

var p = Person()
print(class_getInstanceSize(Person.self)) // 32

var m = Man()
print(class_getInstanceSize(Man.self)) // 24
复制代码

我们可以看出 Man对象占用 24 字节 ,Person对象占用 32 字节,为什么使用 lazy修饰 Int变量后,对象变大了呢? 我们先使用 swiftc将其转化为SIL文件:

swiftc -emit-sil main.swift | xcrun swift-demangle > ./main.sil && open main.sil
复制代码
class Person {
  lazy var age: Int { get set }
  // 1
  @_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set }
  @objc deinit
  init()
}

class Man {
// 2
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @objc deinit
  init()
}
复制代码
  • lazy修饰Int之后,就变成了 Int?类型,变成了可选型,从 1 和 2 的对比中,我们就可以看出。

在 Person 对象age属性的setter方法中

// Person.age.setter
sil hidden @main.Person.age.setter : Swift.Int : $@convention(method) (Int, @guaranteed Person) -> () {
// %0 "value"                                     // users: %4, %2
// %1 "self"                                      // users: %5, %3
bb0(%0 : $Int, %1 : $Person):
  debug_value %0 : $Int, let, name "value", argno 1 // id: %2
  debug_value %1 : $Person, let, name "self", argno 2 // id: %3
  %4 = enum $Optional<Int>, #Optional.some!enumelt, %0 : $Int // user: %7
  %5 = ref_element_addr %1 : $Person, #Person.$__lazy_storage_$_age // user: %6
  %6 = begin_access [modify] [dynamic] %5 : $*Optional<Int> // users: %7, %8
  store %4 to %6 : $*Optional<Int>                // id: %7
  end_access %6 : $*Optional<Int>                 // id: %8
  %9 = tuple ()                                   // user: %10
  return %9 : $()                                 // id: %10
} 
复制代码

我们也可以看到是将 Optional<Int>类型的数据赋值给age属性。 在 age 属性的 getter方法中

// Person.age.getter
sil hidden [lazy_getter] [noinline] @main.Person.age.getter : Swift.Int : $@convention(method) (@guaranteed Person) -> Int {
// %0 "self"                                      // users: %14, %2, %1
bb0(%0 : $Person):
  debug_value %0 : $Person, let, name "self", argno 1 // id: %1
  %2 = ref_element_addr %0 : $Person, #Person.$__lazy_storage_$_age // user: %3
  %3 = begin_access [read] [dynamic] %2 : $*Optional<Int> // users: %4, %5
  %4 = load %3 : $*Optional<Int>                  // user: %6
  end_access %3 : $*Optional<Int>                 // id: %5
  switch_enum %4 : $Optional<Int>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %6
复制代码

通过 switch_enum,来获取属性值。

Optional<Int>的大小为多少呢?

print(MemoryLayout<Optional<Int>>.size) // 9: 实际大小
print(MemoryLayout<Optional<Int>>.stride) // 16:字节对齐,实际占用的空间大小
复制代码

所以 age:Int 属性在使用 lazy 修饰后 变为了 Int?,对象大小也随之发生了改变。在 getter方法中,由于没有加锁,在多线程同时访问时,并不能保证线程安全。

小结:

  • 延迟存储必须有一个默认的初始值。
  • 延迟存储在第一次访问的时候才被赋值。
  • 延迟存储属性对实例对象的大小有影响。
  • 延迟存储属性不能保证线程安全。

类型属性

static关键字

使用static关键字修饰的属性是类型属性

class Person {
     static var age: Int = 0
}

let age = Person.age

print(age)
复制代码

我们先将它转化为 SIL

class Person {
  @_hasStorage @_hasInitialValue static var age: Int { get set }
  @objc deinit
  init()
}
// static Person.age
sil_global hidden @static main.Person.age : Swift.Int : $Int // 1
复制代码
  • 1,使用 static修饰后,变成了一个全局属性。
// Person.age.unsafeMutableAddressor
sil hidden [global_init] @main.Person.age.unsafeMutableAddressor : Swift.Int : $@convention(thin) () -> Builtin.RawPointer {
bb0:
  %0 = global_addr @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0 : $*Builtin.Word // user: %1
  %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer // user: %3
  // function_ref globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
  %2 = function_ref @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0 : $@convention(c) () -> () // user: %3
  // 1
  %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $() 
  %4 = global_addr @static main.Person.age : Swift.Int : $*Int // user: %5
  %5 = address_to_pointer %4 : $*Int to $Builtin.RawPointer // user: %6
  return %5 : $Builtin.RawPointer                 // id: %6
}
复制代码
  • 1,在 age属性赋值时,使用了 builtin "once",在 源码当中,实际调用了 swift_once函数,其源码如下
void swift::swift_once(swift_once_t *predicate, void (*fn)(void *),
                       void *context) {
#if defined(__APPLE__)
  dispatch_once_f(predicate, context, fn);
#elif defined(__CYGWIN__)
  _swift_once_f(predicate, context, fn);
#else
  std::call_once(*predicate, [fn, context]() { fn(context); });
#endif
}
复制代码
  • swift_once 本质上调用了 GCD 的 dispatch_once_f函数,保证变量只被初始化一次,并且是线程安全的。

swift单例

static关键字是线程安全的,并只初始化一次,那我们就可以使用static来创建单例,以下就是swift中单例的创建方法了。

class Animal {
    static let sharedInstance = Animal()
    private init() {

    }
}

let a = Animal.sharedInstance
复制代码

总结

存储属性会占用当前实例对象的内存空间。 计算属性不会占用当前实例对象的内存空间,其存放在元数据中。 属性观察者用来监测属性的值变化。 延迟存储属性lazy对实例对象的大小有影响,并不能保证线程安全。 类型属性static是线程安全的,只被初始化一次。

### Shohag Loves Greatest Common Divisor (GCD) In both programming and mathematics contexts, the concept of the greatest common divisor (GCD) plays a crucial role. The GCD is defined as the largest positive integer that divides each of the integers without leaving a remainder. #### Mathematical Definition Mathematically speaking, given two non-zero integers \(a\) and \(b\), their greatest common divisor can be denoted by \(\gcd(a, b)\). This value represents the highest number which evenly divides both numbers[^1]. #### Recursive Implementation in C Language A recursive approach to computing the GCD involves repeatedly applying Euclid's algorithm until reaching a base case where one parameter becomes zero: ```c int gcd(int m, int n) { if (n == 0) return m; else return gcd(n, m % n); } ``` This implementation efficiently reduces the problem size at every step through modulo operations while ensuring correctness via recursion. An alternative version ensures larger values are always passed first before performing division checks: ```c int gcd(int n, int m) { int temp; if (n < m) { // Ensure n >= m temp = n; n = m; m = temp; } if (n % m == 0) return m; else return gcd(m, n % m); } ``` Such adjustments improve performance when dealing with specific input ranges but maintain fundamental logic based on modular arithmetic principles[^2]. #### Application Example: String Division Problem An interesting application appears within LeetCode challenge **1071**, concerning strings rather than numeric types directly. Here, determining whether string `s` consists entirely of repeated instances of another substring `t`, effectively translates into checking divisibility properties between lengths |s| and |t|. If such conditions hold true, then further analysis applies similar concepts seen earlier regarding numerical factors and multiples[^3]: Given this background information about how GCD operates across different domains—from basic mathematical theory down to practical coding exercises—one gains deeper insight into its versatile utility beyond mere factorization tasks alone.
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值