張小張 發表於 2023-6-19 11:46:15

Swift可选值优化示例详解

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>nil&nbsp;的语义</li><li>nil&nbsp;在内存中的表示</li><li>nil&nbsp;的优化</li><ul class="second_class_ul"><li>Bool</li><li>String</li><li>Class</li><li>Enum</li></ul><li>结语</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>nil&nbsp;的语义</h2>
<p>在 Objective-C 中,<code>nil</code>&nbsp;表示空对象,它本质是一个指向&nbsp;<strong>0x00000000</strong>&nbsp;的指针。但对于非指针的值类型,OC 中是无法表示_没有值_这个概念的,比如&nbsp;<strong>NSInteger</strong>,它可以是 0,也可以是其他任何值,但就是不存在_没有值_。</p>
<p>Swift 作为一种强类型的语言,它从一开始就引入了_没有值_这个概念,虽然还是用&nbsp;<code>nil</code>&nbsp;关键字,但实际语义上有所不同。比如&nbsp;<strong>Int?</strong>,它可以是&nbsp;<code>nil</code>,也可以是&nbsp;<strong>0</strong>,<strong>0</strong>&nbsp;是一个具体的值,而&nbsp;<code>nil</code>&nbsp;不是。然而,计算机作为一个二进制的机器,它内存中保存的非 0 即 1,如何表示_没有值_呢?换句话说,<code>nil</code>&nbsp;在内存中究竟是什么?我们可以通过简单的代码找出它在内存中的真相。</p>
<p class="maodian"></p><h2>nil&nbsp;在内存中的表示</h2>
<div class="jb51code"><pre class="brush:cpp;">/// 以下方法取 value 的地址,并从地址处向后取它在内存中的大小 size 个字节,转为对应的数组
func bytes&lt;T&gt;(of value: T) -&gt; {
    var value = value
    let size = MemoryLayout&lt;T&gt;.size
    return withUnsafePointer(to: &amp;value, {
      $0.withMemoryRebound(
            to: UInt8.self,
            capacity: size,
            {
                Array(UnsafeBufferPointer(
                  start: $0, count: size))
            })
    })
}
var int: Int? = 0
bytes(of: int)    //
int = nil
bytes(of: int)    // </pre></div>
<p>从上面我们可以得知,可选的&nbsp;<strong>Int?</strong>&nbsp;类型比普通&nbsp;<strong>Int</strong>&nbsp;类型多占一个字节,用来表示是不是&nbsp;<strong>没有值</strong>。如果这样的话,在&nbsp;<code>struct</code>&nbsp;或&nbsp;<code>class</code>&nbsp;中用可选类型岂不是会浪费较多内存空间?因为内存对齐的缘故,多一个字节,就要浪费剩下的 7 字节,比如:</p>
<div class="jb51code"><pre class="brush:cpp;">struct N {
    var b: Int? = 2
    var a: Int? = 3
}
var n = N()
bytes(of: n)    // [2, 0, 0, 0, 0, 0, 0, 0, 0, 76, 68, 3, 1, 0, 0, 0,
                //3, 0, 0, 0, 0, 0, 0, 0, 0]</pre></div>
<p>以上原本可以用 16 字节表示的结构体,实际上占了 25 字节(考虑结尾处内存对齐,其实占了 32 字节)。我们在实际开发中,可能会在&nbsp;<code>class</code>&nbsp;中声明大量的可选字段,如果都这样的话,那内存使用率也太低了,有优化手段吗?</p>
<p>答案是有的,而且&nbsp;<strong>Swift</strong>&nbsp;编译器已经默默帮我们做了。</p>
<p class="maodian"></p><h2>nil&nbsp;的优化</h2>
<p class="maodian"></p><h3>Bool</h3>
<p>Bool 类型理论上只用 0 1 两个值,一个 bit 即可,但它却占了一整个 byte ,剩下的几个 bit 是可以用来区分是否有值的。</p>
<div class="jb51code"><pre class="brush:cpp;">var b: Bool? = false
bytes(of: b) //
b = true
bytes(of: b) //
b = nil
bytes(of: b) // </pre></div>
<p>从以上结果得知,<strong>Swift</strong>&nbsp;用 2 表示&nbsp;<strong>Bool?</strong>&nbsp;的_没有值_,所以没有内存浪费。这样也使得&nbsp;<strong>Bool?</strong>&nbsp;不再是两态的开关,而是一个三态的开关。于是经常在代码中看到看起来比较蠢的写法:</p>
<div class="jb51code"><pre class="brush:cpp;">var value: Bool?
if value == true {
}</pre></div>
<p>因为一般来说是不建议 Bool 值与 true 判断等的,它本身已经是 Bool 了。而在 Swift 中又用起来是那么自然&hellip;&hellip;</p>
<p class="maodian"></p><h3>String</h3>
<p>String 类型不同于 Int 这种&mdash;&mdash;0 也是合法值,String 的内存值为 0 是可以表示_没有值_的,所以它也没有内存浪费</p>
<p>String 在&nbsp;<strong>Swift</strong>&nbsp;中是一个结构体,无论字符串多长,String 变量本身只占 16 字节,短<!--cke_bookmark_7642S-->的字符串通过类似 OC 中&nbsp;://developer.apple.com/videos/play/wwdc2020/10163/" rel="external nofollow"   target="_blank">Tagged Pointer&nbsp;的技术直接存在指针中,长的字符串需要指向堆内存地址。</p>
<p class="maodian"></p><h3>Class</h3>
<p>Class 类型同 OC 中的一样,是指针类型,空指针可以表示_没有值_,没有内存浪费。</p>
<div class="jb51code"><pre class="brush:cpp;">class MyObject {
    var b: Int? = 2
    var a: Int? = 3
}
var o: MyObject? = .init()
bytes(of: o)//
o = nil
bytes(of: o)// </pre></div>
<p>无论&nbsp;<strong>Class</strong>&nbsp;中有多少成员变量,Class 变量本身(即指向它的指针)只占 8 字节(64位系统中)。</p>
<p class="maodian"></p><h3>Enum</h3>
<p>枚举类型一般是有限的,最终总可以找到一个不在枚举范围内的值表示 _没有值_,也可以没有内存浪费。</p>
<div class="jb51code"><pre class="brush:cpp;">enum Edge {
    case left
    case right
    case top
    case bottom
}
var e: Edge? = .left
bytes(of: e)//
e = .bottom
bytes(of: e)//
e = nil
bytes(of: e)// ,用越界值表示 nil,没有值</pre></div>
<p>当然并不是所有&nbsp;<strong>Enum</strong>&nbsp;类型都能这样,带关联值的就可能不行。</p>
<p class="maodian"></p><h2>结语</h2>
<p>综上所述,<strong>Swift</strong>&nbsp;编译器会尽可能地优化可选值的内存占用,日常开发并不需要太多关心,但是部分情况仍要求开发者尽量少使用可选值,如结构体中连续几个可选&nbsp;<strong>Int</strong>&nbsp;的情况,如果 0 也能满足代码逻辑,就使用非可选值,并用 0 初始化它吧!</p>
<div class="jb51code"><pre class="brush:cpp;">// 浪费的内存比较可观
struct My {
   var a: Int?
   var b: Int?
   var c: Int?
   var d: Int?
}</pre></div>
<p>以上就是Swift可选值优化示例详解的详细内容,更多关于Swift可选值优化的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>swift中可选值?和!使用的方法示例</li><li>Swift中非可选的可选值类型处理方法详解</li><li>Swift Error重构优化详解</li><li>Swift使用SnapKit模仿Kingfisher第三方扩展优化</li><li>SwiftUI List在MacOS中的性能优化示例</li><li>Swift中图片资源使用流程的优化方法详解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Swift可选值优化示例详解