<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ 牧云踏歌 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ 牧云踏歌 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 06 May 2026 14:33:16 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/muyuntage/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 基于流复制创建 PostgreSQL 15 从库 ]]>
                </title>
                <description>
                    <![CDATA[ 都说数据无价，本文就介绍一下如何快速拥有一个PostgreSQL的备库，从而实现主从数据库 。通常情况下，从库可以作为一个备份的存在（只读），当主库出现宕机后，我们可以快速把从库提升为主库（读写）。步骤并不复杂，一起来尝试一下吧。 创建用户，专门用来实现数据复制 create user replica with replication login password 'replication'; 检查postgresql.conf配置文件 wal_level = replica 检查pg_hba.conf权限配置文件 建议在最后追加一行host replication replica all md5，其中all也可以替换为从库所处机器的ip地址，比如 192.168.0.10/32 生成备份库  pg_basebackup -h 192.168.0.21 -p 54321 -U replica -R -P -v -C --slot=pgstandby1 -D /Users/liurui/pg2  * -R 说明会创建standby.signal文件，以及补充postgres ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/postgresql15-stream-replication/</link>
                <guid isPermaLink="false">639a7f69a7bffa07c7441624</guid>
                
                    <category>
                        <![CDATA[ PostgreSQL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Thu, 15 Dec 2022 02:49:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/12/iShot2020-12-04-14.24.51.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>都说数据无价，本文就介绍一下如何快速拥有一个<code>PostgreSQL</code>的备库，从而实现<strong>主从数据库</strong>。通常情况下，从库可以作为一个备份的存在（只读），当主库出现宕机后，我们可以快速把从库提升为主库（读写）。步骤并不复杂，一起来尝试一下吧。</p>
<h3 id="">创建用户，专门用来实现数据复制</h3>
<pre><code>create user replica with replication login password 'replication';
</code></pre>
<h3 id="postgresqlconf">检查<code>postgresql.conf</code>配置文件</h3>
<p><code>wal_level = replica</code></p>
<h3 id="pg_hbaconf">检查<code>pg_hba.conf</code>权限配置文件</h3>
<p>建议在最后追加一行<code>host replication replica all md5</code>，其中<code>all</code>也可以替换为从库所处机器的<code>ip</code>地址，比如<code>192.168.0.10/32</code></p>
<h3 id="">生成备份库</h3>
<pre><code> pg_basebackup -h 192.168.0.21 -p 54321 -U replica -R -P -v -C --slot=pgstandby1 -D /Users/liurui/pg2
</code></pre>
<ul>
<li>-R 说明会创建<code>standby.signal</code>文件，以及补充<code>postgresql.auto.conf</code>的内容</li>
<li>-P 显示备份进度</li>
<li>-v 显示更加详细信息</li>
<li>-C 同时创建复制槽</li>
<li>--slot 指定复制槽的名字（一个备库一个名字）</li>
<li>-D 生成备库的路径</li>
</ul>
<blockquote>
<p>复制槽的好处<br>
主库的事务日志一直处于滚动消耗的状态，如果备库下线，随着主库频繁的数据变动，可能就会存在当备库重新上线后，已经找不到之前没有拉取的事务日志的情况（被主库回收掉了）。<br>
但是有了复制槽，主库就会为复制槽保留它没有消费的日志，等待它上线后进行消费。当然代价是对磁盘的消耗，不过只要备库不是永久丢失，磁盘消耗对于大部分场景来说不是问题。<br>
但是如果备库永久丢失了，要记得删除主库中对应的复制槽。删除复制槽的语句为<code>select pg_drop_replication_slot('pgstandby1');</code></p>
</blockquote>
<blockquote>
<p>注意<br>
备库的操作系统环境最好与主库一致，如果一个是Windows一个是Linux，几乎一定会遇到字符集不一致的问题，导致备库无法启动。</p>
</blockquote>
<h3 id="">启动备库</h3>
<pre><code>pg_ctl -D /Users/liurui/pg2 start
</code></pre>
<h3 id="">验证流复制可用性</h3>
<ul>
<li>在主库进行数据变动，在备库可进行观察</li>
<li>在主库执行<code>select * from pg_replication_slots;</code>能够观察到复制槽</li>
<li>在主库执行<code>select * from pg_stat_replication;</code>可以观察到备库连接情况，只有备库上线的情况下，该查询才会返回数据</li>
<li>顺便说一下，从库的数据目录会生成名为<code>postgresql.auto.conf</code>的文件（对应上面的<code>-R</code>参数），里面存放了主库的连接信息，感兴趣的小伙伴可以观察看看，但是这个文件不建议手动编辑。</li>
<li><strong>注意</strong>，这样创建的备库其实叫异步备库，极端情况下，备库的数据跟主库比是有一定延迟的，如果需要无延迟的备库，就需要启动<code>同步</code>模式，当然它会牺牲更多的性能，也要付出更高的运维成本，本文并不涉及<code>同步</code>模式的介绍。</li>
</ul>
<h3 id="">如何获得指定延迟时间的备库（可选）</h3>
<p>大部分情况下，备库与主库保持一致是我们需要的。但是有时候，我们也需要的也一个比主库有延迟的备库，比如<code>延迟三十分钟</code>，也就意味着，当主库的数据被不小心删除后，三十分钟内，我们都可以在备库里找到。此时需要在备库配置：</p>
<pre><code>recovery_min_apply_delay = 30min
</code></pre>
<h3 id="">手动把备库提升为主库</h3>
<p>备库平时只能作为只读数据库使用，因为可写数据是主库才有的特权。但是一旦主库挂掉，并且已知备库的数据足够完整的情况下，我们可以迅速把备库提升为主库。只要找到备库的文件路径，把里面的<code>standby.signal</code>文件删除即可。顺便说一下，这个文件是没有内容的，它是一个空文件，通过这个文件的存在来表明自己是个备库，所以删掉或者重命名该文件，都能把这个备库提升为主库。当然，别忘了删除该文件后，重启备库才能生效。</p>
<h3 id="docker">补充：在docker环境下生成备库</h3>
<ol>
<li>
<p>像平常在<code>docker</code>使用一样，启动一个数据库，当然要注意该备库的版本与主库保持一致。一个典型的yml文件如下：</p>
<pre><code>version: '3'
services:
 postgres15-bak:
  image: postgres:15.1
  ports:
   - 34325:5432
  volumes:
   - /root/docker/volume/postgres15:/var/lib/postgresql/data
</code></pre>
</li>
<li>
<p>使用<code>docker-compose</code>启动该镜像后，再通过<code>docker exec</code>进入它</p>
</li>
<li>
<p>切换用户：<code>su postgres</code></p>
</li>
<li>
<p>进入<code>postgres</code>拥有权限的路径：<code>cd /var/lib/postgresql/data</code></p>
</li>
<li>
<p>生成备库：<code>pg_basebackup -h 192.168.0.21 -p 54321 -U replica -R -P -v -C --slot=pgstandby1 -D ./bak</code> 此时备库路径就在<code>/var/lib/postgresql/data/bak</code>下</p>
</li>
<li>
<p>退出<code>docker</code>，回到宿主机，并关闭刚刚使用的<code>docker</code>镜像</p>
</li>
<li>
<p>在宿主机把刚才镜像映射的文件夹调整一下，以上面的<code>yml</code>文件为例，就应该先把<code>/root/docker/volume/postgres15/bak</code>文件夹拷贝出来，再把<code>/root/docker/volume/postgres15</code>文件夹删除掉，之后把刚拷贝出来的<code>bak</code>文件夹还原为<code>/root/docker/volume/postgres15</code>，此时该镜像映射的文件夹就是我们新产生的从库备份了。</p>
</li>
<li>
<p>启动上面的<code>docker</code>镜像。则从库已经接入主库。</p>
</li>
</ol>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（10）- 泛型 【完结🎉🎉🎉】 ]]>
                </title>
                <description>
                    <![CDATA[ 泛型这个词是从generics翻译过来的，本意有一般的的意思。但是不知道被哪位惊才绝艳的前辈翻译成了泛型 ，初看之下让人摸不着头脑，但其实大有深意。在之前的学习中，我们已经了解了class与struct，如果不考虑struct 的内存特殊性的话，其实它们都可以归于一种广义意义上的类型。也就是说，在Swift的设计哲学中，类型是无处不在的，一块具体的数据它要么是class要么是 struct，总之得有具体类型。 而把具体类型看作一枚银币的其中一面，那么另外一面就应该是“非具体类型”，或者说是泛化类型，简称范型。 不过你也不要误会，所谓泛化类型，并不是要抹除具体类型存在的意义。而是说，对于某些数据的类型，可以从编写代码阶段的明确延迟到运行阶段再明确。 这么说可能有些抽象，我们来看下苹果官方文档给出的例子： func swapTwoInts(_ a: inout Int, _ b: inout Int) {     let temporaryA = a     a = b     b = temporaryA } 这个函数的功能是交换两个Int值，同理如果想交换两个String， ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-10/</link>
                <guid isPermaLink="false">62f4aab68d13aa0845c6439b</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Thu, 11 Aug 2022 07:19:48 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/08/swift-og2-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><strong>泛型</strong>这个词是从<code>generics</code>翻译过来的，本意有<code>一般的</code>的意思。但是不知道被哪位惊才绝艳的前辈翻译成了<code>泛型</code>，初看之下让人摸不着头脑，但其实大有深意。在之前的学习中，我们已经了解了<code>class</code>与<code>struct</code>，如果不考虑<code>struct</code>的内存特殊性的话，其实它们都可以归于一种广义意义上的<code>类型</code>。也就是说，在<code>Swift</code>的设计哲学中，<code>类型</code>是无处不在的，一块具体的数据它要么是<code>class</code>要么是<code>struct</code>，总之得有<strong>具体类型</strong>。</p>
<p>而把<strong>具体类型</strong>看作一枚银币的其中一面，那么另外一面就应该是“非具体类型”，或者说是<strong>泛化类型</strong>，简称<strong>范型</strong>。</p>
<p>不过你也不要误会，所谓<strong>泛化类型</strong>，并不是要抹除<strong>具体类型</strong>存在的意义。而是说，对于某些数据的类型，可以从编写代码阶段的<strong>明确</strong>延迟到<strong>运行</strong>阶段再明确。</p>
<p>这么说可能有些抽象，我们来看下苹果官方文档给出的例子：</p>
<pre><code class="language-swift">func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
</code></pre>
<p>这个函数的功能是交换两个<code>Int</code>值，同理如果想交换两个<code>String</code>，我们还得再写个函数：</p>
<pre><code class="language-swift">func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}
</code></pre>
<p>以此类推，如果要交换两个<code>Double</code>类型还得继续写。并且你会发现，这些函数内部的逻辑就是一模一样的，唯一的区别就是接受参数的类型不同。</p>
<p><strong>那么，如果我们不把参数的类型硬编码，而是也通过参数传递给函数，这样不就可以抽象出来更高级的函数代码了么？</strong></p>
<p>上面这个问题的解决方案就是——<strong>泛型</strong>，我们先来看通过泛型改进过后的代码：</p>
<pre><code class="language-swift">func swapTwoValues&lt;T&gt;(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
</code></pre>
<p>其实函数内部的逻辑代码我们一行没改，变化还是发生在函数的声明部分。这里参数<code>a</code>、<code>b</code>的类型，从之前我们熟悉的类型改成了<code>T</code>，需要注意的是<code>T</code>并不是一个类型，而是一种占位符，本质上来说它也是个参数。就像<code>a</code>是个占位符，也是个参数一样。</p>
<p>但是<code>T</code>参数又很特殊，它用来约定了参数<code>a</code>、<code>b</code>的类型，可以说成是<strong>参数的参数</strong>。那么既然是参数，就得需要调用者进行参数指定吧，我们是否需要明确传递参数<code>T</code>呢？比如这样：</p>
<pre><code class="language-swift">var x = 1
var y = 2

swapTwoValues&lt;Int&gt;(&amp;x, &amp;y)
</code></pre>
<p>遗憾的是我们会收到一行报错：</p>
<blockquote>
<p>error: cannot explicitly specialize a generic function</p>
</blockquote>
<p>想想也是，既然我们传递<code>a</code>、<code>b</code>的值确定了，那这俩参数的<code>Int</code>类型也确定了，所以完全没有必要多此一举再去给<code>T</code>传递参数，直接简简单单这样就可以了：</p>
<pre><code class="language-swift">swapTwoValues(&amp;x, &amp;y)
</code></pre>
<hr>
<p>不过，如果当<code>泛型</code>作用于<code>struct</code>或者<code>class</code>时，情况就不一样了。</p>
<p>在计算机领域中，有一种被称作<code>栈（Stack）</code>的数据结构，想象一下，你桌子上有一摞书，也就是一本压着一本摆放的。显而易见，最下面那本书是你最早放置的，而最上面一本书是你最后放置的。如果你想把第一本书，也就是最下面那本书找出来，你不得不从上开始把书一本一本的拿走（假设你每次只能操纵一本书的话）。这里描述的场景就是<code>栈</code>，用代码模拟的话，可以这样写：</p>
<pre><code class="language-swift">struct IntStack {
    var items: [Int] = []
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -&gt; Int {
        return items.removeLast()
    }
}
</code></pre>
<p>我们构造了一个<code>IntStack</code>用来存储<code>Int</code>类型的栈，它提供两个方法：<code>push</code>负责把数放进栈，<code>pop</code>负责把<code>栈顶</code>（也就是最上面/最后一个）取出来。</p>
<p>跟之前举的例子类似，如果你需要另外一个<code>StringStack</code>来存储<code>String</code>类型的栈的时候，你不得不复制大部分的代码，像下面这样：</p>
<pre><code class="language-swift">struct StringStack {
    var items: [String] = []
    mutating func push(_ item: String) {
        items.append(item)
    }
    mutating func pop() -&gt; String {
        return items.removeLast()
    }
}
</code></pre>
<p>仔细观察<code>IntStack</code>于<code>StringStack</code>代码的些许不同，不难发现问题还是出在<code>类型</code>上，如果<code>Stack</code>关注内容的<code>类型</code>是非硬编码的，而是通过某种手段动态指定的，那么一切的问题将迎刃而解。展现<strong>泛型</strong>威力的时候又到了：</p>
<pre><code class="language-swift">struct Stack&lt;T&gt; {
    var items: [T] = []
    mutating func push(_ item: T) {
        items.append(item)
    }
    mutating func pop() -&gt; T {
        return items.removeLast()
    }
}
</code></pre>
<p>这样我们就拥有了一个不拘泥于一个具体类型的<code>Stack</code>，如果你需要它存储<code>String</code>，只需要像下面这样：</p>
<pre><code class="language-swift">var stackOfStrings = Stack&lt;String&gt;()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
</code></pre>
<p>这里你应该发现了，参数<code>T</code>需要我们在实例化<code>Stack</code>的时候指定，并且一旦指定之后，<code>Stack</code>内部就已经明确了<code>T</code>的类型。如果我们尝试<code>push</code>一个<code>Int</code>类型的数据的话，就会收到报错：</p>
<blockquote>
<p>error: cannot convert value of type 'Int' to expected argument type 'String'</p>
</blockquote>
<hr>
<p>讲到这，<code>泛型</code>的概念你应该理解的差不多了，不论是从函数调用还是从类/结构体的使用角度来看，都可以分为<code>调用者</code>于<code>被调用者</code>，而<code>泛型</code>的价值就是在于让<code>调用者</code>有更大的施展空间，也可以理解为<code>被调用者</code>让出了一部分权利供<code>调用者</code>自己发挥。</p>
<p>但这种权利的让渡也是有限度的，有时候被调用者虽然不再关心具体的类型，但是却希望这些类型能够局限在一个范围内，而不是完全放任自流。请看下面的例子：</p>
<pre><code class="language-swift">func anyCommonElements&lt;T&gt;(_ lhs: T, _ rhs: T) -&gt; Bool
where T:Sequence, T.Element: Equatable
{
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}

anyCommonElements([1, 2, 3], [3])
</code></pre>
<p>这应该算是一个比较复杂的例子了，<code>anyCommonElements</code>可以实现对两个序列是否相交的比较。核心逻辑就是对两个序列进行嵌套循环遍历，一旦发现元素相同，立刻<code>return true</code>进行返回。这个逻辑倒是不难理解，但是其针对<code>泛型</code>的应用，还是值得分析一下。</p>
<p>首先，我们是要运算的参数类型只能是支持遍历的序列，而不能是其他。所以我们要限制<code>T</code>泛型可用类型的范围，必须是遵循<code>Sequence</code>协议的。同时，既然是要比较两个序列，那么序列中的元素必须是可比较的，也就是<code>T.Element: Equatable</code>要求的<code>T</code>中元素必须满足协议<code>Equatable</code>。</p>
<p>你可能会好奇，我是怎么知道<code>T</code>里面有个<code>Element</code>的。其实查看下<code>Sequence</code>的文档就会发现，<code>Sequence</code>的声明也用到了泛型，就是下面这句：</p>
<pre><code class="language-swift">protocol Sequence&lt;Element&gt;
</code></pre>
<p>所以<code>Element</code>的出处不是别的神秘物体，正是我们今天学的泛型的一种官方应用。</p>
<p>此时回头看上面的<code>where T:Sequence, T.Element: Equatable</code>语句应该清晰了，通过<code>where</code>我们可以对函数声明的泛型追加一些限制条件。对于<code>T:Sequence</code>这样直接作用于<code>T</code>的条件，除了写在<code>where</code>处，也是可以在声明<code>T</code>的地方设置的。所以上面方法的声明等价于下面这种写法：</p>
<pre><code class="language-swift">func anyCommonElements&lt;T:Sequence&gt;(_ lhs: T, _ rhs: T) -&gt; Bool
where T.Element: Equatable
</code></pre>
<p>至此，<code>anyCommonElements</code>可以很好的比较同一种类型的两个序列是否有交集了，像下面这样的调用都会返回<code>true</code>：</p>
<pre><code class="language-swift">anyCommonElements([1,2,3], [2])
anyCommonElements(1...3, 2...2)
</code></pre>
<p>但是有个问题，如果我们把上面的参数交叉一下，比如像下面这样：</p>
<pre><code class="language-swift">anyCommonElements(1...3, [2])
</code></pre>
<p>就会得到一个报错：</p>
<blockquote>
<p>error: cannot convert value of type '[Int]' to expected argument type 'ClosedRange<int>'</int></p>
</blockquote>
<p>可以看出，此时函数接收到的两个参数不是同一个类型，因而报错。一个好的改进思路是，我们不再要求两个参数同属一个类型，而是允许它们各自不同，但是只需要满足<code>Sequence</code>即可。同时，即使两个序列的类型不同，也要保证它们内部存储的元素类型必须是相同的，不然无法直接比较。改进后的代码如下：</p>
<pre><code class="language-swift">func anyCommonElements&lt;T:Sequence, U:Sequence&gt;(_ lhs: T, _ rhs: U) -&gt; Bool
where T.Element: Equatable, T.Element == U.Element
{
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
   return false
}
anyCommonElements(1...3, [3])
</code></pre>
<hr>
<p>至此关于<strong>泛型</strong>的基本用法就讲完了。作为本系列教程的第十讲，这里我有一个好消息和一个坏消息要告诉大家：</p>
<ul>
<li><strong>好消息</strong>是有了这十讲的技术铺垫，从语言语法层面上来说你已经具备动手进行App开发的基础了。</li>
<li><strong>坏消息</strong>是短短十期所能阐述的知识也只能算是九牛一毛，还有很多概念我们尚未提到，留待你继续探索喽。</li>
</ul>
<p>如果你想了解更多关于<code>App</code>开发的相关知识，可以关注我整理这篇帖子：<a href="https://www.craft.do/s/yIRDioJmEFN6Y1">从0打造一款App并上架苹果App Store我的经历与总结</a>。</p>
<p>好了，关于<code>Swift</code>语法方面的介绍到这里就结束了，也是时候给大家说声<code>青山不改，绿水长流，有缘江湖再见</code>了。期待下次的重逢，也期待能在<code>App Store</code>里看到你提交的应用😃。</p>
<hr>
<h4 id="">参考资料：</h4>
<ul>
<li><a href="https://docs.swift.org/swift-book/LanguageGuide/Generics.html">https://docs.swift.org/swift-book/LanguageGuide/Generics.html</a></li>
<li><a href="https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html">https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html</a></li>
</ul>
<h4 id="">往期回顾：</h4>
<ul>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-1/">Swift 初体验（1）</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-2/">Swift 初体验（2）- 久仰大名 switch</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-3/">Swift 初体验（3）- 函数和闭包</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-4/">Swift 初体验（4）- 对象与类</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-5/">Swift 初体验（5）- 枚举</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-6/">Swift 初体验（6）- 结构体 Struct</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-7/">Swift 初体验（7）- Protocols</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-8/">Swift 初体验（8）- Extension</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-9/">Swift 初体验（9）- 错误处理</a></li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（9）- 错误处理 ]]>
                </title>
                <description>
                    <![CDATA[ 我们的教程已经来到了第九期，还有一期就要结束了。不知道你有没有跟随我的步伐一点点去实践呢，又有没有尝试把自己心里的某个idea用代码的方式呈现出来呢？今天要讲的概念 错误处理，几乎可以说是众多编程语言都离不开的特性，它早已成为编程世界里的一块基石，是构建复杂软件项目都离不开的，从这个角度上来说，它很基础。不过要学习错误处理 需要掌握的系统关键字是很少的，几行代码就可以演示错误处理的过程，从这个角度上来说，它是简单的。 但是包括我在内的很多人，在初学编程的时候，都对错误处理的概念感到有些无所适从，并不是学不会，而是不太容易理解它存在的价值。今天咱们就来念叨下错误处理 背后隐含的编程哲学问题，不过在探讨形而上的问题之前，咱们先来看看形而下的问题，到底错误处理长什么样。 想象这样一种场景，考试不及格（成绩小于60分）属于一种错误，我们可以这样定义： struct 不及格:Error{} 这里我们定义了一个结构体，并且大大方方给它起了中文名（这样当然也是可以的，不用担心程序会报错，只是外国人不太容易看懂罢了。），同时让这个结构体实现了Error 协议。这样我们就拥有了一个错误，你就可 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-9/</link>
                <guid isPermaLink="false">62bebf988ada24082688b353</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Fri, 01 Jul 2022 09:51:12 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/07/swift-og2-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>我们的教程已经来到了第九期，还有一期就要结束了。不知道你有没有跟随我的步伐一点点去实践呢，又有没有尝试把自己心里的某个idea用代码的方式呈现出来呢？今天要讲的概念<code>错误处理</code>，几乎可以说是众多编程语言都离不开的特性，它早已成为编程世界里的一块基石，是构建复杂软件项目都离不开的，从这个角度上来说，它很基础。不过要学习<code>错误处理</code>需要掌握的系统关键字是很少的，几行代码就可以演示<code>错误处理</code>的过程，从这个角度上来说，它是简单的。</p>
<p>但是包括我在内的很多人，在初学编程的时候，都对<code>错误处理</code>的概念感到有些无所适从，并不是学不会，而是不太容易理解它存在的价值。今天咱们就来念叨下<code>错误处理</code>背后隐含的编程哲学问题，不过在探讨形而上的问题之前，咱们先来看看形而下的问题，到底<code>错误处理</code>长什么样。</p>
<p>想象这样一种场景，考试不及格（成绩小于60分）属于一种错误，我们可以这样定义：</p>
<pre><code class="language-swift">struct 不及格:Error{}
</code></pre>
<p>这里我们定义了一个结构体，并且大大方方给它起了中文名（这样当然也是可以的，不用担心程序会报错，只是外国人不太容易看懂罢了。），同时让这个结构体实现了<code>Error</code>协议。这样我们就拥有了一个<code>错误</code>，你就可以在需要它的时候让他登场了，而“登场”就需要一个特殊的操作——<code>throw</code>。</p>
<p>比如我们有一个设置成绩的函数：</p>
<pre><code class="language-swift">func setScore(_ score:Int) throws {
    if score &lt; 60{
        throw 不及格()
    }
        
    print(score)
}
</code></pre>
<p>注意上面函数的声明处有个<code>throws</code>，就是为了通知要调用这个函数的代码，告诉大家“我的逻辑有风险，调用我须谨慎”。所以调用函数的代码就得加个<code>try</code>，像下面这样：</p>
<pre><code class="language-swift">try setScore(90)
try setScore(0)
</code></pre>
<p>而如果你真的关心函数调用如果出现异常，会出现怎样的异常的话，代码还要写得再丰富些才好：</p>
<pre><code class="language-swift">do {
    try setScore(0)
} catch is 不及格{
    print("成绩不及格")
}
</code></pre>
<p>上面的代码相信你都能看懂，只是可能会觉得有点奇怪，绕这么个大圈子，就是想知道成绩是不是不及格，这样的意义在哪呢？别着急，让我们丰富下我们的示例。首先我们定义一个学生：</p>
<pre><code class="language-swift">struct Student{
    let name:String
    let score:Int
    
    init (name:String, score:Int){
        self.name = name
        self.score = score
    }
}

let 张三 = Student(name: "张三", score: 60)
</code></pre>
<p>这个<code>Student</code>有两个常量，分别存储了<code>姓名</code>、<code>成绩</code>。这次我们不关心他是不是考试及格了，我们关心这个<code>Student</code>的两个关键常量是否合法，比如<code>姓名的字数应大于1</code>，<code>成绩不能小于0</code>。</p>
<p>这次我们用<code>enum</code>定义<code>错误</code>会比之前用<code>struct</code>更加实用，在生产环境大家也多倾向于用<code>enum</code>来承载<code>Error</code>。</p>
<pre><code class="language-swift">enum StudentParamError:Error{
    case nameTooShort //姓名太短
    case nameTooLong //姓名太长
    case scoreIsNegative //得分是负数
    case scoreOver100 //得分大于100
}
</code></pre>
<p>现在我们改进下<code>Student</code>的<code>构造函数（init）</code>：</p>
<pre><code class="language-swift">init (name:String, score:Int) throws{
    if name.count &lt;= 1 {
        throw StudentParamError.nameTooShort
    }
    
    if name.count &gt; 4 {
        throw StudentParamError.nameTooLong
    }
    
    if score &lt; 0 {
        throw StudentParamError.scoreIsNegative
    }

    if score &gt; 100 {
        throw StudentParamError.scoreOver100
    }

    self.name = name
    self.score = score
}
</code></pre>
<p>这样我们以后就可以安全的创建<code>Student</code>了，因为万一提供给它的参数有问题，它都可以自我审查，有问题及时抛出，而不需要我们在外围代码进行额外判断了。当然如果你关心<code>Student</code>创建失败的原因，还是要用上面介绍方法去对错误进行<code>catch</code>比较：</p>
<pre><code class="language-swift">var zhangsan:Student?

do{
    zhangsan = try Student(name: "张三", score: 101)
}catch StudentParamError.nameTooShort{
    print("姓名太短")
}catch StudentParamError.nameTooLong{
    print("姓名太长")
}catch StudentParamError.scoreOver100{
    print("成绩大于100")
}catch StudentParamError.scoreIsNegative{
    print("成绩是负数")
}catch{
    print("其他未知问题")
}
</code></pre>
<p>或者你只关心宏观上是不是参数问题导致的，可以把上面的代码简化一下：</p>
<pre><code class="language-swift">var zhangsan:Student?

do{
    zhangsan = try Student(name: "张三", score: 101)
}catch is StudentParamError{
    print("参数有误")
}catch{
    print("其他未知问题")
}
</code></pre>
<p>当然，还有一种情况也是最常见的情况，我不关心具体错误，我现在就要张三现在出来，立刻、马上：</p>
<pre><code class="language-swift">let zhangsan = try? Student(name: "张三", score: 101)
</code></pre>
<p>此时<code>zhangsan</code>的类型就变成可选类型了。如果参数有误或者其他什么原因，构造张三的过程中报错了，那么你将得到一个<code>nil</code>，否则就会把一个完完整整的张三交到你手上。</p>
<p><code>错误处理</code>是有一种传递机制在里面的，在一个函数内部调用其他有潜在风险的代码，你将有两种选择，一种是我不处理错误，我只传递风险，就像下面的代码：</p>
<pre><code class="language-swift">func hello() throws{
    let zhangsan = try Student(name: "张三", score: 101)
    print("\(zhangsan.name)，你好")
}
</code></pre>
<p>另一种就是我会把风险扼杀在摇篮里，不去打扰调用我的人：</p>
<pre><code class="language-swift">func hello() {
    let zhangsan = try? Student(name: "张三", score: 101)
    if let zhangsan = zhangsan{
        print("\(zhangsan.name)，你好")
    }
}
</code></pre>
<p>你能猜到这两种写法对调用者来说最大的区别是什么么？答案是：<code>try hello()</code>与<code>hello()</code>。因为前者并没有规避风险，所以<code>hello</code>函数也是用<code>throws</code>标记了的，因而要调用它的地方，必须加上<code>try</code>以示对风险的重视。而后者的<code>hello</code>函数就很普通，因此也就可以被普普通通的调用。</p>
<hr>
<p>关于<code>错误处理</code>的基本用法就先演示到这里了。下面请大家思考一个问题，是不是只要我的程序能在<strong>理想化</strong>的情况下运行，每一步都不出错，那么我就可以完全不需要<code>错误处理</code>了呢？单就这个问题来说，答案是：<strong>对，是这么回事，如果一切顺利，其实<code>错误处理</code>的机制没有任何价值。</strong> 那么为什么各种语言都要在<code>错误处理</code>费心设计、健全机制呢？</p>
<p>这就牵扯出来一个重要的编程哲学问题，<strong>面向失败编程</strong>。这句话显然不是要劝我们写会运行失败的程序，而是在任何时候，编程都要考虑潜在导致失败的风险，要能捕捉这些风险，控制这些风险。就像优秀的棋手，每下一步棋，不光要思考之后怎样组织进攻，更要思考如何放手，而不至使自己陷入被动的境地。</p>
<p>那么为什么编程一定要考虑这些风险呢？这些风险就这么普遍么？如果你还有这样的疑问，说明你还没有认识到计算机世界以及人类世界的复杂。我们编写的程序，都是跑在<strong>计算机</strong>（此处把手机等各种智能设备、物联网设备类比为广义的计算机），最终被<strong>人</strong>或者其他<strong>计算机</strong>使用的。</p>
<p>这里面先说<strong>人</strong>，就是极为不靠谱的，他们不会按照你的设想操作你的程序，如果没有限制，他们会在输入窗口输入各种匪夷所思的内容，甚至有些<strong>邪恶</strong>的小伙伴会专门输入恶意的内容，期待看到你程序崩溃的样子。</p>
<p>然后再说<strong>机器</strong>，它们虽然没有情感，没有道德层面的好坏之分，但是它们也是不能太相信的存在。毕竟一部计算机由各种配件组成，而每一个配件都有坏掉的风险，比如当你尝试在有坏道的磁盘上读写文件，就会有文件丢失的风险。更何况把<strong>计算机</strong>链接在一起的<strong>网线</strong>，也是极为脆弱，相信你一定在新闻里看到过网线被挖断导致网络设施瘫痪的故事。</p>
<p>而业界频频提及的<strong>分布式</strong>应用，更是要把不靠谱的计算机用不靠谱的网络组成在一起提供程序功能，可想而知，如果放任自流，那么它出问题的概率更是提高了不知道多少倍。</p>
<p>所以如果认真观察，你就会发现，越是优秀的公司，越是优秀的技术方案，越是在对抗风险上不遗余力，甚至他们的软件产品的重要卖点都离不开对抗风险。所以控制风险，是写在每个程序基因里的，也是根植于每个程序员内心之中的。而<code>错误处理</code>，就是这种设计哲学的最直观表达，是每个程序员都要熟悉，并且赖以生存的基石。</p>
<hr>
<h4 id="">参考资料：</h4>
<ul>
<li><a href="https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html">https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html</a></li>
<li><a href="https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html">https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html</a></li>
</ul>
<h4 id="">往期回顾：</h4>
<ul>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-1/">Swift 初体验（1）</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-2/">Swift 初体验（2）- 久仰大名 switch</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-3/">Swift 初体验（3）- 函数和闭包</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-4/">Swift 初体验（4）- 对象与类</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-5/">Swift 初体验（5）- 枚举</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-6/">Swift 初体验（6）- 结构体 Struct</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-7/">Swift 初体验（7）- Protocols</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-8/">Swift 初体验（8）- Extension</a></li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（8）- Extension ]]>
                </title>
                <description>
                    <![CDATA[ 如果我们想给一个struct或者class 拓展功能，最直白的方式就是修改它们的代码，把我们想追加的函数代码写进去即可。但在一些情况下，我们没有这样做的机会，最典型的场景就是我们在使用别人提供的类库，源代码不是我们写的已经被别人封装过了，自然我们也没有机会修改别人的代码来实现我们想要的功能了。 这个时候，就该我们今天的主角Extension出场了。有了Extension我们就可以对已经存在的struct或者class 拓展功能了。比如，我们想判断一个整数是不是奇数，通常简单的做法是这样： func isOdd(num:Int) -> Bool{     return num%2 != 0 } print(isOdd(num: 4)) print(isOdd(num: 5)) 但是如果有了Extension，我们就可以把isOdd函数直接写到Int里了，至少用起来是这样的。 extension Int {     func isOdd() -> Bool{         return self%2 != 0     } ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-8/</link>
                <guid isPermaLink="false">629857e3dbb8cc083a127cc5</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Thu, 02 Jun 2022 07:52:53 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/06/swift-og2-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>如果我们想给一个<code>struct</code>或者<code>class</code>拓展功能，最直白的方式就是修改它们的代码，把我们想追加的函数代码写进去即可。但在一些情况下，我们没有这样做的机会，最典型的场景就是我们在使用别人提供的类库，源代码不是我们写的已经被别人封装过了，自然我们也没有机会修改别人的代码来实现我们想要的功能了。</p>
<p>这个时候，就该我们今天的主角<code>Extension</code>出场了。有了<code>Extension</code>我们就可以对已经存在的<code>struct</code>或者<code>class</code>拓展功能了。比如，我们想判断一个整数是不是奇数，通常简单的做法是这样：</p>
<pre><code class="language-swift">func isOdd(num:Int) -&gt; Bool{
    return num%2 != 0
}

print(isOdd(num: 4))
print(isOdd(num: 5))
</code></pre>
<p>但是如果有了<code>Extension</code>，我们就可以把<code>isOdd</code>函数直接写到<code>Int</code>里了，至少用起来是这样的。</p>
<pre><code class="language-swift">extension Int {
    func isOdd() -&gt; Bool{
        return self%2 != 0
    }
}


print(4.isOdd())
print(5.isOdd())
</code></pre>
<p>明显上面的写法在最终调用时更加符合直觉，也更优雅。</p>
<p>考虑到<code>extension</code>中也可以扩展<code>计算属性（Computed Properties）</code>，我们还可以再进一步：</p>
<pre><code class="language-swift">extension Int {
    var isOdd:Bool {
        return self%2 != 0
    }   
}

print(4.isOdd)
print(5.isOdd)
</code></pre>
<p><code>extension</code>不光可以扩展普通的函数，也可以扩展构造函数。比如下面这个例子：</p>
<pre><code class="language-swift">import Foundation

extension Date{
    init (_ str:String){
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        
        let target = dateFormatter.date(from: String(str))!
        self.init(timeIntervalSince1970: target.timeIntervalSince1970)
    }
}


var aDate = Date("2022-06-01")

print(aDate)
</code></pre>
<p>在上面的例子中，我们给<code>Date</code>增加了一个新的构造函数，可以传入形如<code>yyyy-MM-dd</code>的字符串构建一个<code>Date</code>。</p>
<p>除了普通函数、计算属性以及构造函数。还有一种能够扮演逻辑功能的特殊语法我们之前还没有讲过，就是<code>Subscripts（下标）</code>。我们最常遇到的场景就是数组，你应该已经接触过了，比如这样：</p>
<pre><code class="language-swift">var nums = [1,2,3,4,5]
print(nums[0])
</code></pre>
<p>除了数组之外，即使是<code>struct</code>或者<code>class</code>，只要源码在自己手上，都可以很容易的通过添加<code>subscript</code>获得对下标的支持，以达到类似于数组的使用效果。但是当源码不掌握在自己手里的时候，我们就可以借助<code>extension</code>对现有的<code>struct</code>及<code>class</code>进行改造了。比如下面这段代码：</p>
<pre><code class="language-swift">var str:String = "abc"
print(str[0])
</code></pre>
<p>我们期望程序返回<code>a</code>，但事实上程序会报错。这时我们就可以通过<code>extension</code>自己实现对<code>String</code>功能的增强了：</p>
<pre><code class="language-swift">extension String {
    subscript(digitIndex: Int) -&gt; String {
        if digitIndex &lt; 0 || digitIndex &gt;= self.count{
            return ""
        }
        
        let arr = self.map{String($0)}
        return arr[digitIndex]
    }
}

print("abc"[0])
</code></pre>
<p>有了上面的代码，我们就可以把<code>String</code>当成<code>Array</code>一样，优雅地通过下标的方式获取到对应位置的单个字符串了。如果我们传入的下标不在支持的范围内，那程序就返回一个<code>""</code>（空字符串）。</p>
<p><code>extension</code>可以跟我们上次讲的<code>protocol</code>结合在一起使用，比如这样：</p>
<pre><code class="language-swift">protocol Hello {
   func say()
}

extension Int:Hello{
    func say() {
        print("Hello, \(self)")
    }
}

4.say()
</code></pre>
<p>有了这样的特性，我们就可以让别人写的<code>struct</code>或者<code>class</code>遵照我们定义的<code>protocol</code>行事了。</p>
<p>最后再说一下，<code>extension</code>除了可以拓展功能外，可以拓展静态属性，一个常见的场景是在使用<code>SwiftUI</code>时，需要对一些公共的颜色进行预知，方便之后的使用。那么就可以这样：</p>
<pre><code class="language-swift">import SwiftUI

extension Color {
    static  var background = Color("background")
    static  var separator = Color("separator")
    static  var textColor = Color("text_color")
}
</code></pre>
<p>这样我们在编写<code>UI</code>代码时就可以优雅地访问颜色了：</p>
<pre><code class="language-swift">var body: some View {
    VStack{
        Text("hello")
            .foregroundColor(.textColor)
        Color.separator.frame(width:100, height:2)
    }
    .background(.background)
}
</code></pre>
<hr>
<h4 id="">参考资料：</h4>
<ul>
<li><a href="https://docs.swift.org/swift-book/LanguageGuide/Extensions.html">https://docs.swift.org/swift-book/LanguageGuide/Extensions.html</a></li>
<li><a href="https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html">https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html</a></li>
</ul>
<h4 id="">往期回顾：</h4>
<ul>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-1/">Swift 初体验（1）</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-2/">Swift 初体验（2）- 久仰大名 switch</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-3/">Swift 初体验（3）- 函数和闭包</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-4/">Swift 初体验（4）- 对象与类</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-5/">Swift 初体验（5）- 枚举</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-6/">Swift 初体验（6）- 结构体 Struct</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-7/">Swift 初体验（7）- Protocols</a></li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（7）- Protocols ]]>
                </title>
                <description>
                    <![CDATA[ Protocols顾名思义它是一种约定，在Swift中protocol比较类似于其他面向对象语言中的interface（接口） 。空口说还是比较抽象，我们还是先看代码。 protocol Staff{     func work() } class Programmer:Staff{     func work() {         print("我在写代码")     } } class Designer:Staff{     func work() {       ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-7/</link>
                <guid isPermaLink="false">627e298e60237306d2606c17</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Fri, 13 May 2022 10:12:37 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/05/swift-og2-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><code>Protocols</code>顾名思义它是一种<strong>约定</strong>，在<code>Swift</code>中<code>protocol</code>比较类似于其他面向对象语言中的<code>interface（接口）</code>。空口说还是比较抽象，我们还是先看代码。</p>
<pre><code class="language-swift">protocol Staff{
    func work()
}

class Programmer:Staff{
    func work() {
        print("我在写代码")
    }
}


class Designer:Staff{
    func work() {
        print("我在搞设计")
    }
}

let 张三 = Programmer()
let 李四 = Designer()

张三.work()
李四.work()
</code></pre>
<p>除了开头三行定义<code>protocol</code>的代码，其他的我们之前都用过或者见过类似的。即使不运行代码，你应该也能猜到最后程序的输出内容。可能引起费解的应该是定义<code>Programmer</code>的<code>Programmer:Staff</code>以及<code>Designer:Staff</code>这段，因为我们之前介绍过，如果需要一个类继承另外一个类，就是需要用<code>:</code>做连接的。但是此刻，显然我们不是在做继承，因为前面那个叫<code>Staff</code>就不是<code>class</code>，当然也不能当作<code>父类</code>。<code>Staff</code>前面的关键字亮明了身份，它就是我们今天要介绍的<code>protocol</code>。</p>
<p><code>protocol</code>可以用来规定一组行为，它自己并不告诉我们这些行为具体该怎么做，但是它可以用来约定其他人，要求这些其他人必须把这些事情做到位，这就是<code>protocol</code>的主要用途。</p>
<p>结合上面的例子已经很好理解了。工作需要有<code>职工 Staff</code>，他们的主要能力就是<code>工作 work</code>，但是不同的工种有不同的<code>work</code>内容。所以<code>Programmer</code>、<code>Designer</code>都有不同的<code>work</code>函数的具体实现（例子中就是打印一句话）。因为<code>Programmer</code>、<code>Designer</code>都被<code>Staff</code>约束着，你就可以想象，存在一位老板，他会口头禅就是：“我管你是什么工种，来我这就得给我干活”。</p>
<p>此时，如果我们再创造一个类<code>ProductManager</code>，它也通过<code>:Staff</code>受到约束，但是它就不干活，我们来看看会怎样。</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2022/05/CleanShot-2022-05-13-16.20.13.png" alt="CleanShot-2022-05-13-16.20.13" width="600" height="400" loading="lazy"></p>
<p>如上图，我们没有在<code>ProductManager</code>里提供<code>work</code>函数，此时编译器就会报错了，告诉我们这个<code>ProductManager</code>违背了<code>Staff</code>的约定。</p>
<p><code>protocol</code>不光可以约定别人的函数，也可以约定别人的属性。接着我们的例子说，老板对每位员工都一视同仁，管你是中国人还是外国人，都必须有个工号（<code>jobNumber</code>），这就可以用<code>protocol</code>来实现。</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2022/05/CleanShot-2022-05-13-16.25.37.png" alt="CleanShot-2022-05-13-16.25.37" width="600" height="400" loading="lazy"></p>
<p>但是如果我们贸然对<code>Staff</code>增加一个变量是不行的，就会收到编译器给出的报错，如上图。同时，编译器也给出了一个提示帮我们改进代码，改进过后的代码如下：</p>
<pre><code class="language-swift">protocol Staff{
    var jobNumber:String{get set}
    func work()
}
</code></pre>
<p>当你改完<code>Staff</code>之后，之前的<code>Programmer</code>、<code>Designer</code>也会同时跟着报错，因为编译器检查到它俩已经不在满足之前的约定了，也得与时俱进才行。这个时候，你只要在<code>Programmer</code>、<code>Designer</code>中追加属性<code>var jobNumber: String</code>就可以了。但是因为它俩毕竟是<code>class</code>，但是你声明了一个不为空的变量，又没有构造函数来初始化数据，肯定是说不过去的，编译器还是不愿意放过你，所以你会看到下面的报错：</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2022/05/CleanShot-2022-05-13-16.36.02.png" alt="CleanShot-2022-05-13-16.36.02" width="600" height="400" loading="lazy"></p>
<p>如果你没有忘记之前我们讲过的<code>init</code>方法的话，想修复这个问题其实很容易，最终<code>Programmer</code>代码如下：</p>
<pre><code class="language-swift">class Programmer:Staff{
    var jobNumber: String
        
    init(jobNumber:String){
        self.jobNumber = jobNumber
    }

    func work() {
        print("我在写代码")
    }
}
</code></pre>
<p>那么创建一个程序员的代码也得稍加变动，还以张三为例的话，就得这样写了：</p>
<pre><code class="language-swift">let 张三 = Programmer(jobNumber: "001")
</code></pre>
<p>这里留个小小的思考题给你，如果我想在<code>张三.work()</code>时输出<code>我是工号：001，我在写代码</code>，该怎么修改代码呢？</p>
<hr>
<p>在前面的例子中，我们展示了<code>protocol</code>是如何作用于<code>class</code>的，但其实它也可以作用于<code>struct</code>和<code>enum</code>，主要用法差不多，但是有一点是值得注意的，因为<code>struct</code>、<code>enum</code>毕竟是值类型，默认不具备自己修改自己的能力，在一些特殊场景下还是要注意的。</p>
<p>下面我们就来看一下稍微特殊点的场景，回到前面的<code>Staff</code>的例子，我们再给它加一个属性<code>薪资 salary</code>，以及追加一个函数<code>涨薪 salaryIncrease</code>，调整过的<code>Staff</code>如下：</p>
<pre><code class="language-swift">protocol Staff{
    var salary:Float{get}
    var jobNumber:String{get set}
    
    func work()
    func salaryIncrease(ratio:Float)
}
</code></pre>
<p>有一点注意的是，我们并没给<code>salary``set</code>的机会，因为薪资嘛，岂能让别人说改就改，只有老板通过<code>salaryIncrease</code>才能对员工变动薪资。同时，<code>salaryIncrease</code>的参数我们设计为一个比例值，如果该值为<code>0.1</code>的话，就是<code>涨薪10%</code>的意思。下面我们来优化<code>Programmer</code>，最终代码如下：</p>
<pre><code class="language-swift">class Programmer:Staff{
    var salary: Float
    var jobNumber: String
        
    init(jobNumber:String, salary:Float){
        self.jobNumber = jobNumber
        self.salary = salary
    }

    func work() {
        print("我是工号：\(jobNumber)，我在写代码")
    }
    
    func salaryIncrease(ratio: Float) {
        self.salary = self.salary + self.salary*ratio
    }
    
}
</code></pre>
<p>上面的代码是基于<code>class</code>的，当我们把关键字<code>class</code>改成<code>struct</code>的时候，它就变成结构体了。但是同时，我们会收到一个报错：</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2022/05/CleanShot-2022-05-13-17.16.23.png" alt="CleanShot-2022-05-13-17.16.23" width="600" height="400" loading="lazy"></p>
<p>也就是前面提到的<code>struct</code>默认不能自己改自己的问题。此时你需要声明这个方法是<code>mutating</code>的，从而获得可修改自己的能力。</p>
<pre><code class="language-swift">mutating func salaryIncrease(ratio: Float) {
    self.salary = self.salary + self.salary*ratio
}
</code></pre>
<p>当然这样改了之后，<code>protocol</code>那边也得要改，最终<code>Staff</code>代码如下：</p>
<pre><code class="language-swift">protocol Staff{
    var salary:Float{get}
    var jobNumber:String{get set}
    
    func work()
    mutating func salaryIncrease(ratio:Float)
}
</code></pre>
<p>这样改动之后，如果我们真的想给张三加薪的话，就可以通过下面的代码实现：</p>
<pre><code class="language-swift">var 张三 = Programmer(jobNumber: "001",salary: 1000)
print(张三.salary) //1000.0
张三.salaryIncrease(ratio: 0.1)
print(张三.salary) //1100.0
</code></pre>
<p>顺便说一下，因为<code>class</code>也可以实现<code>protocol</code>，但是约定方法的关键字<code>mutating</code>是不适用<code>class</code>的，那么这样含有<code>mutating</code>的<code>protocol</code>还能用来约定<code>class</code>么？</p>
<p>答案是可以的，但是要注意的是，虽然<code>protocol</code>中的函数是有<code>mutating</code>作为修饰，但是在<code>class</code>中，要把<code>mutating</code>关键字删除就行了。说起来有点绕，这里我们分别用<code>Programmer</code>作为<code>class</code>、<code>Designer</code>作为<code>struct</code>来给大家演示一下，最终代码如下：</p>
<pre><code class="language-swift">protocol Staff{
    var salary:Float{get}
    var jobNumber:String{get set}
    
    func work()
    mutating func salaryIncrease(ratio:Float)
}

class Programmer:Staff{
    var salary: Float
    var jobNumber: String
        
    init(jobNumber:String, salary:Float){
        self.jobNumber = jobNumber
        self.salary = salary
    }

    func work() {
        print("我是工号：\(jobNumber)，我在写代码，我的薪资是\(salary)")
    }
    
    func salaryIncrease(ratio: Float) {
        self.salary = self.salary + self.salary*ratio
    }
    
}

struct Designer:Staff{
    var salary: Float
    var jobNumber: String
        
    init(jobNumber:String, salary:Float){
        self.jobNumber = jobNumber
        self.salary = salary
    }

    func work() {
        print("我是工号：\(jobNumber)，我在搞设计，我的薪资是\(salary)")
    }
    
    mutating func salaryIncrease(ratio: Float) {
        self.salary = self.salary + self.salary*ratio
    }
    
}


let 张三 = Programmer(jobNumber: "001",salary: 1000)
张三.salaryIncrease(ratio: 0.1)
张三.work()



var 李四 = Designer(jobNumber: "002",salary: 1000)
李四.salaryIncrease(ratio: 0.15)
李四.work()
</code></pre>
<p>得到的输出结果会是：</p>
<pre><code>我是工号：001，我在写代码，我的薪资是1100.0
我是工号：002，我在搞设计，我的薪资是1150.0
</code></pre>
<p>上面的代码中还有个小细节，<code>张三</code>我们用<code>let</code>声明，而<code>李四</code>却用的<code>var</code>，这可不是笔误，你能参透其中的原因么？</p>
<p>最后还要强调的是，用<code>:</code>去约定<code>protocol</code>是可以同时实现多个的，但是继承父类，只能继承一个，它们都是用唯一的<code>:</code>来做连接的。如果存在既要继承<code>class</code>又要实现<code>protocol</code>的情况，应该把父类放在紧随<code>:</code>之后，如图：</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2022/05/CleanShot-2022-05-13-17.47.54.png" alt="CleanShot-2022-05-13-17.47.54" width="600" height="400" loading="lazy"></p>
<hr>
<h4 id="">参考资料：</h4>
<ul>
<li><a href="https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html">https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html</a></li>
<li><a href="https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html">https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html</a></li>
</ul>
<h4 id="">往期回顾：</h4>
<ul>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-1/">Swift 初体验（1）</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-2/">Swift 初体验（2）- 久仰大名 switch</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-3/">Swift 初体验（3）- 函数和闭包</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-4/">Swift 初体验（4）- 对象与类</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-5/">Swift 初体验（5）- 枚举</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-6/">Swift 初体验（6）- 结构体 Struct</a></li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（6）- 结构体 Struct ]]>
                </title>
                <description>
                    <![CDATA[ 我们在第四讲 [https://chinese.freecodecamp.org/news/swift-getting-started-4/]时已经介绍过 class和面向对象的相关概念。如果你有其他语言的编程经验，理解面向对象应该不难。今天我们要介绍的struct也就是结构体，乍看之下会觉得和Class 很像，但其实它也是一个古老的概念，早在C语言时代就已存在。不过随着Java、JavaScript、Python 等语言逐渐占据C位，结构体的概念已经很少被人提起了。但是近两年，Go、Rust的呼声日响，struct又逐渐回归大众视野，而Swift因为与C 语言的渊源，自诞生之初就提供了struct的支持。 今天我们就来看看struct，相信通过今天对结构体的学习，不仅能帮你更好地掌握Swift，即使以后遇到其他语言的相关概念，你也可以触类旁通，了然于胸。 先来看一段简单的结构体代码，就用我们在第四讲 [https://chinese.freecodecamp.org/news/swift-getting-started-4/]接触过的案例。 struct Shape {     ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-6/</link>
                <guid isPermaLink="false">6264bd2699ec7406219e7888</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Sun, 24 Apr 2022 03:12:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/04/swift-og2-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>我们在<a href="https://chinese.freecodecamp.org/news/swift-getting-started-4/">第四讲</a>时已经介绍过<code>class</code>和面向对象的相关概念。如果你有其他语言的编程经验，理解面向对象应该不难。今天我们要介绍的<code>struct</code>也就是<code>结构体</code>，乍看之下会觉得和<code>Class</code>很像，但其实它也是一个古老的概念，早在<code>C</code>语言时代就已存在。不过随着<code>Java</code>、<code>JavaScript</code>、<code>Python</code>等语言逐渐占据C位，结构体的概念已经很少被人提起了。但是近两年，<code>Go</code>、<code>Rust</code>的呼声日响，<code>struct</code>又逐渐回归大众视野，而<code>Swift</code>因为与<code>C</code>语言的渊源，自诞生之初就提供了<code>struct</code>的支持。</p>
<p>今天我们就来看看<code>struct</code>，相信通过今天对结构体的学习，不仅能帮你更好地掌握<code>Swift</code>，即使以后遇到其他语言的相关概念，你也可以触类旁通，了然于胸。</p>
<p>先来看一段简单的结构体代码，就用我们在<a href="https://chinese.freecodecamp.org/news/swift-getting-started-4/">第四讲</a>接触过的案例。</p>
<pre><code class="language-swift">struct Shape {
    var numberOfSides = 0
    
    init() {
       print("i am here")
    }
    
    func simpleDescription() -&gt; String {
        return "A shape with \(numberOfSides) sides."
    }
}

var shape = Shape()
</code></pre>
<p>这段代码几乎与我们之前写的一模一样，唯一的区别，就是把定义类的<code>class</code>关键字改成了定义结构体的<code>struct</code>关键字。所以从共性上来说，<code>类</code>和<code>结构体</code>是很相似的。它们都可以定义属性和函数。那么<code>struct</code>到底与<code>class</code>有什么区别呢？要理解这个问题，我们必须搞懂什么是值传递，什么是引用传递。下面我们来看一些简单的代码：</p>
<pre><code class="language-swift">var a = [1,2,3]
var b = a
</code></pre>
<p>第一行定义一个变量<code>a</code>，并设置其值一系列数字组成的数组<code>，第二行定义了变量</code>b<code>，并把</code>a<code>传递给</code>b`。继续：</p>
<pre><code class="language-swift">a.append(4)

print(a)
print(b)
</code></pre>
<p>此时我们把数组<code>a</code>追加一个数字<code>4</code>，然后再打印<code>a</code>、<code>b</code>两个变量的值，你觉得会是多少呢？动手试一下你就知道：</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2022/04/CleanShot-2022-04-24-at-11.06.41.png" alt="CleanShot-2022-04-24-at-11.06.41" width="600" height="400" loading="lazy"></p>
<p>如图，<code>b</code>与<code>a</code>的内容不再相同，也就是说<code>b</code>的值不受<code>a</code>变化的影响。像这样，只是在<code>var b = a</code>的时候，发生了一次<code>a</code>值到<code>b</code>值的传递，之后它俩天各一方再无瓜葛，中间的那次<code>=</code>号赋值就是<code>值传递</code>。</p>
<p>与<code>值传递</code>相反的就是<code>引用传递</code>，这个必须借助<code>class</code>才能演示，我们来看下面的代码：</p>
<pre><code class="language-swift">class Circle{
    var radius:Int
    
    init (_ r:Int){
        self.radius = r
    }
}
</code></pre>
<p>我们定义了一个类，用来描述一个圆形，它有唯一的一个属性<code>radius（半径）</code>，同时我们可以用构造方法直接提供半径的大小。下面我们定义一个圆形：</p>
<pre><code class="language-swift">var a = Circle(1)
print(a.radius)
</code></pre>
<p>显而易见，上面的代码会输出<code>1</code>。那么我们故技重施，在准备个<code>b</code>，然后去修改<code>a</code>的半径，代码如下：</p>
<pre><code class="language-swift">var b = a
a.radius = 2

print(a.radius)
print(b.radius)
</code></pre>
<p>大家觉得输出会是什么呢？建议动手实验一下，其实我们得到的输出是：</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2022/04/CleanShot-2022-04-24-at-11.10.27.png" alt="CleanShot-2022-04-24-at-11.10.27" width="600" height="400" loading="lazy"></p>
<p>对，此时<code>a</code>与<code>b</code>的半径都是<code>2</code>了，也就是说，改变<code>a</code>，也就意味着<code>b</code>的改变，反过来也是一样的，比如我们继续执行：</p>
<pre><code class="language-swift">b.radius = 3
print(a.radius)
print(b.radius)
</code></pre>
<p>这样之后输出也都是<code>3</code>，其实此时已经不需要再纠结是<code>a</code>影响了<code>b</code>还是<code>b</code>影响了<code>a</code>，因为**<code>a</code>就是<code>b</code>、<code>b</code>就是<code>a</code>**，而之所以有这样的结果，就发生在那行<code>var b = a</code>，关键就在于，这个<code>=</code>赋值实现的是<code>引用传递</code>。</p>
<p>简单总结下<code>值传递</code>和<code>引用传递</code>的区别，<code>值传递</code>就是<code>值复制传递</code>，先<code>复制</code>一份再传递；<code>引用传递</code>就是<code>值共享传递</code>，传递是<code>引用</code>，而共享的是<code>值</code>。那么什么是<code>引用</code>，<code>引用</code>和<code>值</code>到底有什么关系呢？</p>
<p>这里给大家举个例子，你想邀请朋友来你家玩，<code>你家</code>这个物理意义上的一个<strong>具体空间</strong>就是一个值。而你为了能让朋友找到你家，你抄了好多小纸条，纸条上写清楚了你家的具体地址。这一张张小纸条，就是<code>引用</code>，把小纸条分给你朋友的过程就是<code>引用传递</code>。</p>
<p>现在关于<code>=</code>起到的两种传递效果大家应该很清楚了。那么什么时候发生的是<code>值传递</code>？什么时候发生的是<code>引用传递</code>呢？其实非常简单，如果是<code>class</code>所制造的各种<code>对象</code>，那么传递就是<code>引用传递</code>，反之就是<code>值传递</code>。</p>
<p>除了<code>值传递</code>和<code>引用传递</code>的区别，<code>struct</code>与<code>class</code>最大的区别就是前者无法享受后者的<code>继承</code>特性。</p>
<p>理解了这些区别之后，真遇到开发的具体场景，你可能还会有疑惑，针对某一个具体场景，到底该选择<code>struct</code>还是<code>class</code>呢？一个简单的技巧告诉大家，就是优先考虑<code>struct</code>，当<code>struct</code>无法满足需求的时候，再用<code>class</code>来替换。因为从<code>struct</code>改成<code>class</code>很容易，只要替换一个关键字就行，但是反之往往会遇到困难。当然如果你真这么操作了，也别忘了前文提到的<code>值传递</code>和<code>引用传递</code>的区别，相关的代码也应检查一下。</p>
<hr>
<h4 id="">参考资料：</h4>
<ul>
<li><a href="https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html">https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html</a></li>
<li><a href="https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html">https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html</a></li>
</ul>
<h4 id="">往期回顾：</h4>
<ul>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-1/">Swift 初体验（1）</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-2/">Swift 初体验（2）- 久仰大名 switch</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-3/">Swift 初体验（3）- 函数和闭包</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-4/">Swift 初体验（4）- 对象与类</a></li>
<li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-5/">Swift 初体验（5）- 枚举</a></li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（5）- 枚举 ]]>
                </title>
                <description>
                    <![CDATA[ 日常生活中，经常会遇到一组组的数据，它们像兄弟姐妹一样，彼此结伴出现。比较常见的如：东、南、西、北、男、女等。这样的数据在软件领域，我们通常会用一种被称为枚举 的类型专门表示。今天我们就来聊聊枚举的使用。 在Swift中，最简单的定义枚举的方式如下： enum Gender {     case male     case female     case other } 这里我们通过enum关键字定义了名为Gender的枚举，用来约束性别的几种可能，这些可能性通过case来逐条声明。其实这三条case 也可以合并成一行书写，像下面这样，也是可以的： enum Gender {     case male , female , other } 定义完枚举类型后，我们来演示下怎么使用它。想象定义一个类，用来描述人类，它包含两个基本字段：age、gender，分别代表年龄和性别。其中age 的类型显而易见，而gender的类型，就用我们前面定义的枚举即可，请看下面的代码： class People {     ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-5/</link>
                <guid isPermaLink="false">623e6f2e7f18d1062895bd38</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Fri, 25 Mar 2022 02:58:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/03/swift-og2-3-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>日常生活中，经常会遇到一组组的数据，它们像兄弟姐妹一样，彼此结伴出现。比较常见的如：<code>东、南、西、北</code>、<code>男、女</code>等。这样的数据在软件领域，我们通常会用一种被称为<code>枚举</code>的类型专门表示。今天我们就来聊聊枚举的使用。</p><p>在<code>Swift</code>中，最简单的定义枚举的方式如下：</p><pre><code class="language-swift">enum Gender {
    case male
    case female
    case other
}
</code></pre><p>这里我们通过<code>enum</code>关键字定义了名为<code>Gender</code>的枚举，用来约束性别的几种可能，这些可能性通过<code>case</code>来逐条声明。其实这三条<code>case</code>也可以合并成一行书写，像下面这样，也是可以的：</p><pre><code class="language-swift">enum Gender {
    case male , female , other
}
</code></pre><p>定义完枚举类型后，我们来演示下怎么使用它。想象定义一个类，用来描述人类，它包含两个基本字段：<code>age</code>、<code>gender</code>，分别代表年龄和性别。其中<code>age</code>的类型显而易见，而<code>gender</code>的类型，就用我们前面定义的枚举即可，请看下面的代码：</p><pre><code class="language-swift">class People {
    var age:Int
    var gender:Gender
    
    init(age:Int, gender:Gender){
        self.age = age
        self.gender = gender
    }
}

let aGirl = People(age:8, gender:.female)
</code></pre><p>在上面代码的最后一行，我们定义了一位8岁的女性，所以她是为小女孩，下面我们尝试在<code>People</code>类里让她开口说话。定义一个<code>sayHello</code>方法，它可以判断自己的性别，然后给出不同的反馈。这里涉及到的知识点，就是对枚举类型的判断。具体请看下面的代码：</p><pre><code class="language-swift">    func sayHello()-&gt;String{
        switch gender{
        case .male:
            return "I am a boy."
        case .female:
            return "I am a girl."
        case .other:
            return "Hello."
        }
    }
</code></pre><p>当上面的方法被定义到<code>People</code>中后，我们就可以这样测试一下效果：</p><pre><code class="language-swift">let aGirl = People(age:10, gender:.female)
print(aGirl.sayHello())
</code></pre><p>这里面要强调一点，枚举一旦参与<code>switch</code>语句判断，它的每一项都要接受比较。比如上面的场景，如果我想说，<code>.other</code>的情况我不关心，那么如果我删除了它，而使代码成为这样的话：</p><pre><code class="language-swift">    func sayHello()-&gt;String{
        switch gender{
        case .male:
            return "I am a boy"
        case .female:
            return "I am a girl"
    }

</code></pre><p>很遗憾，我们收到一个<code>Switch must be exhaustive</code>编译错误，提醒我们要判断完整。如果你实在需要省略部分<code>case</code>的选项的话，可以引入<code>default</code>，来匹配你不关心的选项，比如这样：</p><pre><code class="language-swift">    func sayHello()-&gt;String{
        switch gender{
        case .female:
            return "I am a girl."
        default:
            return "Hello."
        }
    }
</code></pre><p>枚举除了和<code>switch</code>语句走得比较近之外，还经常参与迭代，也就是和<code>for</code>循环关系同样紧密。如果想用<code>for</code>循环遍历枚举的每一项的话，我们需要声明该枚举支持迭代，声明的方式非常简单，需要在类名后用<code>:</code>跟随<code>CaseIterable</code>，代码如下：</p><pre><code class="language-swift">enum Gender : CaseIterable{
    case male , female , other
}
</code></pre><p>我们在之前介绍过，如果用类与类的继承关系，会用到<code>:</code>跟随它要继承的父类。而在上面的用法中，并不是在继承父类，属于比较特殊的情况，这个我们以后再详细介绍。话说回来，只要我们追加了魔法般的<code>CaseIterable</code>之后，我们就可以很方便的对枚举进行迭代了。</p><pre><code class="language-swift">for gender in Gender.allCases{
    print(gender)
}
</code></pre><p>输出的结果为：</p><pre><code>male
female
other
</code></pre><p>这里不要疑惑，虽然打印到控制台的内容是字符串。但是本质上<code>for</code>循环体内的<code>gender</code>对应的还是<code>Gender</code>类型，这跟<code>People</code>定义的<code>gender</code>变量类型是完全相同的。如果你想验证一下的话，可以用下面的代码：</p><pre><code class="language-swift">for gender in Gender.allCases{
    print(type(of:gender))
}

print(type(of: aGirl.gender))
</code></pre><p>所以<code>type</code>函数可以帮我们确定一个参数的类型。那么上面神奇的<code>Gender.allCases</code>，咱们也可以观察下。</p><pre><code class="language-swift">print(type(of: Gender.allCases))
</code></pre><p>你将得到结果<code>Array&lt;Gender&gt;</code>，所以它就是个<code>Array</code>，你可以像把玩其他数组一样把玩<code>Gender.allCases</code>，比如这样<code>print(Gender.allCases.count)</code>可以打印该枚举的大小。</p><p>其实有了<code>switch</code>，有了<code>for</code>，我们就能对枚举做很多事了。但是某些情况下，可能还是有点力不从心。比如我们尝试构造一个星期的枚举。</p><pre><code class="language-swift">enum Week{
    case monday , tuesday , wednesday , thursday , friday , saturday , sunday
}
</code></pre><p>如果我想判断周末是不是到了，诚然可以去跟<code>.saturday</code>、<code>.sunday</code>分别比较一下，也不是不行。但是如果能直接判断一个<code>index&gt;5</code>，那不是很容易么？这就牵扯到枚举类型<code>原始值</code>的概念。在这里，我希望一个星期的每一天，都能对应一个整型数字，所以需要这样写：</p><pre><code class="language-swift">enum Week : Int{ // &lt;--注意这里
    case monday , tuesday , wednesday , thursday , friday , saturday , sunday
}
</code></pre><p>这样我们的每个枚举就拥有了<code>原始值(rawValue)</code>，以<code>.monday</code>为例，可以这么访问<code>print(Week.monday.rawValue)</code>，略显尴尬的是，看到的结果是<code>0</code>。这也可以理解，计算机世界里，牵扯到计数一般都是从<code>0</code>开始走的。所以我们需要手动给<code>monday</code>分配<code>1</code>，也非常简单，就像这样：</p><pre><code class="language-swift">enum Week : Int{
    case monday = 1 , tuesday , wednesday , thursday , friday , saturday , sunday
}
</code></pre><p>如果你想事无巨细地把枚举里的每个元素都分配一个<code>原始值</code>当然是可以的，但其实<code>swift</code>已经很聪明的帮你把枚举中的后续元素以<code>1</code>为起点，逐个的修正好了。想验证的话，我们可以通过前面的<code>for</code>循环，同时别忘了，要在枚举命名处的<code>:</code>后面追加<code>CaseIterable</code>。最后的代码如下：</p><pre><code class="language-swift">enum Week : Int , CaseIterable{
    case monday = 1 , tuesday , wednesday , thursday , friday , saturday , sunday
}

for week in Week.allCases{
    print(week.rawValue)
}
</code></pre><p>有了<code>原始值</code>的支持，我们获取一个具体的枚举元素就可以用另外一个方式了。比如：</p><pre><code>print(Week(rawValue: 7) == Week.sunday)
</code></pre><p>返回的就是<code>true</code>，足以证明通过<code>原始值</code>拿到的枚举与直接点出来的枚举是一回事。你可能会有疑问，没有<code>原始值</code>的枚举明明也可以工作的不错，那么<code>原始值</code>的加入到底能带来什么呢？想象一下，你的业务是跟一个关系型数据库(SQL)打交道的，最终存储在数据库里的数据都是符合数据库标准的，如果你尝试把<code>Week.sunday</code>存进去的话，你甚至不知道该给这个数据列定义什么类型。而有<code>原始值</code>的支持，你就可以在数据库支持的类型与<code>Swift</code>的枚举类型中做优雅的转换工作了。</p><p>还有一点需要注意的是，在上面的例子中，我把<code>原始值</code>的类型定义为<code>Int</code>，只是觉得它正好满足<code>星期几</code>的这种需求。如果你的业务有其他场景，完全可以用其他类型表示<code>原始值</code>。比如下面这个例子：</p><pre><code class="language-swift">enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}
</code></pre><hr><p>在前面的例子中，我们都在用枚举表示某种定数，比如从星期一到星期天，不外乎7种情况。但其实<code>swift</code>的枚举设计得更为强大，可以存储变幻多端的数据，请看下面的代码：</p><pre><code class="language-swift">enum ServerResponse {
    case ok(String)
    case failure(Int , String)
}

let success = ServerResponse.ok("hello world")
let failure = ServerResponse.failure(502,"Bad Gateway")

switch failure {
case let .ok(context):
    print("Response is \(context)")
case let .failure(stateCode , message):
    print("Failure... \(stateCode)  \(message)")
}
</code></pre><p>这里的描述的场景是服务器<code>HTTP</code>返回的数据。如果成功的话，就是<code>ok</code>，并提供服务器返回的内容；如果失败的话，要提供一个状态码，以及错误的详细信息。你可以通过替换上面代码中的<code>switch failure</code>为<code>switch success</code>观察不同的输出效果。</p><p>上面的演示只是<code>枚举</code>能力的冰山一角，更多高阶的功能就等待你自己去探索发觉了。</p><hr><h4 id="-">参考资料：</h4><ul><li><a href="https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html">https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html</a></li><li><a href="https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html">https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html</a></li></ul><h4 id="--1">往期回顾：</h4><ul><li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-1/">Swift 初体验（1）</a></li><li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-2/">Swift 初体验（2）- 久仰大名 switch</a></li><li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-3/">Swift 初体验（3）- 函数和闭包</a></li><li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-4/">Swift 初体验（4）- 对象与类</a></li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（4）- 对象与类 ]]>
                </title>
                <description>
                    <![CDATA[ 面向对象 是一种古老且生机勃勃的编程范式，甚至可以追述到上世纪六十年代。在之前的几篇教程中，我们暂且搁置了类与对象的概念，着重讨论了常量、变量、函数以及闭包，这是因为 Swift给了我们一个机会，能够在Playground的环境中暂时把class放在一边，但最终，我们还要回到面向对象，回到class 的怀抱，它是编写软件的根基，每一个class的声明，就是对蓝图细致的描绘，每一个对象实例的诞生，就是软件的呼吸的律动。 一个简单的class声明如下： class Shape {     var numberOfSides = 0     func simpleDescription() -> String {         return "A shape with \(numberOfSides) sides."     } } 好像并没有什么特别的，一个class关键字后面跟上你想要的名字，再用花括号紧随其后包裹一个区域。在这个区域内，你就可以填上前几节课教的常量、变量与方法了。 上面的代码，仅仅是声明了一个class，如果像使用它，还需要造个对象出来，也就是创建一个该cl ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-4/</link>
                <guid isPermaLink="false">622af601cda7d006365a75de</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Fri, 11 Mar 2022 07:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/03/swift-og2-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><code>面向对象</code>是一种古老且生机勃勃的编程范式，甚至可以追述到上世纪六十年代。在之前的几篇教程中，我们暂且搁置了类与对象的概念，着重讨论了常量、变量、函数以及闭包，这是因为<code>Swift</code>给了我们一个机会，能够在<code>Playground</code>的环境中暂时把<code>class</code>放在一边，但最终，我们还要回到<code>面向对象</code>，回到<code>class</code>的怀抱，它是编写软件的根基，每一个<code>class</code>的声明，就是对蓝图细致的描绘，每一个对象实例的诞生，就是软件的呼吸的律动。</p>
<p>一个简单的<code>class</code>声明如下：</p>
<pre><code class="language-swift">class Shape {
    var numberOfSides = 0
    func simpleDescription() -&gt; String {
        return "A shape with \(numberOfSides) sides."
    }
}
</code></pre>
<p>好像并没有什么特别的，一个<code>class</code>关键字后面跟上你想要的名字，再用花括号紧随其后包裹一个区域。在这个区域内，你就可以填上前几节课教的常量、变量与方法了。</p>
<p>上面的代码，仅仅是声明了一个<code>class</code>，如果像使用它，还需要造个对象出来，也就是创建一个该<code>class</code>的<code>实例</code>，一个简单的例子如下：</p>
<pre><code class="language-swift">var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
print(shapeDescription)
</code></pre>
<p>在第一行，我们创建了一个叫<code>shape</code>的实例，它拥有的变量及函数就是我们在<code>Shape</code>这个<code>class</code>声明的。一个<code>class</code>可以有千千万万个实例，但是每一个实例都享受它自己的特殊性，在这个例子里，就是<code>numberOfSides</code>变量了。为了让<code>shape</code>这个独一无二的对象（实例）拥有它自己的特殊的<code>numberOfSides</code>内容（数字7），就有了上面代码的第二行。</p>
<p>如果我们回过头来审视第一行代码——<code>Shape()</code>，会发现这种写法与函数调用基本没啥区别，只是目前调用这个函数时没有提供任何参数罢了。那么有没有一种可能，在创建对象实例的时候，我们能传点参数进去。用上面的代码举例子，就是我们把原本两行的代码：</p>
<pre><code class="language-swift">var shape = Shape()
shape.numberOfSides = 7
</code></pre>
<p>精简成：</p>
<pre><code>var shape = Shape(numberOfSides: 7)
</code></pre>
<p>这其实是一种很常见的需求，只需要我们在<code>Shape</code>类里面多提供一个方法，最终的<code>Shape</code>代码如下：</p>
<pre><code class="language-swift">class Shape {
    var numberOfSides = 0
    
    init(numberOfSides : Int) {
       self.numberOfSides = numberOfSides
    }
    
    func simpleDescription() -&gt; String {
        return "A shape with \(numberOfSides) sides."
    }
}
</code></pre>
<p>其中<code>init</code>函数就是我们刚刚追加的，这种函数会在对象被构造出来的时候执行，所以这种特殊的函数就叫<code>构造函数</code>。至于函数代码段中的那个<code>self</code>，见文知意，就是说的是实例的自身。<code>self.numberOfSides = numberOfSides</code>的意思就是把函数接收到的参数<code>numberOfSides</code>内容赋值给<code>self.numberOfSides</code>，也就是本实例的变量<code>numberOfSides</code>。还应注意的是，与上节课我们介绍的函数使用类似，如果我们在声明<code>init</code>函数参数的时候前面加个<code>_</code>，就像下面这样：</p>
<pre><code class="language-swift">init(_ numberOfSides : Int) {
   self.numberOfSides = numberOfSides
}
</code></pre>
<p>那么我们就可以在创建<code>Shape</code>实例时，省略参数的名字，像这样：</p>
<pre><code class="language-swift">var shape = Shape(7)
</code></pre>
<p>话说回来，如果你只想在对象被实例化的时候做点什么小动作，但是并不一定需要接收什么参数，其实你可以使用无参数的<code>init</code>函数，比如下面这段代码，我们就只是在<code>shape</code>生成时打印了一句话而已。</p>
<pre><code class="language-swift">class Shape {
    var numberOfSides = 0
    
    init() {
       print("i am here")
    }
    
    func simpleDescription() -&gt; String {
        return "A shape with \(numberOfSides) sides."
    }
}

var shape = Shape()
</code></pre>
<p>既然<code>init</code>是用来解决对象<strong>创建时做点什么</strong>的问题，那么<strong>对象销毁时</strong>，是不是也会有做点什么的需求呢？如果有这样的需求，代码要写在什么地方呢？答案就是<code>deinit</code>。与<code>init</code>一样，他们都是可选的，只在你需要的时候，可以召唤他们。但是与<code>init</code>不同的是，<code>deinit</code>不会接受任何参数，因而它不是一个正儿八经的函数。如果需要在<code>Shape</code>对象销毁时打印一句话，我们可以这样写：</p>
<pre><code class="language-swift">class Shape {
    var numberOfSides = 0
    
    init() {
       print("i am here")
    }

    deinit {
        print("deinit")
    }
    
    func simpleDescription() -&gt; String {
        return "A shape with \(numberOfSides) sides."
    }
}
</code></pre>
<p>想测试上面的<code>deinit</code>，我们就必须想办法销毁一个对象。最简单的办法就是把一个原本存储了对象的变量设置成<code>nil</code>，就像下面这样：</p>
<pre><code class="language-swift">var shape:Shape? = Shape()
shape = nil
</code></pre>
<p>如果你对第一行代码中<code>?</code>感到疑惑，建议再回顾下本系列文章的第一篇。</p>
<p>面向对象的一个明显优势就是可以通过类的<strong>继承</strong>来组织类与类之间的关系，从而大幅减少代码重复率提升代码的可维护性。请看下面的例子：</p>
<pre><code class="language-swift">class NamedShape {
    var numberOfSides: Int = 0
    var name: String

    init(name: String) {
       self.name = name
    }
    
    func simpleDescription() -&gt; String {
       return "A shape with \(numberOfSides) sides."
    }
}
</code></pre>
<p>我们这里准备了一个叫<code>NamedShape</code>的类，它用来存储各种形状，通过<code>init</code>函数，我们要求创建形状的时候，需要给具体的形状起个名字。同时我们的形状也拥有<code>numberOfSides</code>变量，用来记录具体形状拥有的边的数量。</p>
<p>有了<code>NamedShape</code>，当我们考虑创建正方形（Square）这种形状的时候，就会发现正方形本质上也是一种形状，它跟<code>NamedShape</code>有很多共通之处，只是<code>numberOfSides</code>是已被确定的值，应该填入<code>4</code>。来看下面的代码：</p>
<pre><code class="language-swift">class Square: NamedShape {
    var sideLength: Double

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }

    func area() -&gt; Double {
        return sideLength * sideLength
    }

    override func simpleDescription() -&gt; String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
</code></pre>
<p>在代码第一行，声明<code>Square</code>类的时候，我们通过<code>:</code>，明确了<code>Square</code>对<code>NamedShape</code>继承关系。然后<code>Square</code>就拥有了<code>NamedShape</code>已拥有的一切。比如<code>numberOfSides = 4</code>中的<code>numberOfSides</code>就是原本属于<code>NamedShape</code>的变量。而<code>super.init(name: name)</code>其实就是在子类中调用了其父类的<code>init</code>构造函数。因为<code>init</code>这个函数，子类也有，父类也有，所以当子类调用父类的函数时，需要提供<code>super.</code>，用来明确要调用的函数是属于父类的。不光构造方法，如果是要调用父类的其他方法，也是同样的道理，都需要用<code>super.</code>明确。甚至那句<code>numberOfSides = 4</code>也可以写成<code>super.numberOfSides = 4</code>，道理也是一样的。</p>
<p>还应注意的是<code>simpleDescription</code>函数，这里我们在<code>func</code>之前追加了关键字<code>override</code>，是因为<code>NamedShape</code>的<code>simpleDescription</code>函数功能不满足需求，我们想用<code>Square</code>新定义的。但是因为函数名在父、子类中是相同的，所以我们需要在子类中通过<code>override</code>明确覆盖掉父类的同名方法。</p>
<p>在前面的例子中我们演示了一些属于类的变量的使用，比如：<code>numberOfSides</code>，这样的变量跟第一节课讲的没啥区别。当然我们可以在类内定义常量，这些变量和常量，都可以叫做是类的属性，确切的说应该叫<code>存储属性</code>。但是有一种属性非常特殊，我们叫它<code>计算属性</code>，我们来看下面的例子：</p>
<pre><code class="language-swift">class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }

    var perimeter: Double {
        get {
             return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }

    override func simpleDescription() -&gt; String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
</code></pre>
<p>其中<code>perimeter</code>就是一种<code>计算属性</code>，这种<code>计算属性</code>对于使用方来说，就像是普通的属性一样，比如下面这种调用方式：</p>
<pre><code class="language-swift">var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9
print(triangle.sideLength)
</code></pre>
<p>我们通过构造函数，创造了一个名叫<code>a triangle</code>的正三角形，它的边长为<code>3.1</code>，所以其实它的周长是可以被推算出来的，我们就可以省去设置周长的代码。所以在上面代码的第二行，我们可以得到<code>9.3</code>的结果。</p>
<p>但是当我们执行到第三行的时候，如果我们非要把正三角形的周长改成<code>9.9</code>，那么显然相应地正三角形的边长也会发生改变。因而我们在第四行代码可以得到这样的结果：<code>3.0</code>。</p>
<p>所以<code>计算属性</code>的使用，对外围的使用者来说，就像在访问普通的变量一样轻松，没有任何区别。但其实，对类内部来说，<code>计算属性</code>的访问是可以隐含着一系列运算的。具体到上面的例子，当我们相查询<code>perimeter</code>时，其实命中的是<code>get</code>的代码段，而当我们试图给<code>perimeter</code>赋值时，命中的就是<code>set</code>代码段，并且在<code>set</code>代码段内，我们拥有一个天然的局部值<code>newValue</code>，它的类型，就是对应<code>计算属性</code>的类型，它的值，就是我们相给它塞的值，在上面的代码中，就是<code>9</code>。</p>
<p>顺便一说，上面代码中的这段：</p>
<pre><code class="language-swift">get {
     return 3.0 * sideLength
}
</code></pre>
<p>其<code>return</code>关键字也是可以省略的。</p>
<p>同时，如果你想构建一个<strong>只读</strong>的<code>计算属性</code>也是可以的，比如这样：</p>
<pre><code class="language-swift">var perimeter: Double {
    get {
         3.0 * sideLength
    }
}
</code></pre>
<p>那么显然<code>perimeter</code>就只能用来获取，而不可以被赋值了。在构造这种只读属性的时候，代码还可以进一步简化成下面的样子：</p>
<pre><code class="language-swift">var perimeter: Double {
    return 3.0 * sideLength
}
</code></pre>
<p>而且你应该也能猜到，这里的<code>return</code>是可以省略的。</p>
<p>好了，关于<code>Swift</code>中类和对象的基本用法就先介绍到这里，下期我们会介绍枚举和结构体类型。</p>
<p>阅读相关文章：</p>
<p><a href="https://chinese.freecodecamp.org/news/swift-getting-started-1/">Swift 初体验（1）</a><br>
<a href="https://chinese.freecodecamp.org/news/swift-getting-started-2/">Swift 初体验（2）- 久仰大名 switch</a><br>
<a href="https://chinese.freecodecamp.org/news/swift-getting-started-3/">Swift 初体验（3）- 函数和闭包</a></p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（3）- 函数和闭包 ]]>
                </title>
                <description>
                    <![CDATA[ 在Swift中，一个标准的函数创建是这样的： func greet(person: String, day: String) -> String {     return "Hello \(person), today is \(day)." } greet(person: "Bob", day: "Tuesday") 值得注意的是，在调用函数传递参数的时候，我们不光提供了参数的值，同时也提供了参数的名 ，这样确实牺牲了一点开发效率，但是提升了不少代码的可读性，总的来说肯定是利大于弊的。不过即使我们提供了参数的名 ，传递参数的顺序仍然不可更改，在上面的例子中，如果写成greet(day: "Tuesday",person: "Bob")是不可以的。 当然也有一些情况，不传递参数名其实也不影响可读性，比如最常见的print方法，显而易见它的第一参数都是要输出打印的东西，如果我们画蛇添足，明明可以 print("Hello Word!")的，却非要写成print(items: "Hello Word!")就会显得过于鸡肋了。所以Swift 提供一种方式，可以声明这个参数在调用时不需 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-3/</link>
                <guid isPermaLink="false">620c9bf02304130635760841</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Wed, 16 Feb 2022 07:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/02/swift-og2-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在<code>Swift</code>中，一个标准的<code>函数</code>创建是这样的：</p>
<pre><code class="language-swift">func greet(person: String, day: String) -&gt; String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
</code></pre>
<p>值得注意的是，在调用函数传递参数的时候，我们不光提供了参数的<strong>值</strong>，同时也提供了参数的<strong>名</strong>，这样确实牺牲了一点开发效率，但是提升了不少代码的可读性，总的来说肯定是利大于弊的。不过即使我们提供了参数的<strong>名</strong>，传递参数的顺序仍然不可更改，在上面的例子中，如果写成<code>greet(day: "Tuesday",person: "Bob")</code>是不可以的。</p>
<p>当然也有一些情况，不传递参数<strong>名</strong>其实也不影响可读性，比如最常见的<code>print</code>方法，显而易见它的第一参数都是要输出打印的东西，如果我们画蛇添足，明明可以<code>print("Hello Word!")</code>的，却非要写成<code>print(items: "Hello Word!")</code>就会显得过于鸡肋了。所以<code>Swift</code>提供一种方式，可以声明这个参数在调用时不需要提供<strong>名</strong>，就像这样：</p>
<pre><code class="language-swift">func greet(_ person: String, day: String) -&gt; String {
    return "Hello \(person), today is \(day)."
}
greet("Bob", day: "Tuesday")
</code></pre>
<p>在声明函数的时候，在其参数名之前通过<code>_</code>标记一下，即可让这个参数在调用时免去提供参数名的麻烦。并且需要被<code>_</code>的参数，没有顺序要求，像下面这样的代码也是可以的：</p>
<pre><code class="language-swift">func greet(person: String, _ day: String) -&gt; String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob","Tuesday")
</code></pre>
<p>这里还有一个不怎么常用的小技巧，就是<code>_</code>的位置也可以替换成其他内容，从而对这个参数起一个别名，比如这样：</p>
<pre><code class="language-swift">func greet(_ person: String, on day: String) -&gt; String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
</code></pre>
<p>函数的返回值是通过<code>-&gt;</code>标记的，这点在上面的例子上也已经看到了。如果需要返回多个值的话，可以使用<code>Tuple</code>（元组类型），比如这样：</p>
<pre><code class="language-swift">func calculateStatistics(scores: [Int]) -&gt; (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0

    for score in scores {
        if score &gt; max {
            max = score
        } else if score &lt; min {
            min = score
        }
        sum += score
    }

    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
</code></pre>
<p>上面的函数接收一组整型数据作为参数，并计算出这组数据的最大值、最小值以及总和，把这些结果通过封装在在元组中返回，以达到返回多值的效果。</p>
<p>在一个函数内部还可以继续定义函数，并且遵循了大家已知的闭包作用域的规范：</p>
<pre><code class="language-swift">func returnFifteen() -&gt; Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()
</code></pre>
<p><code>Swift</code>中的函数，是遵循<code>first-class</code>原则，也是一等公民，跟其他数据类型一样享受同样的待遇，当然也可以作为一个函数的返回值，比如：</p>
<pre><code class="language-swift">func makeIncrementer() -&gt; ((Int) -&gt; Int) {
    func addOne(number: Int) -&gt; Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)
</code></pre>
<p>我们可以通过<code>print(increment.self)</code>查看<code>increment</code>变量的类型，得到的结果会是<code>(Function)</code>。</p>
<p>函数既然能被用作返回值，当然也可以当作参数进行传递，比如：</p>
<pre><code class="language-swift">func hasAnyMatches(list: [Int], condition: (Int) -&gt; Bool) -&gt; Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -&gt; Bool {
    return number &lt; 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
</code></pre>
<p>像上面这些把一个函数看成传统类型，一会当返回值一会当参数的玩法在其他很多语言里都是有的。对初学者来说，可能理解起来会稍有困难，主要还是因为实践的少，遇到的场景不多。我觉得没关系，你只要留个印象，以后在阅读别人代码的时候，如果遇到了，自己花点时间能读懂就行了。至于融会贯通、学以致用，可以慢慢来，随着经验的积累自然就会好的。</p>
<p>下面我们再来谈谈<code>Closures</code>（闭包），首先我们准备一个数组，就用前面的那个例子：</p>
<pre><code class="language-swift">var numbers = [20, 19, 7, 12]
</code></pre>
<p>如果我想对数组中的每个元素进行一次平方运算，然后获得一个新的数组，首先我需要准备一个能把<code>Int</code>平方的函数：</p>
<pre><code class="language-swift">func square(number: Int) -&gt; Int{
    return number * number
}
</code></pre>
<p>而数组提供了一个叫<code>map</code>的函数，可以让我们方便地对其元素做逐个运算，并把运算结果汇集到新的数组中。所以我们只要对<code>numbers</code>进行如下的运算就可以了：</p>
<pre><code class="language-swift">numbers.map(square)
</code></pre>
<p>这时候我们得到的结果就是：</p>
<pre><code>[400, 361, 49, 144]
</code></pre>
<p>但其实我们可以把声明<code>square</code>和调用<code>map</code>合在一起写，就像这样：</p>
<pre><code class="language-swift">numbers.map({ (number: Int) -&gt; Int in
    return number * number
})
</code></pre>
<p>如你所见，声明专门的<code>square</code>函数或者是用闭包的方式来给<code>map</code>传递参数都是等价的。而后者的魅力在于可以省略掉一些已知的东西，进而简化成这样：</p>
<pre><code class="language-swift">numbers.map({number in number * number})
</code></pre>
<p>这里，我们简化了入参的类型，<code>return</code>关键字，其实我们还可以更进一步，就连参数<code>number</code>也可以用内嵌的参数名<code>$0</code>替代：</p>
<pre><code class="language-swift">numbers.map({$0 * $0})
</code></pre>
<p>当然，如果你的闭包是接收多个参数的，就可以在<code>$0</code>之后继续通过<code>$1</code>、<code>$2</code>...获取其他的参数。比如，如果我们要对<code>numbers</code>排序，就需要提供一个能接收两个参数的闭包，最后写成这样：</p>
<pre><code>numbers.sorted { $0 &gt; $1 }
</code></pre>
<p>运算之后的结果为：</p>
<pre><code>[20, 19, 12, 7]
</code></pre>
<p>如果你仔细观察<code>sorted</code>和<code>map</code>的代码，会发现在<code>sorted</code>调用闭包的时候，我们省略了闭包外面的<code>()</code>，这是因为如果函数调用只有一个参数，并且这个参数是个闭包时，为了简化输入，可以省略函数调用的<code>()</code>。</p>
<p>关于函数和闭包就先介绍到这里了，我们下次见。</p>
<!--kg-card-end: markdown--><p>阅读相关文章：</p><ul><li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-1/">Swift初体验（1）</a></li><li><a href="https://chinese.freecodecamp.org/news/swift-getting-started-2/">Swift 初体验（2）- 久仰大名 switch</a></li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（2）- 久仰大名 switch ]]>
                </title>
                <description>
                    <![CDATA[ switch这个语法，相信所有碰过编程的小伙伴都不会陌生。是在众多老旧语言中，switch 都有着惊人的相似实现。但笔者一直觉得它的存在多少有点鸡肋，让我们先来看看Java中的switch： public class SwitchDemo {     public static void main(String[] args) {         int score = 5;         String scoreLabel;         switch (score) {  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-2/</link>
                <guid isPermaLink="false">61d7d7c7cddf5a0670324b6f</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Fri, 07 Jan 2022 07:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/01/swift-og2-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><code>switch</code>这个语法，相信所有碰过编程的小伙伴都不会陌生。是在众多老旧语言中，<code>switch</code>都有着惊人的相似实现。但笔者一直觉得它的存在多少有点鸡肋，让我们先来看看<code>Java</code>中的<code>switch</code>：</p>
<pre><code class="language-java">public class SwitchDemo {
    public static void main(String[] args) {

        int score = 5;
        String scoreLabel;
        switch (score) {
            case 1:
                scoreLabel = "差评";
                break;
            case 2:
                scoreLabel = "不满意";
                break;
            case 3:
                scoreLabel = "一般";
                break;
            case 4:
                scoreLabel = "还不错";
                break;
            case 5:
                scoreLabel = "非常满意";
                break;
            default:
                scoreLabel = "无效得分";
                break;
        }
        System.out.println(scoreLabel);
    }
}
</code></pre>
<p><code>Java</code>的这段<code>switch</code>应该还是很有代表性的，就是从<code>C</code>延续下来的写法，很多语言都是这么设计的。说实话，这一水的<code>break</code>，看着还是有点难受，也不能说<code>break</code>的设计完全没用，但是如果大部分情况下<code>break</code>都是不能省的（省了逻辑就变了），那这个设计就值得商榷了。<code>Java</code>程序员忍了多少年，情况终于在<strong>2019</strong>年发生了一点改变，以下是<code>Java12</code>带来的新的语法：</p>
<pre><code class="language-java">String scoreLabel = switch (score) {
    case 1 -&gt; "差评";
    case 2 -&gt; "不满意";
    case 3 -&gt; "一般";
    case 4 -&gt; "还不错";
    case 5 -&gt; "非常满意";
    default -&gt; "无效得分";
};
</code></pre>
<p>这样写的确清爽很多，我们再来看看同样的问题，在<code>Swift</code>中怎么实现：</p>
<pre><code class="language-swift">let score = 5
var scoreLabel = ""
switch score{
    case 1:
        scoreLabel = "差评"
    case 2:
        scoreLabel = "不满意"
    case 3:
        scoreLabel = "一般"
    case 4:
        scoreLabel = "还不错"
    case 5:
        scoreLabel = "非常满意";
    default:
        scoreLabel = "无效得分";
}
print(scoreLabel)
</code></pre>
<p>猛一看，<code>Swift</code>这版代码多了好多行，感觉没有<code>Java12</code>这版优雅啊。这是因为<code>Swift</code>里的<code>switch</code>不能当作一个具备返回值的表达式放在等号的右边。但其实我并不觉得这是<code>Swift</code>的缺点。因为在强类型语言中，任何数据的类型都是非常重要的，不可以朦胧不清。在<code>Java12</code>的示例中，<code>scoreLabel</code>是一个<code>String</code>，但是等号右边的<code>switch</code>如何保证自己的返回是一个<code>String</code>呢？作为一个人类，纯看代码，其实并不能明确地看出来。当然，作为一个机器，编译器还是能看出来，比如我们稍微改下<code>Java12</code>这版代码：</p>
<pre><code class="language-java">int score = 5;
String scoreLabel = switch (score) {
    case 1 -&gt; "差评";
    case 2 -&gt; "不满意";
    case 3 -&gt; "一般";
    case 4 -&gt; 3;
    case 5 -&gt; "非常满意";
    default -&gt; "无效得分";
};
System.out.println(scoreLabel);
</code></pre>
<p>在编译代码阶段，我们就会遇到一个报错提示：</p>
<pre><code>java: 不兼容的类型: switch 表达式中的类型错误
    int无法转换为java.lang.String
</code></pre>
<p>所以，不能说<code>Java12</code>的设计有什么问题，反正编译器这边是清醒的。但是我个人总觉得，把等号左边的变量类型通过<code>switch</code>穿透到其内部，来约束<code>switch</code>里面的事情，有越俎代庖之嫌。</p>
<p>下面我们来改进<code>Swift</code>的版本，看看能否得到近似于<code>Java12</code>这般优雅的方式。</p>
<pre><code class="language-swift">let score = 5
let scoreLabel = {()-&gt;String in
    switch (score) {
    case 1 :  return "差评"
    case 2 :  return "不满意"
    case 3 :  return "一般"
    case 4 :  return "还不错"
    case 5 :  return "非常满意"
    default : return "无效得分"
    }
}()

print(scoreLabel)
</code></pre>
<p>我这个写法对于<code>Swift</code>新手来说，稍微有点超纲了。等号右边是一个<strong>立即调用函数表达式</strong>，类似于<code>JavaScript</code>里面的这种写法：</p>
<pre><code class="language-js">var result = (function () {
    var name = "Barry";
    return name;
})();
// IIFE 执行后返回的结果：
result; // "Barry"
</code></pre>
<p>我们说回<code>Swift</code>，你大概能明白<code>scoreLabel</code>的值就是等号右边函数执行过后的返回值。你可能会诧异为什么我写的<code>函数</code>没有名字，因为这里是用<code>Closure（闭包）</code>写成的匿名函数，被花括号包围的整个区域（包括花括号），也就是：<code>{()-&gt;String in …… }</code>都是这个函数的定义，其中单词<code>in</code>左侧的<code>String</code>代表了这个函数的返回值，而<code>in</code>之后的内容就是函数的逻辑代码了。在函数定义的<code>}</code>之后，我放一对小括号<code>()</code>，也就是这对小括号产生了神奇的效果，让函数获得了立即执行的魔力。</p>
<p>如果你不小心漏写了最后的小括号，比如这样：</p>
<pre><code class="language-swift">let score = 5
let scoreLabel = {()-&gt;String in
    switch (score) {
    case 1 :  return "差评"
    case 2 :  return "不满意"
    case 3 :  return "一般"
    case 4 :  return "还不错"
    case 5 :  return "非常满意"
    default : return "无效得分"
    }
}

print(scoreLabel)
</code></pre>
<p>你的得到的结果将会是：</p>
<pre><code>(Function)
</code></pre>
<p>也就是说<code>scoreLabel</code>这个常量存储的内容是个函数。而有了小括号，<code>scoreLabel2</code>里面存的就是函数的返回值了，你将会看到如下输出：</p>
<pre><code>非常满意
</code></pre>
<p>现在回过头来对比下<code>Swift</code>与<code>Java12</code>的两个实现版本，不难看出<code>Swift</code>中通过一次函数返回值的明确声明，锁定了其内部的<code>return</code>语句必须返回<code>String</code>，不失为一个优雅的解决方案，与<code>Java12</code>相比，可谓各有特色。但是考虑下面这种场景，<code>Swift</code>交出来的答卷就有意思了：</p>
<pre><code class="language-swift">score = 3
let scoreLabel3 = {()-&gt;String in
    switch (score) {
    case 1 ... 2 :  return "差评"
    case 3 ... 5 :  return "好评"
    default : return "无效得分"
    }
}()

print(scoreLabel3)
</code></pre>
<p>如果我们只想给得分来个二分类，1到2分是差评、3到5分是好评。用上面的代码就会非常直观。并且<code>case 1 ... 2 :</code>在此也可以写为<code>case 1 ..&lt; 3 :</code>，同样具有不错的表现力。</p>
<p>写到这一步，<code>Java</code>其实就已经力不从心了。不过下面的代码才是<code>Swift</code>独享的炫技时刻：</p>
<pre><code class="language-swift">let vegetable = "red pepper"
switch vegetable {
    case "celery":
        print("Add some raisins and make ants on a log.")
    case "cucumber", "watercress":
        print("That would make a good tea sandwich.")
    case let x where x.hasSuffix("pepper"):
        print("Is it a spicy \(x)?")
    default:
        print("Everything tastes good in soup.")
}
</code></pre>
<p>这里是一位小伙伴在表达自己对各种蔬菜看法。我们先抛开他的个人喜好，注意代码中的第三段<code>case</code>。它专门用来判断<code>vegetable</code>是否以<code>pepper</code>字样结尾，在本示例中，最终输出的代码为：</p>
<pre><code>Is it a spicy red pepper?
</code></pre>
<p>通过这段输出，你应该已经明白<code>x</code>的内容就是<code>red pepper</code>，也就是说<code>x</code>即是<code>vegetable</code>。并且这里的<code>x</code>仅仅是个代号，你可以换成任何其他你愿意使用的变量名。也就是说，<code>case</code>后面的这个<code>let</code>，就像魔术师的手，响指一打，你就会获得需要做<code>switch</code>的判断的那个值。有了这个值，我们就可以基于它构建一个能够计算出布尔值的表达式，并把这个表达式放在<code>where</code>之后。这样，当表达式的结果为<code>true</code>时，这条<code>case</code>语句也就命中了。并且不要忘记，刚才魔术师帮你拿到的<code>x</code>，是可以在<code>case</code>语句执行逻辑的区域内使用的。</p>
<p>显然<code>swift</code>的这种<code>switch</code>用法，给我们增加了很多的想象力。不过也许有人会说，这样的<code>switch</code>是不是与<code>if</code>的功能重叠有点多了？我觉得确实是这样，最早<code>switch</code>被设计成一种<code>if</code>的简化版本，主要用来针对<code>等值</code>判断的，但是显然程序员们的愿望不止如此。所以我们才会在本世纪的一些新鲜语言里，看到更为激进的表现形式。</p>
<p>不过，语言层面提供的这些东西，就像佐料，至于真正做菜的时候加什么，放多少，还得是你这位程序员说的算。</p>
<!--kg-card-end: markdown--><p>阅读相关文章：<a href="https://chinese.freecodecamp.org/news/swift-getting-started-1/">Swift初体验（1）</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Swift 初体验（1） ]]>
                </title>
                <description>
                    <![CDATA[ Swift是一门苹果公司于2014年首次推出的语言，主要应用领域就是苹果公司各种设备的软件开发。从手机到手表，从电视盒子到电脑，只要是苹果自家的产品，统统囊括。所以，这当然是融入苹果生态开发的必备编程语言。讲到这，可能有小伙伴会想，我对苹果生态的软件开发完全不感兴趣，所以这门语言就和我没关系了呗？ 先别着急关掉这个页面。本文的目的就是让程序员朋友们能一窥Swift的优秀设计，了解一下这门年轻的语言值不值得学习。 在正式进入到Hello World!之前，先容我介绍几个Swift的非编程特性： > 如果你对这段不感兴趣，往后翻可以直接到编码阶段 1. 开源 是的，这是一门开源的语言，采用Apache License 2.0许可。代码就在GitHub [https://github.com/apple/swift] ，并且此时此刻的Star数为58.3k，着实不低了。 2. 跨平台 请不要惊讶，作为一门苹果公司推出的，用来开发苹果生态产品的编程语言居然是跨平台的。除了macOS， 它还支持Linux与Windows，更夸张的是即使是iOS 的老对手Android，这门语言也是支 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/swift-getting-started-1/</link>
                <guid isPermaLink="false">61cd5366d97f9306419f50e1</guid>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Thu, 30 Dec 2021 07:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/12/swift-og2-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Swift是一门苹果公司于2014年首次推出的语言，主要应用领域就是苹果公司各种设备的软件开发。从手机到手表，从电视盒子到电脑，只要是苹果自家的产品，统统囊括。所以，这当然是融入苹果生态开发的必备编程语言。讲到这，可能有小伙伴会想，我对苹果生态的软件开发完全不感兴趣，所以这门语言就和我没关系了呗？</p>
<p>先别着急关掉这个页面。本文的目的就是让程序员朋友们能一窥Swift的优秀设计，了解一下这门年轻的语言值不值得学习。</p>
<p>在正式进入到<code>Hello World!</code>之前，先容我介绍几个Swift的非编程特性：</p>
<blockquote>
<p><em>如果你对这段不感兴趣，往后翻可以直接到编码阶段</em></p>
</blockquote>
<h3 id="1">1. 开源</h3>
<p>是的，这是一门开源的语言，采用<code>Apache License 2.0</code>许可。代码就在<a href="https://github.com/apple/swift">GitHub</a>，并且此时此刻的Star数为<code>58.3k</code>，着实不低了。</p>
<h3 id="2">2. 跨平台</h3>
<p>请不要惊讶，作为一门苹果公司推出的，用来开发苹果生态产品的编程语言居然是跨平台的。除了<code>macOS</code>， 它还支持<code>Linux</code>与<code>Windows</code>，更夸张的是即使是<code>iOS</code>的老对手<code>Android</code>，这门语言也是支持的。毕竟开源的力量是无穷的。</p>
<p>不过这里要泼个冷水，支持是一回事，好用又是另外一回事。并且在非苹果自家的平台上运行，因为对应UI库的缺失。<code>Swift</code>更适合扮演<strong>后端</strong>编程语言的角色。</p>
<h3 id="3">3. 活跃</h3>
<p>毕竟是一门年轻的语言，截至2021年末，最新的<code>Swift</code>最新的版本为<code>5.5.2</code>，侧面体现了这门语言的更新之频繁。</p>
<p>凡事都要两面看，“活跃”带来的未必都是好事，过分的“活跃”发版，意味着多变，很多东西没有沉淀，同时也意味着程序员需要付出更多的精力才能跟上这门语言的节奏。不过好在经过几年的野蛮生长，目前<code>Swift</code>的特性演进已经放缓，这点从<code>5.0</code>的版本发布于2019年初即可看出端倪。</p>
<h3 id="4">4. 苹果公司主导</h3>
<p>这也值得一说么？别误会，我并不是要吹嘘苹果公司如何如何。而是想聊聊关于<strong>成就</strong>与<strong>包袱</strong>的哲学。</p>
<p><em>对于一个人，过去曾给他带来荣耀的成就，最终都会变成某种包袱伴其左右，对于一个公司亦如是</em>，这话不是鲁迅说的，是我自个儿想的。当你看到多少功成名就的人固步自封，甚至被捧杀后又跌落神坛；又有多少公司尾大不掉，深陷泥潭，最终被历史淘汰，或许能理解我的这句话。</p>
<p>就拿我最喜欢的语言<code>Java</code>来说，给世人的印象就是<strong>保守</strong>，它有广袤的生态，相对悠久的历史，储量很大的程序员队伍，这些是它的<strong>成就</strong>，恰恰也是它的<strong>包袱</strong>。生态的繁荣，导致很多早已<code>Out</code>的解决方案还能苟延残喘；悠久的历史导致生产环境有很多现存的系统，即使千疮百孔，但无人敢碰，只能继续延用老的架构；庞大的程序员队伍，就像物理定律一般，<em>质量越大，惯性越大</em>，在巨大惯性的作用下，大家你看看我，我看看你，然后心安理得地选择了固步自封。就是因为拖着这些包袱，<code>Java语言</code>或者说<code>Java生态</code>迟迟不能跟过去切割，总给人一种<strong>迟暮</strong>与<strong>臃肿</strong>之感，即使这些刻板印象，只是某种未加深入了解的错觉，但也在无形中矮化了其在新晋程序员心目中的印象。</p>
<p>我们再回头看看<code>Swift</code>的缔造者——苹果公司。这是一个不论在消费者还是在生产者（程序员）队伍中都极具号召力的公司。并且它很有主见，做事不拖泥带水。笔记本接口改革，砍掉<code>HDMI</code>，砍掉<code>SD卡槽</code>，说砍就砍，说加就加。丝毫不影响销量。每年<code>iOS</code>大版本更新，升级率也傲视整个手机界。所以，苹果公司是一个敢于跟过去<strong>切割</strong>的公司，在多年的品牌文化熏陶下，它的用户们也早已习惯了这种“切割”。同时围绕着苹果生态的程序员，大都跟苹果公司的消费者一样，拥有异于其他技术栈的粘性，毕竟要入坑苹果生态开发，需要买个Mac，十有八九还要注册个苹果开发者账号，年租金688，这些可都是真金白银的投入，就算是图个心理安慰，他们追随苹果公司脚步的意愿也不是其他技术栈程序员可比拟的。</p>
<p>当然，最令其他公司难望项背的实力还是苹果公司对上下游无与伦比的掌控力。一个技术点，从硬件设计，到操作系统升级与API开放，再到号召程序员跟进完全都受其领导。比如<code>iOS13</code>新增的文档扫描API，显然依赖于苹果之前在其芯片中对机器学习算力单元的布局，一旦硬件铺开，苹果就可以直接在对应的<code>iOS</code>版本中提供<code>API</code>，而广大开发者几乎都会迅速跟进，早在<code>iOS</code>的<code>beta</code>阶段就可以试水磨练自己的<code>App</code>。等开发者和苹果的<code>beta</code>系统磨合得差不多了，随着正式版<code>iOS</code>向全球用户推送，数月之内广大的终端消费者都会陆续更新他们的<code>iOS</code>版本——对，你可能会说，肯定会有不少年龄大的或者不会更新系统版本的消费者。可是这些消费者恰好也跟愿意在<code>App Store</code>花钱购买<code>App</code>的消费者不重合啊——所以你看到没？这是一个运作良好的正反馈循环。</p>
<p>只要回头看看<code>Android</code>开发，你就会明白这个反馈循环是开发者梦寐以求的。在<code>Android</code>平台上，不管<code>Google</code>拿出什么杀手锏级别的<code>API</code>，广大开发者们都没有勇气在第一时间跟进。因为谁也不知道那些造手机的硬件厂商会在什么时间跟进<code>Android</code>大版本。而只要有那么一两个主流手机厂商更新慢了，那么软件开发者就不愿，或者不敢过早的介入新版<code>API</code>的开发，因为没有人愿意陷入兼容性调试的噩梦以及多版本代码并线开发的深渊。显而易见的，这就是一个负反馈循环，作为<code>Android</code>的供应商<code>Google</code>空有一番力却使不出来，而广大软件开发者们亦是多方掣肘牵绊太多。</p>
<hr>
<p>好了，关于主观的铺垫就讲到这里。下面我们该进入<code>Swift</code>的编程世界了。本文主要专注语言语法本身的介绍，所以不会过多介绍开发环境的搭建以及IDE的使用。如果你需要动手尝试<code>Swift</code>的话，最好有个<code>Mac</code>然后安装好<code>Xcode</code>，或者你有个<code>iPad</code>，也可直接安装<code>Swift Playgrounds</code>，相信第一眼看到这个截图就会被它所吸引。</p>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2021/12/CleanShot-2021-12-30-at-11.33.17.png" alt="CleanShot-2021-12-30-at-11.33.17" width="1043" height="759" loading="lazy"></p>
<p>如果你是Windows或者Linux等非苹果生态的用户，可以在这里找到相应平台的安装包，<a href="https://www.swift.org/download/">https://www.swift.org/download/</a> ，祝你好运，我们进入正题。</p>
<hr>
<h4 id="1helloworld">1. Hello World!</h4>
<pre><code>print("Hello, world!")
</code></pre>
<h4 id="2">2. 变量与常量</h4>
<pre><code>var myVariable = 42
myVariable = 50
let myConstant = 42
</code></pre>
<ul>
<li>var 代表变量</li>
<li>let 代表常量</li>
<li>在提供数据的情况下，类型可以自动推断</li>
<li>如果你需要指定类型，可以像这样</li>
</ul>
<pre><code>let explicitDouble: Double = 70
</code></pre>
<p>如果你想直接组合一个字符串和数字，就会报错，像这样：</p>
<pre><code>let label = "The width is "
let width = 94
let widthLabel = label + width //🙀Binary operator '+' cannot be applied to operands of type 'String' and 'Int'
</code></pre>
<p>但是你可以这样：</p>
<pre><code>let widthLabel = label + String(width)
</code></pre>
<p>还有一种更优雅的方式把值整合进字符串，像这样：</p>
<pre><code>let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
</code></pre>
<p>当然多行字符串也是一门现代语言所必备的技能：</p>
<pre><code>let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""
</code></pre>
<h4 id="3">3.数组与字典</h4>
<p>用中括号可以快速定义数组：</p>
<pre><code>var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"
shoppingList.append("blue paint")
</code></pre>
<p>如果你需要定义一个字典类型（在<code>Java</code>里它比较像<code>HashMap</code>）可以这样：</p>
<pre><code>var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
 ]
occupations["Jayne"] = "Public Relations"
</code></pre>
<p>如果是定义空的数组或字典，可以这样：</p>
<pre><code>let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]
//当然被声明为let，就不可添加元素喽
</code></pre>
<p>遍历数组也很简单，像这样：</p>
<pre><code>let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score &gt; 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore) //会输出：11
</code></pre>
<h4 id="4optionalnil">4. Optional与nil</h4>
<p>我想每一个使用面向对象语言编程过的人，都与空指针之类的报错战斗过。作为一个<code>Java</code>程序员，<code>NullPointerException</code>绝对是我这辈子遇到过最多的报错，没有之一。</p>
<p>好在<code>Swift</code>从语法层面解决了这个问题。<br>
如果一个变量，我还没想好放什么值，先用<code>nil</code>（类比为<code>Java</code>里的<code>null</code>）顶一下。</p>
<pre><code>var x:String = nil //🙀'nil' cannot initialize specified type 'String'
</code></pre>
<p>你将得到一个报错，也就是说<code>String</code>里面是必须有内容的。如果你执意如此，就得这样写：</p>
<pre><code>var x:String? = nil
</code></pre>
<p>这样你得到的就不是<code>String</code>了，而是一个<code>Optional</code>，它像一种包装器，把一个<code>String</code>装了进去。但是当你要使用它的时候，因为拿到的是个盒子，你也不知道里面有没有<code>String</code>，所以就需要多一步，把包装打开确认里面有<code>String</code>才行。我们来看下官方示例：</p>
<pre><code>var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
</code></pre>
<p>代码中的第三行，就体现了拆包的动作，通过一次<code>let</code>操作，就把<code>optionalName</code>拆成了<code>name</code>，而这个<code>name</code>就是彻头彻尾的<code>String</code>类型了，在花括号范围内可以任意使用， 不用担心<code>nil</code>的问题。如果你觉得平白无故多了个叫<code>name</code>的东西有点碍眼，上面的代码还可以写成这样：</p>
<pre><code>var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let optionalName = optionalName {
    greeting = "Hello, \(optionalName)"
}
</code></pre>
<p>还有一种场景，就是在遇到<code>nil</code>的时候提供一个默认值，比如这样：</p>
<pre><code>let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"
</code></pre>
<p>你大概也能猜到，如果<code>nickname</code>有值，就用它，反之，就用<code>fullName</code>拼装到<code>informalGreeting</code>中。</p>
<hr>
<p>讲到这，你有没有觉得这门语言似乎有点意思了，希望你可以动手体验一下<code>Swift</code>编程。咱们下节课见喽。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 你不知道的Java ]]>
                </title>
                <description>
                    <![CDATA[ 本文记录了JVM及Java生态中一些有意思的事情，希望能带给你一个耳目一新的Java。 Java真的有点老了，之前我们不觉得，但是当95后程序员看着一门比他们年纪还大的语言的时候，那种感觉，应该跟我这样的80后看到C++的感觉差不多。但是你也不要被这种“老”所误导，Java仍然是一门相当活跃的语言，这种活跃体现在官方发版和社区生态两个方面。尤其是2017年以后，Java也是发车式release，一年两班车，版本必须发，赶得上的特性就上，赶不上就等一下班。 我觉得喜欢技术的人，没人会抗拒这种激进的发版策略。但是公司就不一样了，现在我司所有的Java程序还跑在Java8上，还想在生产环境用Oracle的Java8的话就有风险了，不付费的话，更新补丁啥的想都不要想。 这就引出了我要讲的第一个地方： 除了Oracle的Java，还有的选么？ 有，不仅有，而且非常多。这里我随便列几家（摘自sdk list java [ https://sdkman.io]）：  * AdoptOpenJDK  * Amazon  * Azul Zulu  * BellSoft  * SAP  * Tr ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/java-you-have-not-know-about/</link>
                <guid isPermaLink="false">5fb156d35f583f05650910fa</guid>
                
                    <category>
                        <![CDATA[ Java ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 后端开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 编程语言 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Thu, 18 Nov 2021 10:10:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/photo-1497515114629-f71d768fd07c-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><em>本文记录了JVM及Java生态中一些有意思的事情，希望能带给你一个耳目一新的Java。</em></p><p>Java真的有点老了，之前我们不觉得，但是当95后程序员看着一门比他们年纪还大的语言的时候，那种感觉，应该跟我这样的80后看到C++的感觉差不多。但是你也不要被这种“老”所误导，Java仍然是一门相当活跃的语言，这种活跃体现在官方发版和社区生态两个方面。尤其是2017年以后，Java也是发车式release，一年两班车，版本必须发，赶得上的特性就上，赶不上就等一下班。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/11/diagramm-oracle-1.png" class="kg-image" alt="diagramm-oracle-1-w438" width="600" height="400" loading="lazy"></figure><p>我觉得喜欢技术的人，没人会抗拒这种激进的发版策略。但是公司就不一样了，现在我司所有的Java程序还跑在Java8上，还想在生产环境用Oracle的Java8的话就有风险了，不付费的话，更新补丁啥的想都不要想。</p><p>这就引出了我要讲的第一个地方：</p><h3 id="-oracle-java-">除了Oracle的Java，还有的选么？</h3><p>有，不仅有，而且非常多。这里我随便列几家（摘自<a href=" https://sdkman.io">sdk list java</a>）：</p><ul><li>AdoptOpenJDK</li><li>Amazon</li><li>Azul Zulu</li><li>BellSoft</li><li>SAP</li><li>TravaOpenJDK</li></ul><p>是不是有点意思了？</p><p>当然这都源于06年的时候，Sun公司做的一个无比英明的决定，就是搞开源版的Java，后来便有了OpenJDK项目，真是大写的服字。那年笔者刚上大学，不过还没等我大学毕业，Sun公司转眼就被Oracle给收购了，怎不令人唏嘘？</p><p>顺便再提一下这两天刚发生的有意思的事情：Apple公司不是在几天前（2020-11-11）刚刚发布了他们基于ARM架构的新Mac么，而就在前不久，微软站出来说给Java贡献代码，为了实现Java向Apple的新架构的适配。我还在新闻下面看到一个评论，大意是说曾几何时，微软的JVM还是特别厉害的JVM实现呢。</p><p>所以，Java虽然名义上是Oracle的，但因其开源的历史渊源，其实很多公司都对它投入了相当大心血，包括很多大佬级别的公司，比如华为公司也是贡献过不少代码的。这些全球顶尖的公司，共同维持着Java世界的秩序。现在Java的迭代速度又坐上火箭了，所以网上那些非要较真引战的伪技术帖，动不动就唱衰Java，我觉得还为时过早。</p><p>如果你是名前端程序员，想学学后端，技多不压身想法的话，我觉得后端学什么都挺好；但是如果你是打算吃后端这碗饭，我个人还是首推Java的。</p><p>但是Java写起项目来，仪式感真是太强了（贬义），对新人极度不友好。有时候别说是新人了，那种充满了仪式感的样板语言，还有言Java必Spring的无脑式安利，对老人也是一种摧残。</p><h3 id="-spring-">这里我推荐几个框架，让大家从Spring的樊笼中解放出来</h3><ol><li> <a href="https://sparkjava.com">SparkJava</a></li></ol><ul><li>上手难度：★☆☆☆☆</li><li>学习曲线：★☆☆☆☆</li><li>功能强大：★★☆☆☆</li><li>场景推荐：入门学习或者个人微型项目使用</li></ul><p>2. &nbsp; &nbsp;<a href="https://micronaut.io">Micronaut</a></p><ul><li>上手难度：★★☆☆☆</li><li>学习曲线：★★★☆☆</li><li>功能强大：★★★★☆</li><li>场景推荐：有Java Web开发经验的团队使用</li></ul><p>3. &nbsp; &nbsp; <a href="https://vertx.io">Vert.x</a></p><ul><li>上手难度：★☆☆☆☆</li><li>学习曲线：★★★☆☆</li><li>功能强大：★★★★★</li><li>场景推荐：有异步编程基础的个人或团队，需要应对高并发项目使用</li></ul><p>4. &nbsp; &nbsp;<a href="https://quarkus.io">Quarkus</a></p><ul><li>上手难度：★★☆☆☆</li><li>学习曲线：★★★★☆</li><li>功能强大：★★★★★</li><li>场景推荐：离不开Java生态，团队战力强，做云原生项目使用</li></ul><p>以上几款框架都是发展态势非常好的框架，笔者多多少少也在项目里用过，所以就根据自己的经验做了推荐。具体还得各位看看文档，自己斟酌食用。</p><p>当你打开上面几个框架的官网，应该还会发现另外一个有意思的事情——几乎这些框架都支持多种语言。我个人最喜欢Vert.x，它也是最神勇的一个。来看看它支持的语言吧，下面是其官网的截图：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/11/16054538781615-1.jpg" class="kg-image" alt="16054538781615-1" width="600" height="400" loading="lazy"></figure><p>醒目的“Polyglot”，相当给力的多语言支持。这又是怎么回事呢？咱们开始下一个议题。</p><h4 id="java-java">Java不止Java</h4><p>我们都知道Java程序实际上是靠Java虚拟机，也就是JVM解释执行Java字节码运行的（当然也有例外，这个以后有机会展开说）。所以用什么语言写代码其实反而没那么重要，只要最后经过编译，能搞成字节码在JVM上执行就行了。微软也是这么干的鼻祖，近几年前端JS、TS也得编译编译，最后被浏览器执行，我觉得也都是这么个意思。</p><p>那么，在Java生态里，可以编译成字节码被JVM运行的语言有多少呢？我列几个给大家看看：</p><ul><li>Ruby对应的JRuby</li><li>Python对应JPython</li><li>Groovy</li><li>Scala</li><li>Clojure</li><li>Kotlin</li></ul><p>上面这几个兄弟，在JVM面前，跟老大哥Java语言基本上也平起平坐了，用起来都算方便的。可以根据自己的经验，比如之前用Ruby、Python什么的可以继续用着；而搞科研的一般Scala用得比较多，不过你要是没听过，基本也就别用了，曲高和寡啊；Kotlin现在如日中天，如果你除了是全栈，也想搞搞全端开发，Kotlin还是很推荐的；我自己呢，Groovy用得比较多，这是一款动静两相宜的语言，用过都说好。</p><p>对比一下上一小节的图片，唯独少了JavaScript，这又是怎么回事呢？这个又是个有意思的事了。</p><h4 id="java-javascript">Java与JavaScript</h4><p>网上经常拿雷锋与雷峰塔类比这哥俩，我是觉得说得有点太伤感情了。毕竟Java与JavaScript还是有不少历史渊源的，至少后者当年定名是为了蹭Java的热度来的，有兴趣的小伙伴可以自己查查当年的故事，也是不错的。</p><p>更何况现在地球人都知道Java是Oracle的，毕竟Oracle因为Google Android侵权Java而起诉的事情闹得沸沸扬扬也有年头了——9行代码引出的血案。但是JavaScript的商标属于哪个公司，不知道大家都知道么？<strong>答案还是Oracle</strong>。</p><p>上面都是些历史或者商业上的事，跟技术到底有啥关系呢？其实也没啥关系。不过在JVM里面有个叫<strong>Nashorn</strong>的东西又是一个超神的存在，它居然是一个嵌在JVM里面的JavaScript引擎。说白了，JavaScript代码是可以跑在Java环境下的。来看下面的例子：</p><pre><code class="language-javascript">var hello = function() {
  print("Hello Nashorn!");
};

hello();</code></pre><p>把上面的内容保存到hello.js文件里。如果你的电脑装过Java，可以通过<strong>jjs</strong>命令调用上面的脚本文件，如图：<br></p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/11/16054567055477-1.jpg" class="kg-image" alt="16054567055477-1" width="600" height="400" loading="lazy"></figure><p><br>惊不惊喜，意不意外？不过如果你安装了最新版本的Java 15的话，就不用了这个功能了，因为这个功能在15里被拿掉了。不过，考虑这个叫Nashorn的引擎从Java 8开始就一直存在了，至少从2014年到现在也有6年了，事实上不论是JavaScript程序员还是Java程序员都极少知道它的存在，建议你现在可以就地问问你身边写Java代码的朋友知不知道这回事；）</p><h4 id="-">结语</h4><p>好奇心重的小伙伴可能会问了：Nashorn被拿掉之后，Oracle难道就没什么后着？还有前面说Java主要就是JVM解释字节码执行代码的时候，留了个伏笔，暗示还有不一样的玩法存在。下一回，我来给大家讲讲目前一个超级有意思的项目，一个既能颠覆Java自己，又能影响整个语言生态的东西——<strong>GraalVM</strong>。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 在 JavaScript 中利用尾递归来优化执行效率 ]]>
                </title>
                <description>
                    <![CDATA[ 递归是编程过程中常用的手段，跟许多初学者想象不同的是，我们使用递归，恰恰是因为递归的直觉性，能够很好的表达设计代码的逻辑思路，易于他人理解代码。 但是递归的代价也很明显，在Java中一个最典型的错误就是StackOverflowError，当你对一个factorial（阶乘）递归函数传入参数10000 时，就可以遇到它了。 当然，在JavaScript也是会遇到类似的问题的。请看下面这段代码： function factorial(n) {     if (n < 2) {         return 1;     }     return n * factorial(n - 1); } 这就是一段非常典型的递归调用，可以算出n的阶乘。不加优化的情况下，这个函数会在执行的瞬间在内存中持有n份factorial函数的上下文，相应地我们就付出了n 份内存的代价。显而易见，这样的程序在空间复杂度的考量上是极不友好的。同时，伴随着每次函数执行成功，返回、出栈，也会对CPU造成一定的压力。 因此，当我们在实战中真的遇到调用深度可能比较深的时候，要想办法避免传统的递归，而转而采用一种 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/js-tail-recursion/</link>
                <guid isPermaLink="false">6128367a0cd9ee0623e159b0</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Fri, 27 Aug 2021 03:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/08/graphic-4070337_960_720-down2-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>递归是编程过程中常用的手段，跟许多初学者想象不同的是，我们使用递归，恰恰是因为递归的<strong>直觉</strong>性，能够很好的表达设计代码的逻辑思路，易于他人理解代码。</p>
<p>但是递归的代价也很明显，在<code>Java</code>中一个最典型的错误就是<code>StackOverflowError</code>，当你对一个<code>factorial（阶乘）</code>递归函数传入参数<code>10000</code>时，就可以遇到它了。</p>
<p>当然，在<code>JavaScript</code>也是会遇到类似的问题的。请看下面这段代码：</p>
<pre><code class="language-js">function factorial(n) {
    if (n &lt; 2) {
        return 1;
    }
    return n * factorial(n - 1);
}
</code></pre>
<p>这就是一段非常典型的递归调用，可以算出<code>n</code>的阶乘。不加优化的情况下，这个函数会在执行的瞬间在内存中持有<code>n</code>份<code>factorial</code>函数的上下文，相应地我们就付出了<code>n</code>份内存的代价。显而易见，这样的程序在空间复杂度的考量上是极不友好的。同时，伴随着每次函数执行成功，返回、出栈，也会对<code>CPU</code>造成一定的压力。</p>
<p>因此，当我们在实战中真的遇到调用深度可能比较深的时候，要想办法避免传统的递归，而转而采用一种叫<code>尾递归</code>的方式来优化代码。</p>
<p>直接解释<code>尾递归</code>的概念的话，比较抽象。我们先来看用<code>尾递归</code>思路优化过后的代码：</p>
<pre><code class="language-js">function factorial(n, result = 1) {
    if (n &lt; 2) {
        return result;
    }
    return factorial(n - 1, n * result);
}
</code></pre>
<p>这里我建议读者朋友们可以尝试在草稿纸上推导一下<code>factorial(3)</code>的执行过程，从而理解上面两种写法都是可以实现目的的。并且我在本文一开始也说了，递归的运用，其实是对程序逻辑的直观表达，更具可读性。这个在我们理解上面两段代码的逻辑时应该能有所体会。第一种写法应该是更好懂一些。</p>
<p>但是第二种写法是对机器极其友好的写法，因为它运用了<code>尾递归</code>，其执行速度就跟普通的循环没什么区别了，并且递归的深度也不受任何限制，也不用担心调用栈溢出或内存耗尽。</p>
<p>像上面第二种写法，在方法结束时出现的表达式，仅仅是自身的函数调用的递归，就是尾递归。</p>
<p>这里比较<code>return n * factorial(n - 1);</code>与<code>return factorial(n - 1, n * result);</code>就可以很好的看出来了，后者是一个纯粹的函数调用，而前者是在函数调用完后仍然做了其他运算。</p>
<p>那么为什么<code>尾递归</code>的执行效率更高呢？这其实是<code>JavaScript</code>解释器帮我们优化的结果。要理解这种优化，我们还需要引入另外一个概念——<code>尾调用</code>，先别着急头大。<code>尾调用</code>的概念更加简单些，<a href="https://zh.wikipedia.org/wiki/%E5%B0%BE%E8%B0%83%E7%94%A8">维基百科</a>里是这么解释的：</p>
<blockquote>
<p>尾调用是指一个函数里的最后一个动作是返回一个函数的调用结果的情形，即最后一步新调用的返回值直接被当前函数的返回结果。</p>
</blockquote>
<p>也就是上面的<code>尾递归</code>代码就是一种典型的<code>尾调用</code>，只是因为调用的函数也是自身，所以同时命中了递归的概念。其实<code>JavaScript</code>解释器之所以能够优化<code>尾递归</code>的执行效率，本质上是因为其对<code>尾调用</code>的优化。这种优化过程，说起来稍微有点绕，下面引用一段<a href="https://zh.wikipedia.org/wiki/%E5%B0%BE%E8%B0%83%E7%94%A8">维基百科</a>中的介绍帮助大家理解：</p>
<blockquote>
<p>传统模式的编译器对于尾调用的处理方式就像处理其他普通函数调用一样，总会在调用时创建一个新的栈帧（stack frame）并将其推入调用栈顶部，用于表示该次函数调用。</p>
</blockquote>
<p>当一个函数调用发生时，电脑必须“记住”调用函数的位置——返回位置，才可以在调用结束时带着返回值回到该位置，返回位置一般存在调用栈上。在尾调用这种特殊情形中，电脑理论上可以不需要记住尾调用的位置而从被调用的函数直接带着返回值返回调用函数的返回位置（相当于直接连续返回两次）。尾调用消除即是在不改变当前调用栈（也不添加新的返回位置）的情况下跳到新函数的一种优化（完全不改变调用栈是不可能的，还是需要校正调用栈上形式参数与局部变量的信息。）</p>
<p>由于当前函数帧上包含局部变量等等大部分的东西都不需要了，当前的函数帧经过适当的更动以后可以直接当作被尾调用的函数的帧使用，然后程序即可以跳到被尾调用的函数。产生这种函数帧更动代码与 “jump”（而不是一般常规函数调用的代码）的过程称作尾调用消除（Tail Call Elimination）或尾调用优化（Tail Call Optimization，TCO）。尾调用优化让位于尾位置的函数调用跟 goto 语句性能一样高，也因此使得高效的结构编程成为现实。</p>
<p>如果你想了解具体<code>JavaScript</code>解释器是怎么操作的，还可以阅读《JavaScript悟道》这本书的第 143 页。</p>
<p>对于初学者来说，理解不了上面复杂的优化概念也没有关系。我们需要了解的第一是递归的写法和应用场景，第二要明白递归是有代价的，不宜过深，第三就是在合适的时候通过尾递归的方式优化递归函数的执行效率。牢记这三点就足够了。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Linux/MacOS 上管理 SDK 的最正确方式 ]]>
                </title>
                <description>
                    <![CDATA[ 对开发者来说，就算是同一种语言，我们也可能会在电脑上装数个不同版本的环境，以应对一些历史包袱和复杂多变的客户环境。 所以长远来看，对待任何一门语言的SDK环境，我们都应找到一种方式，合理管理不同的SDK版本。这里我强烈建议你在处理自己电脑的开发环境时，有意识地对SDK版本进行管理，而不是直接上网搜了个最新版的安装包，莫名其妙地装，莫名其妙地用。 别担心，这并不难。各路大神已经把轮子给你造好了。 几乎对任何一门语言，只要你在网上搜索： xxxLanguage version manager 稍加甄别就能得到自己想要的答案。当然，下面我也把常用的一些语言及环境的version manager列了出来，供你享用。  * Java——sdkman [https://sdkman.io]👍  * Java——jabba [https://github.com/shyiko/jabba]  * Python——Miniforge [https://github.com/conda-forge/miniforge]👍  * Python——Anaconda [https://www ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/sdk-version-manager/</link>
                <guid isPermaLink="false">604b18f86ce45b059394b81c</guid>
                
                    <category>
                        <![CDATA[ SDK ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mac ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Fri, 12 Mar 2021 07:58:47 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/03/--2021-03-12---3.45.31-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>对开发者来说，就算是同一种语言，我们也可能会在电脑上装数个不同版本的环境，以应对一些历史包袱和复杂多变的客户环境。</p>
<p>所以长远来看，对待任何一门语言的SDK环境，我们都应找到一种方式，合理管理不同的SDK版本。这里我强烈建议你在处理自己电脑的开发环境时，有意识地对SDK版本进行管理，而不是直接上网搜了个最新版的安装包，莫名其妙地装，莫名其妙地用。</p>
<p>别担心，这并不难。各路大神已经把轮子给你造好了。</p>
<p>几乎对任何一门语言，只要你在网上搜索：</p>
<pre><code>xxxLanguage version manager
</code></pre>
<p>稍加甄别就能得到自己想要的答案。当然，下面我也把常用的一些语言及环境的<code>version manager</code>列了出来，供你享用。</p>
<ul>
<li>Java——<a href="https://sdkman.io">sdkman</a>👍</li>
<li>Java——<a href="https://github.com/shyiko/jabba">jabba</a></li>
<li>Python——<a href="https://github.com/conda-forge/miniforge">Miniforge</a>👍</li>
<li>Python——<a href="https://www.anaconda.com">Anaconda</a></li>
<li>Python——<a href="https://github.com/pyenv/pyenv">pyenv</a></li>
<li>Node——<a href="https://github.com/nvm-sh/nvm">nvm</a></li>
<li>Go——<a href="https://github.com/moovweb/gvm">gvm</a></li>
<li>Ruby——<a href="https://github.com/rvm/rvm">rvm</a></li>
<li>PHP——<a href="https://github.com/phpbrew/phpbrew">phpbrew</a></li>
</ul>
<p>这些工具的使用大同小异，咱们前后端各选一个代表测试一下，下面先来演示<code>node</code>的管理环境<code>nvm</code>。</p>
<h3 id="">安装</h3>
<p>直接在终端里执行这句话即可：</p>
<pre><code class="language-sh">curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
</code></pre>
<h3 id="">使用</h3>
<p>配置完毕后，打开一个新的终端，可以通过<code>nvm ls-remote</code>列出所有可用的<code>node</code>版本了，如果只想查看<code>lts</code>版本，可以通过追加参数实现，<code>nvm ls-remote --lts</code>。确认要安装的版本后，就可以通过<code>nvm install v15.11.0</code>的方式安装了。如果你安装了多个版本，需要在不同版本间切换，只需要简单的使用<code>nvm use v15.11.0</code>就可以进行版本切换了。</p>
<h3 id="">更多</h3>
<p>关于<code>nvm</code>的使用，还有很多功能有待你来发掘，具体可以执行<code>nvm --help</code>获得更多有用信息。顺便一提，用<code>nvm</code>在<code>ARM</code>架构的<code>Mac</code>电脑上安装<code>node</code>的时候，是基于<code>node</code>的源码进行本地编译安装的，因而你将获得一个<code>ARM</code>版本的<code>node</code>，这点对正在使用<code>M1</code>芯片<code>Mac</code>的小伙伴非常友好。</p>
<p>介绍完了前端，咱们再来看看后端，这里以<code>Java</code>的<code>sdkman</code>进行演示。</p>
<h3 id="">安装</h3>
<p>也是非常简单，一句话的事。</p>
<pre><code>curl -s "https://get.sdkman.io" | bash
</code></pre>
<h3 id="">使用</h3>
<p>列出可用的<code>Java（JDK）</code>版本，执行<code>sdk list java</code>即可，然后通过执行<code>sdk install java 15.0.2-zulu</code>就可以安装你需要的<code>JDK</code>版本了。</p>
<p>额外说一句，如果是<code>ARM</code>架构的<code>Mac</code>其实是支持<code>x86</code>和<code>ARM</code>两种架构的<code>JDK</code>的，只是前者要经过<code>MacOS</code>的<code>Rosetta2</code>的转译，大部分情况下我们为了追求性能想使用<code>ARM</code>版本的<code>JDK</code>，此时你可以更改<code>sdkman</code>的配置文件，<code>vim ~/.sdkman/etc/config</code>，找到<code>sdkman_rosetta2_compatible</code>那一行，如果设置成<code>true</code>，就仅显示<code>ARM</code>版本的<code>JDK</code>了。</p>
<p>如果你安装了多个版本的<code>JDK</code>，一样可以通过<code>sdk use java 8.0.275-amzn</code>的方式进行切换。如果你想改变全局环境的默认<code>JDK</code>版本，则可以使用<code>sdk default java 8.0.275-amzn</code>进行设置。</p>
<h3 id="">其他</h3>
<p><code>sdkman</code>除了支持<code>JDK</code>的版本管理外，还支持<code>Java</code>系的很多技术栈，比如：<code>Gradle</code>、<code>Maven</code>、<code>Kotlin</code>、<code>Groovy</code>等。所以如果你有这方面需求的话，记得执行<code>sdk --help</code>探索更多有用功能哦。</p>
<h3 id="">八卦</h3>
<p>其实关于<code>sdkman</code>还有一段有意思的往事，曾经<code>sdkman</code>的名字是<code>gvm</code>，专门用来管理一门自<code>2003</code>年就存在的<code>Groovy</code>语言，这门语言跟<code>Java</code>一样，也是运行在<code>JVM</code>（Java虚拟机）上的。</p>
<p>但是后来程序界又迎来了另一个<code>G</code>姓语言——<code>Go</code>，越来越多的小伙伴希望<code>gvm</code>这个名字能见文知意，用作<code>Go</code>的版本管理，而不是去管理一个小众的<code>JVM</code>语言。</p>
<p>于是在2015年，之前维护<code>gvm</code>的小伙伴，宣布了<code>gvm</code>改名为<code>sdkman</code>，从而把<code>gvm</code>的使用权交给了<code>Go Version Manager</code>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 2021年了，你真的应该考虑PostgreSQL了（4·终章） ]]>
                </title>
                <description>
                    <![CDATA[ 本篇文章将会是这个系列的最后一篇。数据库是一门实践为王的技术，所以我无法通过寥寥几篇专栏就把PostgreSQL 给说清楚，更多有价值的功能及用法只能有待你自己去亲自动手尝试了。 今天我还会介绍一个属于PostgreSQL独有的特性，并且这个特性如同一把钥匙，可以给你打开一个崭新的数据库编程世界，至少我是这么认为的，那么我们赶紧开始吧。 SQL这种东西，本身是面向结果的，而不是面向过程的。这跟传统的编程语言有很大的区别，比如你在使用JavaScript、Java 时，这些语言都是基于过程的，你要一步一步通过代码表达式把要做的逻辑过程编写出来，让计算机理解。而SQL 是不具备这种能力的，虽然它看起来更高级，你只需要对着数据库说“要有光”(select light from universe)，“光”就来了。 但是不可否认，SQL语言的表达能力是有限的，有些复杂的逻辑我们无法依赖SQL直接实现。比如：计算当前时间是本年度的第几周，这显然已经超出了SQL 的能力范围。所以数据库的创造者们，给数据库增加了SQL之外的能力，也就是Procedural Language（过程语言）简称PL  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/2021-postgres-4/</link>
                <guid isPermaLink="false">60042f345f61e30501b5bdae</guid>
                
                    <category>
                        <![CDATA[ PostgreSQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Java ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 后端开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 牧云踏歌 ]]>
                </dc:creator>
                <pubDate>Sun, 17 Jan 2021 12:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/01/iShot2020-12-04-14.24.51.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>本篇文章将会是这个系列的最后一篇。数据库是一门实践为王的技术，所以我无法通过寥寥几篇专栏就把<code>PostgreSQL</code>给说清楚，更多有价值的功能及用法只能有待你自己去亲自动手尝试了。</p><p>今天我还会介绍一个属于<code>PostgreSQL</code>独有的特性，并且这个特性如同一把钥匙，可以给你打开一个崭新的数据库编程世界，<s>至少我是这么认为的</s>，那么我们赶紧开始吧。</p><p><code>SQL</code>这种东西，本身是面向结果的，而不是面向过程的。这跟传统的编程语言有很大的区别，比如你在使用<code>JavaScript</code>、<code>Java</code>时，这些语言都是基于过程的，你要一步一步通过代码表达式把要做的逻辑过程编写出来，让计算机理解。而<code>SQL</code>是不具备这种能力的，虽然它看起来更高级，你只需要对着数据库说“要有光”(<code>select light from universe</code>)，“光”就来了。</p><p>但是不可否认，<code>SQL</code>语言的表达能力是有限的，有些复杂的逻辑我们无法依赖<code>SQL</code>直接实现。比如：计算当前时间是本年度的第几周，这显然已经超出了<code>SQL</code>的能力范围。所以数据库的创造者们，给数据库增加了<code>SQL</code>之外的能力，也就是<code>Procedural Language</code>（过程语言）简称<code>PL</code>。这种过程语言就给开发者提供了传统编程语言的逻辑方式，方便我们在数据里实现一种叫<code>存储过程</code>/<code>函数</code>的东西，进而拓展了数据库本身的逻辑能力。</p><p>几乎每个关系型数据库都有自己的<code>PL</code>实现，比如<code>Oracle</code>的<code>PL/SQL</code>、<code>SQL Server</code>的<code>T-SQL</code>，而在<code>PostgreSQL</code>中，提供的就是<code>PL/pgSQL</code>。</p><p>这当然也不是什么新鲜事。作为本系列的最后一篇，如果只是讲讲<code>PL/pgSQL</code>未免有些平淡了。</p><p>容我给你展示一组清单：</p><ul><li>PL/Java</li><li>PL/PHP</li><li>PL/Perl</li><li>PL/Python</li><li>PL/V8</li></ul><p>结合前文所述，您大概已经猜到了。<code>PL/xxx</code>其实就是在说用基于某个语言实现数据库里的存储过程。换句话说，我们可以用自己熟知的高级语言，如：<code>Java</code>、<code>PHP</code>、<code>Python</code>来编写数据库的存储过程。当然了，这个功能是<code>PostgreSQL</code>独有的，别的数据库可享受不到。至于<code>PL/V8</code>，如果你熟悉前端开发的话，应该不难猜出这里说的是<code>JavaScript</code>的<code>V8</code>引擎。</p><p>下面我就用<code>PL/Python</code>来演示下，如何用<code>Python3</code>来开发<code>PostgreSQL</code>的存储过程。</p><p>首先需要在数据库服务器上安装<code>Python3</code>，然后再安装<code>PostgreSQL</code>的<code>Python3</code>扩展。以<code>ubuntu</code>为例，你需要执行：</p><pre><code class="language-sh">apt-get install postgresql-plpython3-13
</code></pre><p>注意最后的数字<code>13</code>，应该与你本地的<code>PostgreSQL</code>版本相对应。一旦扩展安装关闭，就可以通过一条<code>SQL</code>语句，在数据库中开启对应的扩展功能，你需要执行。</p><pre><code class="language-sql">create extension "plpython3u";
</code></pre><p>下面我们尝试创建一个存储过程（其实称为“函数”可能更准确，不过“存储过程”比“函数”更贴近数据库的专有词汇，为了方便大家理解，我采用了“存储过程”的说法）。</p><pre><code class="language-sql">create function pymax(a integer, b integer)
  returns integer
language plpython3u
as $$
if a &gt; b:
    return a
return b
$$;
</code></pre><p>这是一个简单的比较两个整型大小的存储过程，本身没什么技术含量。需要特别注意的是<code>$$</code>之间的代码段是通过<code>language</code>指定的，当我们指定了<code>plpython3u</code>后，就可以用<code>Python3</code>来编写函数了。</p><p>简单执行测试一下:</p><pre><code class="language-sql">studypg=# select pymax(2, 3);
 pymax
-------
     3
(1 row)
</code></pre><p>可以看到函数顺利执行成功了。</p><p>聪明的你可能会说，能利用上<code>Python</code>的语言本身固然有点意思，但是实际价值却不大。要是能利用上<code>Python</code>的生态，那才是真的强。答案当然是<strong>Yes</strong>啦。</p><p>众所周知，最近几年<code>Python</code>在机器学习领域可谓是独领风骚。这得益于其多年在数学领域的积累，而<code>numpy</code>这个包，更是所有数学工作者的最爱。下面我就用<code>numpy</code>演示，如何在<code>PostgreSQL</code>中使用<code>Python</code>的强大生态。</p><p>首先安装<code>numpy</code>包，执行：</p><pre><code class="language-sh">pip3 install numpy
</code></pre><p>然后我们创建一个存储过程，用来计算以e为底的自然数对数，语句如下：</p><pre><code class="language-sql">create or replace function pylog(x float)
  returns float
language plpython3u
as $$
import numpy as np

return np.log(x)
$$;
</code></pre><p>看下执行结果：</p><pre><code class="language-sql">studypg=# select pylog(1);
 pylog
-------
     0
(1 row)

studypg=# select pylog(0.1);
       pylog
-------------------
 -2.30258509299405
(1 row)
</code></pre><p>非常完美，借助<code>Python</code>生态的力量，我们仅用几句话就实现了原本<code>SQL</code>根本无法实现的功能。其实这还只是冰山一角。笔者甚至通过<code>Python</code>版的数据库函数作为数据库表的触发器，拦截到数据的变化，直接把变化信息转发到<code>MQTT</code>服务器，从而让其他外部应用，可以通过对<code>MQTT</code>消息的订阅，得到数据变化的推送信息。</p><p>好了，关于<code>PostgreSQL</code>中神奇的存储过程，就介绍到这里了。其实在<code>PostgreSQL</code>中还有很多神奇的事情值得挖掘，比如触发器，<code>PostgreSQL</code>不光可以拦截<code>DML</code>还可以拦截<code>DDL</code>；还有外部数据源<code>FDW</code>，<code>PostgreSQL</code>中可以引用多种外部数据，包括<code>MySQL</code>、<code>Oracle</code>等主流关系型数据库，甚至还支持<code>Redis</code>、<code>MongoDB</code>等非关系型数据库。总之，只有想不到，没有做不到。</p><p>关于<code>PostgreSQL</code>的系列文章至此就结束了。希望我的文章能激发出你对数据库的兴趣，愿这门悠久而强大的技术能够在你的工作中起到一些积极作用。最重要的，请一定要多实践。我们下个话题再见😃</p><p><strong>推荐阅读本系列的其他文章：</strong></p><ul><li><a href="https://chinese.freecodecamp.org/news/2021-postgres-1/">2021年了，你真的应该考虑PostgreSQL了（1）</a></li><li><a href="https://chinese.freecodecamp.org/news/2021-postgres-2/">2021年了，你真的应该考虑PostgreSQL了（2）</a></li><li><a href="https://chinese.freecodecamp.org/news/2021-postgres-3/">2021年了，你真的应该考虑PostgreSQL了（3）</a></li></ul> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
