17611538698
webmaster@21cto.com

Swift枚举关联值的内存探究

资讯 0 2295 2020-05-17 04:03:36
<blockquote> <p>这篇文章主要给大家介绍了关于Swift枚举关联值的内存的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者使用Swift具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧</p> </blockquote> <pre> <code class="language-java">enum Season { case Spring, Summer, Autumn, Winter } let s = Season.Spring</code></pre> <p>这是枚举最基础的用法,但是在swift中,对枚举的功能进行了加强,也就是关联值。</p> <p>关联值可以将额外信息附加到 enum case中,像下面这样子。</p> <pre> <code class="language-java">enum Test { case test1(v1: Int, v2: Int, v3: Int) case test2(v1: Int, v2: Int) case test3(v1: Int) case test4 } let t = Test.test1(v1: 1, v2: 2, v3: 3) switch t { case .test1(let v1, let v2, let v3): print(v1, v2, v3) default: break } // 输出: 1 2 3</code></pre> <p>我们可以看到,在我们创建一个枚举值t的时候,设置他的选项为test1,同时可以关联3个Int类型的值,然后在switch中,我们还可以把这3个Int值取出来进行使用。</p> <p>我们今天的主要任务就是探索一下有关联值的枚举类型,再底层的内存布局是什么样子的,这些值都是怎么储存的。</p> <p>在OC中我们使用sizeOf此类方法,可以输出一个变量占用内存的大小,在swift中也有此类的工作类,那就是MemoryLayout。</p> <pre> <code class="language-java">print(MemoryLayout&amp;amp;amp;lt;Int&amp;amp;amp;gt;.size)// 实际使用内存大小 print(MemoryLayout&amp;amp;amp;lt;Int&amp;amp;amp;gt;.stride)//分配内存大小 print(MemoryLayout&amp;amp;amp;lt;Int&amp;amp;amp;gt;.alignment)//内存对其参数 // 输出 8 8 8</code></pre> <p>上面的例子是只是简单的实例MemoryLayout的用法,这个我们知道,在64位的系统中Int类型确实是占用8个字节(64位)。接下来我们就看一下枚举的内存占用情况。</p> <p>点击Xcode菜单栏中的Debug -&gt; Debug Workflow -&gt; View Memory,然后在下面红色框中输入变量的内存地址,就可以看到变量的内存使用情况。</p> <p>使用swift后,从xcode没法直接打印变量的内存地址,这里我们使用了github上的一个工具类来帮助我们输出变量的内存地址。</p> <p><img alt="" src="https://www.21cto.com/uploads/images/2020818144120842.jpg" style="width: 800px; height: 725px;" /></p> <p>准备工作完成后,我们先从最基础的枚举开始。</p> <pre> <code class="language-java">enum Season { case Spring, Summer, Autumn, Winter } print("实际占用:",MemoryLayout&amp;amp;amp;lt;Season&amp;amp;amp;gt;.size) print("分配:",MemoryLayout&amp;amp;amp;lt;Season&amp;amp;amp;gt;.stride) print("对齐参数:", MemoryLayout&amp;amp;amp;lt;Season&amp;amp;amp;gt;.alignment) var s = Season.Spring print("内存地址",Mems.ptr(ofVal: &amp;amp;amp;amp;s)) print("内存数据",Mems.memStr(ofVal: &amp;amp;amp;amp;s, alignment: .one)) s = Season.Summer print("内存数据",Mems.memStr(ofVal: &amp;amp;amp;amp;s, alignment: .one)) s = Season.Autumn print("内存数据",Mems.memStr(ofVal: &amp;amp;amp;amp;s, alignment: .one)) s = Season.Winter print("内存数据",Mems.memStr(ofVal: &amp;amp;amp;amp;s, alignment: .one))</code></pre> <p>注:Mems.memStr可以直接打印内存数据,这样我们就不用每次拿到地址再去工具中看了</p> <blockquote> <p>实际占用: 1<br /> 分配: 1<br /> 对齐参数: 1<br /> 内存地址 0x00007ffee753f0f0<br /> 内存数据 0x00<br /> 内存数据 0x01<br /> 内存数据 0x02<br /> 内存数据 0x03</p> </blockquote> <p>我们可以看到这种普通的枚举类型,只占用一个字节。而且通过我们对变量设置不同的枚举值,打印的这一个字节的数据也是不同的,其实也就是使用这一个字节通过设置不同的数值来表示不同的枚举值,这样的话其实可以至少储存0x00-0xFF共256个值。那如果超过256个case呢?其实我觉得没有必要考虑这种情况,枚举本来设计出就是为了区分有限中情况,如果太多,就像200多个,那完全可以使用Int来设置不同的值了,就没必要用枚举了,当然,如果您愿意探究一下的话也是可以的。</p> <p>接下来我们使用一个带关联值的枚举来看一下。</p> <pre> <code class="language-java">enum Test { case test1(v1: Int, v2: Int, v3: Int) case test2(v1: Int, v2: Int) case test3(v1: Int) case test4 } print("实际占用:",MemoryLayout&amp;amp;amp;lt;Test&amp;amp;amp;gt;.size) print("分配:",MemoryLayout&amp;amp;amp;lt;Test&amp;amp;amp;gt;.stride) print("对齐参数:", MemoryLayout&amp;amp;amp;lt;Test&amp;amp;amp;gt;.alignment) var t = Test.test1(v1: 1, v2: 2, v3: 3) print("内存地址",Mems.ptr(ofVal: &amp;amp;amp;amp;t)) print("内存数据",Mems.memStr(ofVal: &amp;amp;amp;amp;t, alignment: .one)) t = Test.test2(v1: 4, v2: 5) print("内存数据",Mems.memStr(ofVal: &amp;amp;amp;amp;t, alignment: .one)) t = Test.test3(v1: 6) print("内存数据",Mems.memStr(ofVal: &amp;amp;amp;amp;t, alignment: .one)) t = Test.test4 print("内存数据",Mems.memStr(ofVal: &amp;amp;amp;amp;t, alignment: .one)) </code></pre> <p>下面是输出, 为了能直观一下,我给插了几个换行</p> <blockquote> <p>实际占用: 25<br /> 分配: 32<br /> 对齐参数: 8<br /> 内存地址 0x00007ffee0afe0d8<br /> 内存数据<br /> 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x00<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 内存数据<br /> 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x01<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 内存数据<br /> 0x06 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x02<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 内存数据<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00<br /> 0x03<br /> 0x00 0x00 0x00 0x00 0x00 0x00 0x00</p> </blockquote> <p>实际占用了25个字节,我们至少可以确定,枚举的关联值是存储在枚举值的内存中的。</p> <p>但是通过这一个例子其实可能还看不出有什么规律,大家可以多用几个例子来验证,这是我就直接说结论了。</p> <p>有关联值得枚举实际占用的内存是最多关联值占用的内存+1,在我们这个Test中,test1的关联值是最多的,有3个Int类型的关联值,所以要8*3=24字节来存放关联值,但是还需要一个字节来储存(辨别)是哪一个case。</p> <p>带着这个结论我们看一下输出的结果:</p> <p>当t=.test1时,前面24个字节分配给3个Int类型关联值,分别存储了1,2,3, 第25个字节是0。</p> <p>当t=.test2时,前面24个字节还是留给关联值的,但是test2只有两个关联值,所以使用了前面16个字节分配给他的关联值,此时17到24这8字节就空置,第25个字节是1。</p> <p>...</p> <p>最后当t = test4 , 没有关联值,所以前面的字节都是0, 只有第25个字节是3</p> <p>以此类推...</p> <p>第25个字节其实完全可以看成一个辨识位,或者说第25个字节就是枚举的本质,通过不同值来区分不同case,只是因为有了关联值,所以开辟了更多的空间来存储。</p> <p>&nbsp;</p> <p>&nbsp;</p> <blockquote> <p>作者:七九推</p> </blockquote>

评论