<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ting</title>
    <description>The latest articles on DEV Community by Ting (@yo1995).</description>
    <link>https://dev.to/yo1995</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1028343%2Fdf1c585b-54b7-4d72-88d2-cb5bbbdd4955.png</url>
      <title>DEV Community: Ting</title>
      <link>https://dev.to/yo1995</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yo1995"/>
    <language>en</language>
    <item>
      <title>ARGeoTrackingConfiguration 苹果街景定位支持地区</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Wed, 19 Nov 2025 18:05:35 +0000</pubDate>
      <link>https://dev.to/yo1995/argeotrackingconfiguration-ping-guo-jie-jing-ding-wei-zhi-chi-di-qu-jjp</link>
      <guid>https://dev.to/yo1995/argeotrackingconfiguration-ping-guo-jie-jing-ding-wei-zhi-chi-di-qu-jjp</guid>
      <description>&lt;p&gt;SEO: Geotracking, coverage, Visual Positioning System (VPS), ARCore, Geospatial API, ARKit, AR, 街景定位, 视觉定位&lt;/p&gt;

&lt;p&gt;太长不看：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;调用官方 &lt;code&gt;checkAvailability&lt;/code&gt; 方法&lt;/li&gt;
&lt;li&gt;通过苹果地图 Look Around 来确定街景地图覆盖地区&lt;/li&gt;
&lt;li&gt;查询苹果图像收集网站列表&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Apple 在 ARKit 4 时期推出了基于街景的 AR 定位服务，通过综合摄像头采集的图像、设备 GPS、以及所在地的地图信息，来更加精确地确定当前所处的位置。因为街景地图都经过了“地理配准”——即所有街景图可以被认为其对应的地理位置坐标都是准确的，因此，通过比较摄像头画面与街景地图，即可通过视觉算法匹配到摄像头所处的位置坐标。&lt;/p&gt;

&lt;p&gt;在使用这项服务时，一定会用到 &lt;code&gt;ARGeoTrackingConfiguration&lt;/code&gt; 这个类。其中，它涉及到判断设备当前所处位置是否支持街景定位服务。在这项功能刚推出的2020年左右，ARGeoTrackingConfiguration 文档的 &lt;a href="https://developer.apple.com/documentation/arkit/argeotrackingconfiguration#Supported-areas-and-cities" rel="noopener noreferrer"&gt;Supported areas and cities&lt;/a&gt; 里面列出了当时该服务支持的区域。当时，几乎只有美国和世界几个主要城市的核心区域支持该项服务，如果没记错的话，包括纽约、旧金山、洛杉矶、芝加哥、巴黎、新加坡等地。随着时间的推移，支持的地点数量逐渐变多。直到2023年末的一天，我在查看文档时突然发现，支持城市列表不见了！&lt;/p&gt;

&lt;p&gt;直到今天，许多有关的文档仍指向这个文档的列表&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For a list of supported areas and cities, see &lt;a href="https://developer.apple.com/documentation/arkit/argeotrackingconfiguration" rel="noopener noreferrer"&gt;ARGeoTrackingConfiguration&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;然而，在最初的城市列表无处寻觅时，应该如何确定某个地区是否支持街景定位呢？&lt;/p&gt;

&lt;p&gt;其实，这个列表逐渐与“苹果图像收集”项目合为一体，其网址在：&lt;a href="https://maps.apple.com/imagecollection" rel="noopener noreferrer"&gt;https://maps.apple.com/imagecollection&lt;/a&gt; 。可以看到，经过几年的发展，苹果地图逐渐羽翼丰满，几乎所有美国主要城市都有街景地图支持了。&lt;/p&gt;

&lt;p&gt;另外，文档里提供了两个 &lt;code&gt;checkAvailability&lt;/code&gt; 方法，用来确定当前设备位置/某一坐标是否支持街景定位。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;checkAvailability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;completionHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)?)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;最后，苹果地图如今也支持街景地图功能了。点击左下角望远镜标识的“Look Around”功能，就能查看附近的街景地图。理论上，支持街景地图的街道也应支持街景定位。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2t2wz37o1g2nn73xbjou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2t2wz37o1g2nn73xbjou.png" alt=" " width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;你可以下载并运行&lt;a href="https://github.com/yo1995/Daily_Swift_Tasks/tree/master/AppleGeotrackingCheckAvailability" rel="noopener noreferrer"&gt;这个示例app&lt;/a&gt;，来了解 geotracking 在某个地点是否支持。&lt;/p&gt;

</description>
      <category>api</category>
      <category>ios</category>
      <category>resources</category>
    </item>
    <item>
      <title>Swift 静默弃用警告</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Thu, 26 Jun 2025 22:11:40 +0000</pubDate>
      <link>https://dev.to/yo1995/swift-jing-mo-qi-yong-jing-gao-2ael</link>
      <guid>https://dev.to/yo1995/swift-jing-mo-qi-yong-jing-gao-2ael</guid>
      <description>&lt;p&gt;(中文找不到合适的表达，实际上想说的是 suppress/silence deprecation warning in Swift，压制弃用警告？）&lt;/p&gt;

&lt;h2&gt;
  
  
  问题
&lt;/h2&gt;

&lt;p&gt;在开发 SDK 时，会遇到一种和弃用警告相关的问题：当我们把一个类型、属性、方法标为弃用后，如何使其&lt;strong&gt;在用户使用的时候产生警告，而在我们开发过程中忽略警告呢？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;因为在开发过程中，我们始终追求无警告、无错误，所以不能因为自己添加了弃用警告而搬起石头砸自己的脚。在其他语言中，时常有一些宏来告诉编译器忽略这些警告，比如&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#pragma clang diagnostic ignored
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;然而，直到2025年，Swift 6.2 仍旧没有提供类似的工具。因此，Swift 开发者只能另辟蹊径。借 Quinn “The Eskimo!” 的&lt;a href="https://forums.swift.org/t/suppressing-deprecated-warnings/53970/6" rel="noopener noreferrer"&gt;发言&lt;/a&gt;：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Needless to say, the second point (必须用奇技淫巧绕过) does not make me happy (r. 31131633)-:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  示例
&lt;/h2&gt;

&lt;p&gt;以下我分别以几种弃用情况来举例：弃用方法、弃用枚举值、弃用属性、弃用类型。&lt;/p&gt;

&lt;h3&gt;
  
  
  弃用方法
&lt;/h3&gt;

&lt;p&gt;这种情形相对来说是最简单直接的。假如我们类型里有一个方法需要弃用：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Album&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// - Attention: Deprecated at 1.0.&lt;/span&gt;
    &lt;span class="kd"&gt;@available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"getImage() is deprecated and will be retired"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getImage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;在标注这个方法为弃用后，如果我们调用这个方法：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;album&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getImage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8xtn1ewbjus7mq7kn3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8xtn1ewbjus7mq7kn3r.png" alt="方法弃用警告" width="800" height="43"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;编译器会⚠️警告该方法已弃用。这个警告对于用户来说是有用的——他们可以替换掉弃用的方法；对于我们自己而言，我们不想看到 SDK 里面用这个方法时产生警告，所以需要变通一下。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="kt"&gt;GetLatestImage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getImage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;GetLatestImage&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;添加这个协议以后，在我们自己开发 SDK 时如下调用这个方法：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;album&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// let image = album.getImage()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="kt"&gt;GetLatestImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getImage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;弃用警告就不会再出现了。&lt;/p&gt;

&lt;p&gt;需要注意的是，在将 &lt;code&gt;album&lt;/code&gt; 实例转换为协议的类型时，需要添加 &lt;code&gt;any&lt;/code&gt; 关键字。尽管目前版本的 Swift 不添加 &lt;code&gt;any&lt;/code&gt; 不会强制报错，但自从引入了 &lt;code&gt;some/any&lt;/code&gt; 关键字以后，Swift 对于如何使用泛型有了更加清晰严格的要求，所以为了未来不会报错，加上这个关键字比较安全。&lt;/p&gt;

&lt;p&gt;我猜测，用协议来静默警告的原理，就是把编译时的弃用警告检测，延迟到了运行时。编译时无法确定运行时符合协议的实例的具体类型，自然就不会产生警告。可以看出，这种变通方法会浪费一些性能，也有点 code smell。但是在 Swift 没有原生的编译时解决方法的当下，也是不得已而为之。&lt;/p&gt;

&lt;h3&gt;
  
  
  弃用枚举值
&lt;/h3&gt;

&lt;p&gt;假如我们有如下的 &lt;code&gt;MyError&lt;/code&gt; 错误枚举类型：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;MyError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// - Attention: Deprecated at 1.0.&lt;/span&gt;
    &lt;span class="kd"&gt;@available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"missingKey is deprecated and will be retired"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;missingKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;当 switch-case 到这个枚举值时，便会产生警告：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;MyError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fromErrorCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;MyError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;missingKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"The API key is missing."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fatalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unexpected error code"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rf4paqawchre7ud4nkf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rf4paqawchre7ud4nkf.png" alt="枚举弃用警告" width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;类似地，我们也可以用协议来绕过这个警告。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;MyError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fromErrorCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;MyError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// The sole purpose of this protocol is to silence a deprecation warning due to the enum case being deprecated.&lt;/span&gt;
        &lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="kt"&gt;MyErrorProviding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;missingKeyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;MyError&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MyErrorProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MyErrorProviding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;@available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"missingKey is deprecated and will be retired"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;missingKeyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;MyError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;missingKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;// return .missingKey(details: "The API key is missing.")&lt;/span&gt;
            &lt;span class="nf"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MyErrorProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="kt"&gt;MyErrorProviding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;missingKeyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"The API key is missing."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;fatalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unexpected error code"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这样，在使用枚举值时，就不会产生警告了。&lt;/p&gt;

&lt;h3&gt;
  
  
  弃用属性
&lt;/h3&gt;

&lt;p&gt;弃用属性的情况很类似弃用方法。但是因为属性本身要被初始化，所以弃用以后，会影响好几个地方。比如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Album&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// - Attention: Deprecated at 1.0.&lt;/span&gt;
    &lt;span class="kd"&gt;@available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"image is deprecated and will be retired"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4eh6r1va5zcs6ap8mwqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4eh6r1va5zcs6ap8mwqx.png" alt="属性弃用警告" width="800" height="68"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这种情况下，我们需要一些额外的处理，来保证之前被弃用的属性在最终被移除之前仍然可用，但是不会给出警告。话不多说，直接上代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Album&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// - Attention: Deprecated at 1.0.&lt;/span&gt;
    &lt;span class="kd"&gt;@available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"image is deprecated and will be retired"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_image&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;/// This is an internal property to use so we don't use the deprecated property.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;_image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="kt"&gt;GetLatestImage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;GetLatestImage&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;// 在调用的地方&lt;/span&gt;

&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onAppear&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"photo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;album&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// let albumImage = album.image  // 有弃用警告&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;albumImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="kt"&gt;GetLatestImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;  &lt;span class="c1"&gt;// 无弃用警告&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;通过添加一个隐藏的属性，我们便可以在保留原先被弃用的属性的语义的前提下，压制警告的产生。&lt;/p&gt;

&lt;h3&gt;
  
  
  弃用类型
&lt;/h3&gt;

&lt;p&gt;前面三种情况都是只弃用某个类型中的一部分——方法、枚举值、属性。有时候，一整个类型都得被弃用。可以想像这样一种情况：写了一个程序兼容不同的汽车品牌，结果 Apple Car 倒闭了，所有相关的方法和类型都要弃用 🥲：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 👋🍎🚘&lt;/span&gt;

&lt;span class="c1"&gt;/// - Attention: Deprecated at 1.0.&lt;/span&gt;
&lt;span class="kd"&gt;@available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"AppleCar is no longer relevant and will be retired"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AppleCar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// The unique identifier of the car.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;
    &lt;span class="c1"&gt;/// The model image of the car.&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8in4208rqxb5m0lnyqlb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8in4208rqxb5m0lnyqlb.png" alt="类型弃用警告" width="800" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这种情况下，问题和之前就不太一样了。因为类型可以被当作参数传来传去，也可以成为其他类型里属性的类型，所以所有用到弃用类型的地方都会产生警告。比如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"AppleCar is no longer relevant and will be retired"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;AppleCar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;XiaomiCar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;FutureGarage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;appleCars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AppleCar&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;xiaomiCars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;XiaomiCar&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;appleCars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AppleCar&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;xiaomiCars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;XiaomiCar&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appleCars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;appleCars&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xiaomiCars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xiaomiCars&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmkrmw8k5vd9fj9xq995q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmkrmw8k5vd9fj9xq995q.png" alt="类型弃用后会多处报警" width="800" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;此时，有两种选择：一是使用类似前面所说的方法，将该弃用类型里的所有属性、方法全部弃用，并用协议的变通方法压制警告信息；二是使用基础类型来搭建一个替代品，从而不再使用被弃用的类型。这里展示一下第二种思路：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;FutureGarage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"AppleCar is no longer relevant and will be retired"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;appleCars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AppleCar&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;rawAppleCars&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AppleCar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// 用构成被弃用类型的基础类型，来计算出弃用类型的值&lt;/span&gt;
    &lt;span class="c1"&gt;// 如果复杂的话，可以新定义隐藏的内部类型作为这个用途&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;rawAppleCars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;xiaomiCars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;XiaomiCar&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rawAppleCars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;xiaomiCars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;XiaomiCar&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawAppleCars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rawAppleCars&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xiaomiCars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xiaomiCars&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这样，所有的需要静默弃用警告的情形，就都能解决了。&lt;/p&gt;

&lt;h2&gt;
  
  
  结语
&lt;/h2&gt;

&lt;p&gt;Swift 6.1 引入了 &lt;a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0443-warning-control-flags.md" rel="noopener noreferrer"&gt;SE-0443&lt;/a&gt; 来实现更精细化的静默警告信息的编译参数。然而，Swift 暂时还做不到按照代码块来压制警告。因此，2025年的当下，我们仍旧不得不用“奇技淫巧”来去除那些恼人的警告。&lt;/p&gt;

&lt;p&gt;好在，在 SDK 层面压制警告，并不会影响到用户在需要时得到这些弃用警告的消息。&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>programming</category>
    </item>
    <item>
      <title>Xcode 版本与部署目标版本？</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Tue, 17 Jun 2025 22:20:21 +0000</pubDate>
      <link>https://dev.to/yo1995/xcode-ban-ben-yu-bu-shu-mu-biao-ban-ben--2om2</link>
      <guid>https://dev.to/yo1995/xcode-ban-ben-yu-bu-shu-mu-biao-ban-ben--2om2</guid>
      <description>&lt;p&gt;在发布我们的 SDK 时，一个时常需要考虑的问题：我们的新版本 SDK 应该支持哪些“版本”？&lt;/p&gt;

&lt;p&gt;这里的&lt;strong&gt;“版本”&lt;/strong&gt;指代很多的东西，比如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;哪些型号的硬件，如 Intel vs Apple M 或者有没有 LiDAR 和 GPS 的设备&lt;/li&gt;
&lt;li&gt;最低的可以用于开发的 macOS 版本&lt;/li&gt;
&lt;li&gt;最低的可部署的 iOS 版本&lt;/li&gt;
&lt;li&gt;最低可用于开发的 Swift 编译器以及 Swift API 对应的版本，如 4.3，5.0，6.0 等等&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;作为苹果开发者很幸运：苹果公司和 macOS 已经尽量流线化整条产品线，作为苹果开发者毋需过多关注产品的兼容性——想想过去的 Windows 开发者要一直后向兼容到 Windows95，或者 Android 开发者不仅要兼容到 Android 4.0 还要在各种奇形怪状、性能高低的设备上测试，就庆幸自己手上的活儿已经足够简单；但发布时需要考虑的版本矩阵、排列组合，倒也并非一目了然。&lt;/p&gt;

&lt;p&gt;苹果官方文档在这个页面上列出了对应的版本匹配，以方便专业开发者更好地厘清版本号的关联：&lt;a href="https://developer.apple.com/support/xcode/" rel="noopener noreferrer"&gt;https://developer.apple.com/support/xcode/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;对于我们 SDK 而言，大体上的策略是每年 iOS 升级为版本 n，则兼容 n, n - 1, n - 2 三个版本。而 Xcode 基本目前也是一年一个大版本号，我们也就秉承着“新的 major 版本 Xcode n.0 发布后的我们 SDK 的第一次更新，升级至要求当前 Xcode n.0 大版本”这样的策略。&lt;/p&gt;

&lt;p&gt;这几乎已经足够简单明了，但还是有个小的不足——&lt;/p&gt;

&lt;p&gt;由于 Xcode 每年发布一个 major 大版本后 (Xcode 16.0)，通常有四到五次 minor 小版本的更新 (Xcode 16.1 - Xcode 16.4)，这就要求我们随时跟进 Xcode 更新，确保新的工具链依旧能开发、编译 SDK。同时，还要确保支持的最低版本 Xcode，不会因为 SDK 误用新引入的功能，而无法依赖引用它。因此，团队里几乎每一个开发者，都要安装至少三个版本的 Xcode：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimum requirement version, e.g., Xcode 16.0 (16A242d)&lt;/li&gt;
&lt;li&gt;Current released version, e.g. Xcode 16.3 (16E140)&lt;/li&gt;
&lt;li&gt;Next beta version, e.g., Xcode 16.4 Beta (16F1t) (Before WWDC) or Xcode 26.0 Beta (17A5241e) (after WWDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有时，因为切换 Xcode 版本失误，会有不兼容的 API 符号溜进代码里面。好在有重重把关，总有人能在发布前测试发现这样的问题，确保发布的 SDK 不会在某些我们号称支持的版本上不能用。&lt;/p&gt;

&lt;p&gt;然而，还是有点儿后怕——如果版本号的问题从 SDE 和 DevOps 眼皮底下都溜过去，最终被用户碰到，也算事故了。除了给每一台部署和测试的机器都装上三个版本的 Xcode，留一堆各种版本 iOS 的测试机以外，还能有什么更好更灵活的办法，来质检版本号呢？🤔&lt;/p&gt;

</description>
      <category>swift</category>
      <category>devops</category>
      <category>ios</category>
    </item>
    <item>
      <title>Xcode Build Phase 文件类型谜团</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Tue, 10 Jun 2025 21:48:15 +0000</pubDate>
      <link>https://dev.to/yo1995/xcode-build-phase-wen-jian-lei-xing-mi-tuan-o03</link>
      <guid>https://dev.to/yo1995/xcode-build-phase-wen-jian-lei-xing-mi-tuan-o03</guid>
      <description>&lt;p&gt;如果在构建编译过程中，引用了不存在的文件， Xcode 会给出如下错误：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build input file cannot be found: '{path to some file}'. Did you forget to declare this file as an output of a script phase or custom build rule which produces it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;第一次遇到这个问题是在 Xcode 10 升级， Legacy Build System 升级为 Modern Build System 后，因为编译过程并行化，导致在前序的 Build Phase 步骤中下载的文件，无法被后面的步骤检测到。表现出来的错误的情形是：第一次构建时 Xcode 会报错。再按一次 Build 按钮，编译顺利通过。&lt;/p&gt;

&lt;h2&gt;
  
  
  我们的情况
&lt;/h2&gt;

&lt;p&gt;在编译项目过程中，我们需要用到一些从服务器下载下来的文件。随着项目变大，需要下载的文件也逐渐变多。没有选择直接放在项目仓库里一是因为这些文件比较大，直接存在 GitHub 上浪费空间，二是因为二进制文件不适合版本管理。因此，我们通过运行一个 Run Script 的 Build Phase 来管理、下载这些文件。在项目每次编译时，首先执行这个步骤，从而确保本地的二进制文件是最新的。&lt;/p&gt;

&lt;p&gt;因此，这个脚本需要从一个项目文件读取一些信息，然后从服务器下载对应的个数不定的文件，而这些文件后面会作为 &lt;a href="https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/On_Demand_Resources_Guide/" rel="noopener noreferrer"&gt;On-Demand Resources&lt;/a&gt; 打包进 app bundle。&lt;/p&gt;

&lt;h2&gt;
  
  
  编译系统
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=yo8_luxkSXY" rel="noopener noreferrer"&gt;WWDC22: Demystify parallelization in Xcode builds | Apple&lt;/a&gt; 介绍了新的现代构建系统通过一些巧妙的办法，最大程度并行化编译过程，以节省时间。不得不说，看了视频里展示类似于火焰图的性能图表，所有构建流程都紧凑地分散在不同的物理核心上时，令人相当心满意足。&lt;/p&gt;

&lt;p&gt;视频中提到了两个&lt;a href="https://developer.apple.com/documentation/xcode/build-settings-reference" rel="noopener noreferrer"&gt;设置&lt;/a&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run Build Script Phases in Parallel：设置是否并行执行编译过程&lt;/li&gt;
&lt;li&gt;User Script Sandboxing：是否将每一个脚本步骤的输入输出隔离，以作为计算编译过程依赖的图结构的标准&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;经过我的测试，无论是否并行执行，我遇到的上述错误依旧存在；而如果运行存在文件系统输入输出的脚本，sandboxing 设置是需要关闭的。&lt;/p&gt;

&lt;h2&gt;
  
  
  我的问题
&lt;/h2&gt;

&lt;p&gt;因为我们的下载文件的脚本是一个 &lt;a href="https://developer.apple.com/documentation/xcode/running-custom-scripts-during-a-build" rel="noopener noreferrer"&gt;Run Script Phase&lt;/a&gt; ，根据视频中的介绍，如果有文件系统的输入输出，理论上必须定义输入和输出的文件/文件列表。这样，编译系统才能按照定义的文件路径，来检测这个步骤是否完成，从而消除并行编译过程中的 race condition。&lt;/p&gt;

&lt;p&gt;然而，根据我们自己的测试，在不指定输入输出文件的时候，&lt;em&gt;有时&lt;/em&gt; Build Phase 也能正确完成而不抛出错误。因为这个问题只出现在某些文件上，最初遇到时我们百思不得其解，尝试了许多方法去解决这个问题，包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;尝试 debug 下载文件的脚本，看是不是因为产生了 race condition 或者线程问题，导致编译失败。结论是脚本没有这方面的问题。&lt;/li&gt;
&lt;li&gt;尝试把下载文件的步骤移到 &lt;a href="https://developer.apple.com/documentation/xcode/customizing-the-build-schemes-for-a-project#Run-tasks-before-or-after-scheme-actions" rel="noopener noreferrer"&gt;Pre build action&lt;/a&gt; 里面，即 xcscheme 设置里面。这个功能大致可以理解为在每次编译之前时，运行一个任意的脚本执行一些功能，和编译本身没有依赖。我们讨论了这个选项，但决定每次编译之前运行这个脚本的成本有点高，每个开发者都要在上面浪费零点几秒到几秒不等，日积月累也是不小的开销。所以决定不用这个功能。&lt;/li&gt;
&lt;li&gt;尝试把所有输出文件的列表加到 &lt;a href="https://developer.apple.com/documentation/xcode/running-custom-scripts-during-a-build#Specify-the-input-and-output-files-for-your-script" rel="noopener noreferrer"&gt;Output Files&lt;/a&gt; 里面。因为需要下载的文件数量随着项目成长是逐渐变多的，每次新加文件需要手动添加到列表很麻烦，也容易出错，所以决定不是万不得已还是不用这个方法。&lt;/li&gt;
&lt;li&gt;尝试通过一个脚本生成一个 &lt;code&gt;xcfilelist&lt;/code&gt; 文件来列出所有下载的文件。同上，如果有别的解决方法，就想先不用这个方法。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  真正的问题
&lt;/h2&gt;

&lt;p&gt;经过很长时间的尝试，我偶然发现，问题并非来自随机的文件，而是固定的几个文件。进而观察到，每次报错都会涉及到几个和 raster 栅格化底图相关的示例程序。顺藤摸瓜，居然发现罪魁祸首是文件类型！&lt;/p&gt;

&lt;p&gt;当我删掉这几个下载的 raster 文件夹，其中包括 tif 和 xml 格式的文件时，发现项目可以成功编译了。而把这些有问题的文件放到一个&lt;a href="https://stackoverflow.com/a/79190022/14369688" rel="noopener noreferrer"&gt;实际文件夹&lt;/a&gt;里面，而非引用逻辑文件夹时，也能解决问题。&lt;/p&gt;

&lt;p&gt;因此，在&lt;a href="https://github.com/Esri/arcgis-runtime-samples-ios/pull/1036" rel="noopener noreferrer"&gt;这个 Pull Request&lt;/a&gt; 里面，我把受影响的文件都放到了文件系统里的文件夹里，问题迎刃而解。&lt;/p&gt;

&lt;h2&gt;
  
  
  适合你的解法
&lt;/h2&gt;

&lt;p&gt;写这篇笔记的原因是时隔四五年后再次遇到这个问题。&lt;/p&gt;

&lt;p&gt;这次我们需要在编译开始之前，下载一些账号密码和密钥文件。再次遇到 “Build input file cannot be found” 报错时有点摸不到头脑，尝试了一圈以后发现还是文件类型的问题。密钥存储在 plist 文件里时就会报错，移到一个 json 文件里就不报错了。&lt;/p&gt;

&lt;p&gt;然而，对于这种下载固定个数的文件的情况，其实我更推荐以下两种解法&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre Actions，即前文所说的构建前运行脚本。因为密钥文件大小很小，几毫秒就能下载完，对于项目而言也是一个只读的文件，因此用这个功能完全够用。&lt;/li&gt;
&lt;li&gt;指定输出文件路径。因为密钥文件数量固定，只要把输出路径设置好，后期很少需要调整。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;而通过用别的文件类型以避免错误的方法，我在网上查了半天也没找到权威文档对于这个行为的解释，因此可以看作未来 Xcode 有可能悄没声就改了的行为。如果随便猜的话，或许是因为 Xcode 对某些特定类型的文件有特别的处理，而一些 Xcode 无法处理的文件类型则全当作任意文件处理。&lt;/p&gt;

&lt;p&gt;写下这篇笔记时，目前这个行为在 Xcode 11 - Xcode 16 之间仍然存在。&lt;/p&gt;

&lt;p&gt;且用且珍惜吧。🤷&lt;/p&gt;

&lt;p&gt;See this PR as an example.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/Esri/arcgis-maps-sdk-swift-samples/pull/757" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        [Fix] Enclose the tiff file in a folder when the ODR is a standalone raster
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#757&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/yo1995" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F9660181%3Fv%3D4" alt="yo1995 avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/yo1995" rel="noopener noreferrer"&gt;yo1995&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/Esri/arcgis-maps-sdk-swift-samples/pull/757" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 27, 2026&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Description&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;This PR fixes a recent daily build error introduced by #752 . The sample needed an "arran.tif" raster file via ODR, that was incorrectly handled by Xcode, causing a build error of "Build input file cannot be found".&lt;/p&gt;

&lt;p&gt;This type of problem affects all tiff files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To mitigate the problem specifically for a standalone tiff file, I added a logic to the download script, to enclose the tiff file in a folder when it is downloaded from AGOL.&lt;/li&gt;
&lt;li&gt;For rasters that contain more than just 1 tiff file, because typically they already come as a folder, I didn't make any change to handle them.
&lt;ul&gt;
&lt;li&gt;For instance, "Shasta.tif" from "Add raster from file" sample is already enclosed in a folder, so its folder isn't affected by this fix.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Upon a quick search, the only other same case was "srtm.tiff" used by "Apply hillshade renderer to raster" sample, which was fixed in #604. I've reverted that fix.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Backstory&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;

More backstory here
&lt;p&gt;This dates back all the way to &lt;a href="https://github.com/Esri/arcgis-runtime-samples-ios/pull/1036" rel="noopener noreferrer"&gt;https://github.com/Esri/arcgis-runtime-samples-ios/pull/1036&lt;/a&gt; . When Xcode 10 deprecated the legacy build system, there were some unclear changes to how Xcode handle the files that are tracked by the xcodeproj, but aren't part of a build phase's input/output. Later I discovered this undisclosed Xcode behavior that, for &lt;em&gt;certain&lt;/em&gt; file types, Xcode reports the following error, even if they are added the same way as other files&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
error: Build input file cannot be found: 'path_to_build/arcgis-maps-sdk-swift-samples/Portal Data/aa97788593e34a32bcaae33947fdc271/arran.tif'. Did you forget to declare this file as an output of a script phase or custom build rule which produces it? (in target 'Samples' from project 'Samples')&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So far, file types that are known to be affected are: .tif/.tiff, .plist&lt;/p&gt;


&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Linked Issue(s)&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;Slack chat reported by swift-devops.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How To Test&lt;/h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Have a clean build of this branch. Either download a zip archive of the branch, or clear your portal data folder before starting the test&lt;/li&gt;
&lt;li&gt;Build the project. No download error should be reported&lt;/li&gt;
&lt;li&gt;Run the app, open "Apply hillshade renderer to raster" and "Apply map algebra" samples.&lt;/li&gt;
&lt;li&gt;The ODR progress bar shows for both samples, and the samples run correctly.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;/div&amp;gt;
&amp;lt;div class="gh-btn-container"&amp;gt;&amp;lt;a class="gh-btn" href="https://github.com/Esri/arcgis-maps-sdk-swift-samples/pull/757"&amp;gt;View on GitHub&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
 
&lt;/div&gt;

</description>
      <category>programming</category>
      <category>code</category>
      <category>ios</category>
      <category>developer</category>
    </item>
    <item>
      <title>iOS 18.4 模拟器的 URLSession bug</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Tue, 01 Apr 2025 20:55:51 +0000</pubDate>
      <link>https://dev.to/yo1995/ios-184-mo-ni-qi-de-urlsession-bug-d70</link>
      <guid>https://dev.to/yo1995/ios-184-mo-ni-qi-de-urlsession-bug-d70</guid>
      <description>&lt;p&gt;前情提要：&lt;a href="https://developer.apple.com/forums/thread/777999" rel="noopener noreferrer"&gt;https://developer.apple.com/forums/thread/777999&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;大致问题就是在iOS 18.4 sim里面，同样的请求发两遍，第二次请求会报错。经过简短的讨论，目前看来这个问题仅限于模拟器，真机并无此问题。&lt;/p&gt;

&lt;p&gt;爱斯基摩大哥给了一个简单的补救方法，即禁止用HTTP/3协议来发送请求&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="cp"&gt;#if targetEnvironment(simulator)&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assumesHTTP3Capable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="cp"&gt;#endif&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;在app层面，还可以通过创建一个拦截器&lt;a href="https://developers.arcgis.com/swift/api-reference/documentation/arcgis/datataskinterceptor" rel="noopener noreferrer"&gt;interceptor&lt;/a&gt;，来改变请求中的参数；再把这个拦截器设置到我们SDK的通用会话属性上：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Create a custom network interceptor.&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MyInterceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DataTaskInterceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;interceptRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="kt"&gt;DataTaskRequestDetails&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;RequestInterceptionDisposition&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;URLResponse&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;
        &lt;span class="cp"&gt;#if targetEnvironment(simulator)&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assumesHTTP3Capable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="cp"&gt;#endif&lt;/span&gt;
        &lt;span class="c1"&gt;// Ask for the modified request to be sent.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;performModifiedRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Tell the `ArcGISURLSession` to use the custom interceptor.&lt;/span&gt;
&lt;span class="kt"&gt;ArcGISURLSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataTaskInterceptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MyInterceptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这样即使这个恶性bug最终进入了iOS production，我们的SDK也可以有办法不被影响。&lt;/p&gt;

</description>
      <category>swift</category>
      <category>programming</category>
      <category>ios</category>
    </item>
    <item>
      <title>探究是否用 @ViewBuilder 替换 AnyView</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Thu, 12 Dec 2024 00:42:14 +0000</pubDate>
      <link>https://dev.to/yo1995/tan-jiu-shi-fou-yong-viewbuilder-ti-huan-anyview-8n4</link>
      <guid>https://dev.to/yo1995/tan-jiu-shi-fou-yong-viewbuilder-ti-huan-anyview-8n4</guid>
      <description>&lt;h2&gt;
  
  
  起因
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj21ris68yhqvv25pz6kp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj21ris68yhqvv25pz6kp.png" alt="App landing page" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们的开源示例程序 &lt;a href="https://github.com/Esri/arcgis-maps-sdk-swift-samples" rel="noopener noreferrer"&gt;ArcGIS Maps SDK for Swift Samples&lt;/a&gt; 里面，使用了分栏式的展示逻辑，即点击列表中的一个示例，在详细页面 detail view 里展示一个相关地图功能。每一个功能都有很丰富的交互，因此每个详细功能页面少则占用10M+，多则100M+的内存才能加载。&lt;/p&gt;

&lt;p&gt;由于每个页面都算很“重”的视图，因此避免 SwiftUI 因视图 identity 改变而重新计算，变得尤为重要。&lt;/p&gt;

&lt;p&gt;在这个 app 最初设计时，遇到了这样的问题：我有一个 &lt;a href="https://github.com/Esri/arcgis-maps-sdk-swift-samples/blob/323e56704993e8e20cda8471bc577b13fd535403/Shared/Supporting%20Files/Models/Sample.swift#L39" rel="noopener noreferrer"&gt;&lt;code&gt;protocol Sample&lt;/code&gt;&lt;/a&gt; 用来表示一个示例。每一个示例的主页面都是一个不同的类型，如何在点击一个示例时，即时创建这个页面呢？在代码生成里，我使用了如下的方法&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeBody&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;AnyView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(\(&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewName&lt;/span&gt;&lt;span class="p"&gt;)())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这样，从每一个示例名称，就可以生成出对应的示例页面了。&lt;/p&gt;

&lt;p&gt;这样做的好处在于，没有任何复杂的语法，就能直接从字符串创建一个页面；坏处在于，编译时无法提前知道创建的页面的具体类型是什么，因此只能用 type-erased &lt;code&gt;AnyView&lt;/code&gt; 作为返回类型。&lt;/p&gt;

&lt;p&gt;然而，实际上，我是可以“提前”知道页面视图的类型的。根据每个示例的类型，通过 &lt;code&gt;case-is&lt;/code&gt; 语法来推断出对应的视图的类型。但是，这个推断的过程，无法避免地产生多种结果类型。如果使用 existential &lt;code&gt;any View&lt;/code&gt; (&lt;code&gt;func makeBody() -&amp;gt; any View&lt;/code&gt;) 来表示结果类型，编译器在 SwiftUI 会报错，提示无法进行类型检查:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;网上有很多教程都反对使用 &lt;code&gt;AnyView&lt;/code&gt; ，主要缺点就是 SwiftUI 无法确定它的真实 identity，因而在更新子视图树时，没法高效地决定这个视图是否需要更新，从而延长计算的时间。实际上，在视图树很矮、子视图关系不复杂的情况下，其带来的影响很小。具体到我们的 app 上来，我决定测试一番。&lt;/p&gt;

&lt;h2&gt;
  
  
  测试1：&lt;code&gt;AnyView&lt;/code&gt;的创建频率
&lt;/h2&gt;

&lt;p&gt;既然网上说法最关注 &lt;code&gt;AnyView&lt;/code&gt; 频繁重新计算所导致的性能损失，不妨先来看看我们的 app 究竟多频繁更新视图。剧透：其实根本没几次。&lt;/p&gt;

&lt;p&gt;经过测试我发现，由于 &lt;code&gt;AnyView&lt;/code&gt; 处于每个示例视图树的最顶端，即整个示例的所有视图都是 &lt;code&gt;AnyView&lt;/code&gt; 的子节点，因此有且仅有整个示例的视图发生变化时，&lt;code&gt;AnyView&lt;/code&gt;才会更新。即，当我在列表中，从一个示例切换到另一个示例，&lt;code&gt;makeBody()&lt;/code&gt; 方法才会被调用，从而创建一个新的 &lt;code&gt;AnyView&lt;/code&gt;。对于这个 app 而言，切换不同的示例属于低频的用户操作，根本达不到影响渲染性能的级别。&lt;/p&gt;

&lt;h2&gt;
  
  
  测试2：使用 &lt;code&gt;@ViewBuilder&lt;/code&gt; 是否真正节省时间？
&lt;/h2&gt;

&lt;p&gt;剧透：不仅不节省，反而多花时间。&lt;/p&gt;

&lt;p&gt;首先，我们假设，当每个示例的根视图被创建时，无论它是被 &lt;code&gt;AnyView&lt;/code&gt; 所包装，还是被 &lt;code&gt;@ViewBuilder&lt;/code&gt; 计算所得的 &lt;a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0289-result-builders.md" rel="noopener noreferrer"&gt;&lt;code&gt;_ConditionalContent&lt;/code&gt;&lt;/a&gt; 所包装，一旦其被创建，后面渲染的时间是一样的。这样，我们可以只关注 &lt;code&gt;AnyView&lt;/code&gt; 和  &lt;code&gt;@ViewBuilder&lt;/code&gt; 所产生的包装层的时间差别。&lt;/p&gt;

&lt;p&gt;依据我的测试，当我遍历创建整个 app 里的200个左右示例页面，使用&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeBody&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;AnyView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(\(&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewName&lt;/span&gt;&lt;span class="p"&gt;)())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 遍历所有示例并创建视图&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;bodies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;AnyView&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kt"&gt;SamplesApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bodies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeBody&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;和&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@ViewBuilder&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Sample&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;AddPointCloudLayerFromFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kt"&gt;AddPointCloudLayerFromFileView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;SelectFeaturesInFeatureLayer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kt"&gt;SelectFeaturesInFeatureLayerView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fatalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unknown &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; sample view generated."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 遍历所有示例并创建视图&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;bodies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kt"&gt;SamplesApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bodies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;两个方法时 &lt;code&gt;@ViewBuilder&lt;/code&gt; 的方法平均时间比 &lt;code&gt;AnyView&lt;/code&gt; 要慢5%左右（50ms vs 55ms）。&lt;/p&gt;

&lt;p&gt;这样的结果有些出乎我的意料。毕竟，如果理论上创建 &lt;code&gt;AnyView&lt;/code&gt; 需要花更多时间来确定其运行时的类型，难道不应该花更多时间吗？&lt;/p&gt;

&lt;p&gt;更令人意想不到的是接下来的发现。当我试图分析 &lt;code&gt;@ViewBuilder func view(for sample: Sample) -&amp;gt; some View&lt;/code&gt; 这个方法返回的视图类型时，发现其并非如老版本中使用 &lt;code&gt;_ConditionalContent&lt;/code&gt; 来包装视图，而是直接使用了 &lt;code&gt;AnyView&lt;/code&gt; ！即在调试器中，&lt;code&gt;print(type(of: view(for: sample)))&lt;/code&gt; 的结果是 &lt;code&gt;AnyView&lt;/code&gt; 。在调试器变量区显示的类型则是 &lt;code&gt;&amp;lt;&amp;lt;opaque return type of ArcGIS_Maps_SDK_Samples.ContentView.view(for: ArcGIS_Maps_SDK_Samples.Sample) -&amp;gt; some&amp;gt;&amp;gt;.0&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;作为对比，在我第一次测试 &lt;code&gt;@ViewBuilder&lt;/code&gt; 的结果时，它的类型是类似于如下的二叉树状结构的。当时我觉得这很合理——相当于用二叉搜索快速确定一个 result builder 的结果类型，应该性能上很不错。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_ConditionalContent&amp;lt;
    _ConditionalContent&amp;lt;
        _ConditionalContent&amp;lt;
            AddRasterFromFileView, 
            AddSceneLayerFromServiceView
        &amp;gt;, 
        _ConditionalContent&amp;lt;
            BrowseBuildingFloorsView, 
            ClipGeometryView
        &amp;gt;
    &amp;gt;,
    _ConditionalContent&amp;lt;
        _ConditionalContent&amp;lt;
            CreatePlanarAndGeodeticBuffersView, 
            CutGeometryView
        &amp;gt;, 
        _ConditionalContent&amp;lt;
            DisplayFeatureLayersView, 
            DisplayMapView
        &amp;gt;
    &amp;gt;
&amp;gt;,
// …
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;但是经过这次测试得到意想不到的结果，也能明白为什么 &lt;code&gt;@ViewBuilder&lt;/code&gt; 比 &lt;code&gt;AnyView&lt;/code&gt; 要慢了。因为最终创建的都是 &lt;code&gt;AnyView&lt;/code&gt; 的前提下，&lt;code&gt;@ViewBuilder&lt;/code&gt; 多了花在 &lt;code&gt;switch-case&lt;/code&gt; 判断的时间，比起直接从示例类型生成视图，相当于额外的时间。&lt;/p&gt;

&lt;h2&gt;
  
  
  测试3：大量 &lt;code&gt;AnyView&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;用类似如下视图来测试 &lt;code&gt;AnyView&lt;/code&gt; 在列表这种动态计算的视图中的性能影响。这个示例创建 50000 个 &lt;code&gt;HStack&lt;/code&gt; 和 &lt;code&gt;AnyView&lt;/code&gt; 包装的文本视图。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;items50K&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;..&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Identifiable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;NormalItemView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;HStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;AnyItemView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;AnyView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;NormalListView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="kt"&gt;NormalItemView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;AnyListView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="kt"&gt;AnyItemView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;NavigationView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;VStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;NavigationLink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;NormalListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items50K&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NormalView 50K"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="kt"&gt;NavigationLink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;AnyListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items50K&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AnyView 50K"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;明显可以看到，当打开 &lt;code&gt;AnyView&lt;/code&gt; 的列表时，有很长一段卡死的时间。但是当 &lt;code&gt;AnyView&lt;/code&gt;较少，比如一千个以下时，造成的性能影响并不大，不足以拖慢 app 的运行速度。&lt;/p&gt;

&lt;h2&gt;
  
  
  结论
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;AnyView&lt;/code&gt; 个数不多，对性能影响不太大&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;@ViewBuilder&lt;/code&gt; 替代 &lt;code&gt;AnyView&lt;/code&gt;，在一些情况下并不能提升性能&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  参考链接
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/wwdc23/10160?time=899" rel="noopener noreferrer"&gt;Demystify SwiftUI performance - WWDC23 - Videos - Apple Developer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forums.swift.org/t/swiftui-and-anyview-performance-benchmarks/65717" rel="noopener noreferrer"&gt;SwiftUI and AnyView: Performance benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Task vs Job vs Stream</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Wed, 20 Nov 2024 17:56:20 +0000</pubDate>
      <link>https://dev.to/yo1995/task-vs-job-vs-stream-dg</link>
      <guid>https://dev.to/yo1995/task-vs-job-vs-stream-dg</guid>
      <description>&lt;p&gt;在Swift Concurrency的异步编程中，我们逐渐发现，大体上有如下三种情形&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Task: 一般情况下耗时较短的异步回调，例如一个查询请求&lt;/li&gt;
&lt;li&gt;Job: 一般情况下耗时较长的异步回调，例如一个打包下载离线地图的任务&lt;/li&gt;
&lt;li&gt;Stream: 没有确定结束时间的“状态流”，如地理围栏触发的状态更新的通知&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在考虑异步API设计的过程中，需要分门别类将其归为这几种异步情形之一。有一些帮助我们分类的问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;这件任务在回调前，常见需要运行多少时间？（一百毫秒、一秒、一分钟）&lt;/li&gt;
&lt;li&gt;作为用户使用这个API时，是否会希望其附带一个“进度条”？&lt;/li&gt;
&lt;li&gt;回调时常见的数据量大小是多少？&lt;/li&gt;
&lt;li&gt;是否有办法暂停、继续该任务？&lt;/li&gt;
&lt;li&gt;任务开始后，是否只能取消或等待其完成？&lt;/li&gt;
&lt;li&gt;是否存在超时错误？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以及一些帮助我们联想的例子：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件上传、下载&lt;/li&gt;
&lt;li&gt;图像渲染、视频处理&lt;/li&gt;
&lt;li&gt;服务器数据库请求&lt;/li&gt;
&lt;li&gt;批量文件处理&lt;/li&gt;
&lt;li&gt;科学计算、模型训练&lt;/li&gt;
&lt;li&gt;GPS位置更新、股票价格更新&lt;/li&gt;
&lt;li&gt;支付请求超时&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>programming</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>检测app是否运行在visionOS的临时方法</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Wed, 20 Nov 2024 00:42:12 +0000</pubDate>
      <link>https://dev.to/yo1995/jian-ce-appshi-fou-yun-xing-zai-visionosde-lin-shi-fang-fa-4kj4</link>
      <guid>https://dev.to/yo1995/jian-ce-appshi-fou-yun-xing-zai-visionosde-lin-shi-fang-fa-4kj4</guid>
      <description>&lt;p&gt;TL;DR&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// A Boolean indicating whether the current device is running visionOS or not.&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;isOnVisionOS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSClassFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UIWindowSceneGeometryPreferencesVision"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;需要注意的点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;目前（202411）这是一个较为简单的检测方法。苹果还未提供官方API来实现这个功能。&lt;a href="https://developer.apple.com/forums/thread/733029" rel="noopener noreferrer"&gt;source&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;类似的官方API &lt;a href="https://developer.apple.com/documentation/foundation/processinfo/3608556-isiosapponmac" rel="noopener noreferrer"&gt;&lt;code&gt;isiOSAppOnMac&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;这个检测无法判断app是处于iPadOS兼容性模式，还是visionOS原生模式运行。&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>multiplatform</category>
      <category>mixedreality</category>
      <category>ios</category>
    </item>
    <item>
      <title>Synchronized 变量在不同操作系统的不同行为</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Thu, 07 Nov 2024 18:07:58 +0000</pubDate>
      <link>https://dev.to/yo1995/synchronized-bian-liang-zai-bu-tong-cao-zuo-xi-tong-de-bu-tong-xing-wei-i25</link>
      <guid>https://dev.to/yo1995/synchronized-bian-liang-zai-bu-tong-cao-zuo-xi-tong-de-bu-tong-xing-wei-i25</guid>
      <description>&lt;p&gt;SEO:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python Synchronized variable value doesn't change in global context&lt;/li&gt;
&lt;li&gt;Synchronized value not modified in main process&lt;/li&gt;
&lt;li&gt;Synchronized value not shared between processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在Python的&lt;code&gt;multiprocessing&lt;/code&gt;库里面，有&lt;a href="https://docs.python.org/3/library/multiprocessing.html#multiprocessing.sharedctypes.synchronized" rel="noopener noreferrer"&gt;&lt;code&gt;multiprocessing.sharedctypes.synchronized&lt;/code&gt;&lt;/a&gt;这个用来在不同进程间共享数据的wrapper类型，默认使用一个重入锁&lt;code&gt;RLock&lt;/code&gt;来维护数据一致性。在这次重新学习Python之前，我用的是Python 3.6，当时虽然使用过mp库的一些皮毛，但未曾深入考虑过多进程程序在不同操作系统上的不同行为，以及它们可能的影响。&lt;/p&gt;

&lt;p&gt;这次学习时，遇到一个问题：当我在macOS系统用Python 3.12在全局context里创建一个&lt;code&gt;multiprocessing.sharedctypes.Value&lt;/code&gt;变量的时候，如果我在一个新的进程里访问这个变量，其值并未在不同进程之间保持同步。和&lt;a href="https://github.com/ericwgreene" rel="noopener noreferrer"&gt;Eric Greene&lt;/a&gt;老师讨论后，才发现自从Python 3.8以来，不同操作系统的新建进程的方式发生了变化：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux系统始终使用&lt;code&gt;fork&lt;/code&gt;来创建新的进程。在fork的时候，父进程的所有资源都被子进程继承，因此在父进程全局定义的同步变量，同样可以被子进程访问，所以数据一致性得以保留&lt;/li&gt;
&lt;li&gt;Windows系统始终使用&lt;code&gt;spawn&lt;/code&gt;，即创建一个新的Python解释器进程的方法来实现多进程。相当于“多开”Python，因此overhead更多，效率更低，但是这是Windows系统本身的局限。在这种情况下，子进程&lt;strong&gt;只继承&lt;/strong&gt;父进程中，用来启动新进程的&lt;a href="https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Process.run" rel="noopener noreferrer"&gt;&lt;code&gt;run()&lt;/code&gt;&lt;/a&gt;方法所需的资源。&lt;/li&gt;
&lt;li&gt;macOS系统在Python 3.8以前是使用fork，之后&lt;strong&gt;改为使用spawn&lt;/strong&gt;，因此有些多进程行为发生了改变。(macOS仍旧可以设置成使用fork，但Python官方不推荐）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在我的例子里面，因为Synchronized变量在父进程的全局声明，因此在Windows和macOS系统上，它不会被子进程所继承，因此不同进程之间的值产生差异；而在Linux系统上，所有父进程的资源都被继承，所以子进程能改变它的值。&lt;/p&gt;

&lt;p&gt;这便造成了我所看到的现象：我的代码在macOS上全局同步变量没有变；而别人的Linux系统则运行正常，变量在所有进程间同步。&lt;/p&gt;

&lt;p&gt;参考阅读：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods" rel="noopener noreferrer"&gt;https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/multiprocessing.html#multiprocessing.sharedctypes.synchronized" rel="noopener noreferrer"&gt;https://docs.python.org/3/library/multiprocessing.html#multiprocessing.sharedctypes.synchronized&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>linux</category>
      <category>multiplatform</category>
      <category>macos</category>
    </item>
    <item>
      <title>MacOS 和 iOS app 的版本号冲突</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Fri, 09 Aug 2024 00:25:42 +0000</pubDate>
      <link>https://dev.to/yo1995/macos-he-ios-app-de-ban-ben-hao-chong-tu-4cpc</link>
      <guid>https://dev.to/yo1995/macos-he-ios-app-de-ban-ben-hao-chong-tu-4cpc</guid>
      <description>&lt;p&gt;最近新发布了一个由原先iOS代码库改动而来的Mac Catalyst macOS app。自从登上Mac App Store以来，daily build就上传不了了，提示如下的错误：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This bundle is invalid. The value for key CFBundleVersion [4001] in the Info.plist file must contain a higher version than that of the previously uploaded version [20240719]. Please find more information about CFBundleVersion at &lt;a href="https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion" rel="noopener noreferrer"&gt;https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion&lt;/a&gt; With error code STATE_ERROR.VALIDATION_ERROR.90061 for id [redacted] Asset validation failed (-19208)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;刨根问底一路搜查下来，发现问题的根源在于 iOS 和 macOS app 共享了&lt;a href="https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion" rel="noopener noreferrer"&gt;CFBundleVersion&lt;/a&gt;这个键值。在 iOS app 中，这个键值对应着Xcode项目文件主页面里的build输入框中的值；而因为历史原因，在 macOS app 中，这个键值代表了该程序当前的版本号，也就是常见的&lt;code&gt;major.minor.patch&lt;/code&gt;三部分版本。&lt;/p&gt;

&lt;p&gt;这就造成了一个未曾预料的问题：我们的app最初是iOS app，当苹果推出了Mac Catalyst以后才想着顺便支持macOS，发布在MAS上的。因此，从来都是把build number填写为当天的daily build的序数。对于iOS来讲，当version number相同时，TestFlight比较build number；当version number改变后，build number可以重置，因此build number无需严格递增。但这个数在macOS上则被视作一个单独的major版本号，因此相当于我们每天的daily build都是一个新的大版本。如果不关心版本号语义的话，倒也没啥大问题；问题出在我们手动发布新版本时，手动填写了build的数值，导致接下来所有的macOS build的版本号都必须大于这个数值。好嘛，一下子从一个四位数的版本变成了八位数的版本，遥遥领先！&lt;/p&gt;

&lt;p&gt;解决方法就是在第一次发布macOS版本前，一定一定和团队内确定好，如何设计一个build number格式，使其既适合当作iOS的build number，又适合当作macOS的version number。至于如何设计，我母鸡呀？🤷&lt;/p&gt;

&lt;p&gt;归根到底，这是苹果的锅——没有处理好iOS和macOS版本号规则的差异，导致跨平台程序屡屡受挫。&lt;/p&gt;

&lt;p&gt;相关阅读&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/forums/thread/759988" rel="noopener noreferrer"&gt;https://developer.apple.com/forums/thread/759988&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/technotes/tn2420/_index.html#//apple_ref/doc/uid/DTS40016603-CH1-HOW_THESE_NUMBERS_WORK_TOGETHER" rel="noopener noreferrer"&gt;https://developer.apple.com/library/archive/technotes/tn2420/_index.html#//apple_ref/doc/uid/DTS40016603-CH1-HOW_THESE_NUMBERS_WORK_TOGETHER&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: For macOS apps, build numbers must monotonically increase even across different versions. In other words, for macOS apps you cannot use the same build numbers again in different release trains. iOS apps have no such restriction and you can re-use the same build numbers again in different release trains.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>macos</category>
      <category>ios</category>
      <category>swift</category>
      <category>devops</category>
    </item>
    <item>
      <title>测试 iOS app 的后台行为</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Wed, 26 Jun 2024 23:03:11 +0000</pubDate>
      <link>https://dev.to/yo1995/ce-shi-ios-app-de-hou-tai-xing-wei-1dbh</link>
      <guid>https://dev.to/yo1995/ce-shi-ios-app-de-hou-tai-xing-wei-1dbh</guid>
      <description>&lt;p&gt;最近在开发中碰到用 SwiftUI 的 &lt;a href="https://developer.apple.com/documentation/swiftui/scene/backgroundtask(_:action:)" rel="noopener noreferrer"&gt;&lt;code&gt;backgroundTask(_:action:)&lt;/code&gt;&lt;/a&gt; 修饰符来实现“真后台”下载数据的功能。代码实现本身就已经有点儿绕了，而测试更是令人挠头。主要在于 iOS 和其他桌面操作系统相比，最初就没有想着如何精确控制后台程序的行为，导致很多的行为都是靠操作系统自己来协调。因此，测试起来也颇费一番周折。&lt;/p&gt;

&lt;p&gt;在测试过程中，我们发现了一些小的技巧，在此记录一二。&lt;/p&gt;

&lt;h2&gt;
  
  
  打印日志
&lt;/h2&gt;

&lt;p&gt;iOS 14 引入的 &lt;code&gt;OSLog&lt;/code&gt; 库能够让我们用现代而高效的日志来刻画程序运行状态。通过创建一个由环境变量控制的 &lt;a href="https://developer.apple.com/documentation/os/logging/generating_log_messages_from_your_code" rel="noopener noreferrer"&gt;&lt;code&gt;Logger&lt;/code&gt;&lt;/a&gt;，我们可以在 debug build 里打出更多日志，而毋需影响 release build。&lt;/p&gt;

&lt;h2&gt;
  
  
  导出日志
&lt;/h2&gt;

&lt;p&gt;iOS app 的后台行为之所以难测，是因为一旦程序被操作系统挂起、进入所谓“假死”、“软退出”的状态后，Xcode 的 debugger 就不再和程序连接在一起了。因此，也没法通过看 debugger 的输出来决定程序目前到底处在什么状态、下载数据下载到多少。&lt;/p&gt;

&lt;p&gt;这个时候我们就需要把在真实设备上运行的程序日志，输出到电脑上查看。通过以下的命令&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;log collect &lt;span class="nt"&gt;--device-name&lt;/span&gt; &lt;span class="s2"&gt;"My iPhone"&lt;/span&gt; &lt;span class="nt"&gt;--last&lt;/span&gt; 10m &lt;span class="nt"&gt;--output&lt;/span&gt; &lt;span class="s2"&gt;"~/Desktop"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以把设备上之前10分钟的日志输出到桌面的 &lt;code&gt;.logarchive&lt;/code&gt; 文件里，从而用 macOS 的 Console 控制台软件打开。&lt;/p&gt;

&lt;h2&gt;
  
  
  如何模拟后台运行
&lt;/h2&gt;

&lt;p&gt;Eskimo 曾经写过一个很好的回答：&lt;a href="https://developer.apple.com/forums/thread/14855" rel="noopener noreferrer"&gt;https://developer.apple.com/forums/thread/14855&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;主要的几点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;可以用 syscall &lt;code&gt;exit(0)&lt;/code&gt; 来模拟类似挂起的“软退出”。在 iOS 上滑的后台管理界面里，如果你拖拽来杀后台的话，系统默认是“硬退出”，从而会导致后台任务被完全暂停；而如果只是上滑把程序放进后台，而不手动杀后台的话，系统会时不常分给程序一点儿后台时间，从而让后台下载数据的任务持续进行&lt;/li&gt;
&lt;li&gt;尽量用真实设备，而非模拟器来测试后台行为&lt;/li&gt;
&lt;li&gt;为了确保后台行为的可重复性，建议每次都删掉之前的测试 app ，或者手动清空数据，或者使用 &lt;code&gt;invalidateAndCancel()&lt;/code&gt; 方法来重置 app 的状态&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>swiftui</category>
      <category>testing</category>
    </item>
    <item>
      <title>Swift字典键值的查找时间复杂度</title>
      <dc:creator>Ting</dc:creator>
      <pubDate>Mon, 01 Apr 2024 21:59:12 +0000</pubDate>
      <link>https://dev.to/yo1995/swiftzi-dian-jian-zhi-de-cha-zhao-shi-jian-fu-za-du-lb2</link>
      <guid>https://dev.to/yo1995/swiftzi-dian-jian-zhi-de-cha-zhao-shi-jian-fu-za-du-lb2</guid>
      <description>&lt;h2&gt;
  
  
  SEO
&lt;/h2&gt;

&lt;p&gt;Swift Dictionary Keys lookup time complexity contains O(1) O(n) constant time&lt;/p&gt;

&lt;h2&gt;
  
  
  太长不看
&lt;/h2&gt;

&lt;p&gt;先说结论，下面两种访问字典键值/查找一个键值是否存在于字典中的方法，时间复杂度同为amortized O(1)。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  细节
&lt;/h2&gt;

&lt;p&gt;在Swift 4.0也就是大约2019年以前，使用第二种方法时，查找字典键值的时间复杂度为O(n)。因为当时 &lt;code&gt;Dictionary.Keys&lt;/code&gt; 只是一个普通的符合 &lt;code&gt;Collection&lt;/code&gt; 协议的集合。大家发现语言里存在这个问题，会导致下标访问和 &lt;code&gt;contains&lt;/code&gt; 的时间复杂度不同，不符合预期，因此提出了如下提议&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/apple/swift-evolution/blob/main/proposals/0154-dictionary-key-and-value-collections.md" rel="noopener noreferrer"&gt;0154 - Provide Custom Collections for Dictionary Keys and Values&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个提议的具体内容就是用自定义的类型 &lt;a href="https://github.com/apple/swift/blob/826515fbf38536d283cb68ba94e49190a607332e/stdlib/public/core/Dictionary.swift#L1287" rel="noopener noreferrer"&gt;&lt;code&gt;Keys&lt;/code&gt;&lt;/a&gt; 和 &lt;code&gt;Values&lt;/code&gt; 代替默认生成的符合 &lt;code&gt;Collection&lt;/code&gt; 协议的集合，从而实现一些自定方法，以提高随机查找的效率。&lt;/p&gt;

&lt;p&gt;由于字典、集合等（哈希）散列数据结构的实现，多采用“不够就再扩容”的思想，因此这个方法的最坏时间复杂度并非常数时间O(1)，而是线性时间 n ；但均摊分析amortized下的时间复杂度仍是O(1)。&lt;/p&gt;

&lt;p&gt;⚠️ 需要注意的是，如果嵌套了以前 cocoa 中的 &lt;code&gt;NSDictionary&lt;/code&gt; 类型，则时间复杂度不再是一个确定的值。 &lt;/p&gt;

&lt;h2&gt;
  
  
  参考资料
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://forums.swift.org/t/does-dictionary-keys-contains-have-o-1-time-complexity/21647/14" rel="noopener noreferrer"&gt;https://forums.swift.org/t/does-dictionary-keys-contains-have-o-1-time-complexity/21647/14&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/28129401" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/28129401&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/apple/swift/blob/main/stdlib/public/core/Dictionary.swift" rel="noopener noreferrer"&gt;https://github.com/apple/swift/blob/main/stdlib/public/core/Dictionary.swift&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>datastructures</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
