Jekyll2024-02-06T22:32:36+00:00https://jierong.dev/feed.xmlJierong’s IppoA blog about iOS and Android development.Jierong Lijierongli@proton.meデータベーススペシャリスト試験に合格した2022-01-12T00:00:00+00:002022-01-12T00:00:00+00:00https://jierong.dev/ja/2022/01/12/database-specialist-examination-passed<h2 id="受験動機">受験動機</h2>
<ul>
<li>モバイル開発にデータベース使う頻度が低いけど、いざ使うと設計に迷うことを改善したい</li>
<li>外国人として<a href="https://www.moj.go.jp/isa/publications/materials/newimmiact_3_system_index.html">高度人材ポイント制</a>のポイントが付く</li>
</ul>
<h2 id="準備前">準備前</h2>
<p><a href="/ja/2021/09/07/information_security-management-examination-passed.html">情報セキュリティマネジメント試験の場合</a>と似たように、だいぶ時間が経ちましたが、大学では基礎として学習したことがあり、iOS・Androidエンジニアとして、仕事上も多少調べながら運用したことがあります。</p>
<p>午前模擬試験ではギリ合格でした。
しかし筆記試験がどう解いていくすらわからないちんぷんかんポンでした。</p>
<h2 id="受験準備">受験準備</h2>
<h3 id="参考書籍">参考書籍</h3>
<p><a href="https://www.amazon.co.jp/dp/B08DXDYDL1">情報処理教科書 データベーススペシャリスト 2021年版</a></p>
<p>筆記試験の過去問について詳細の解説がある参考書籍にしました。</p>
<h3 id="準備期間">準備期間</h3>
<p>週末含めると平均毎日2時間ぐらいで2ヶ月で、前半午前の過去問解いつつ、参考書籍を読了し、後半ひたすら午後の過去問を解いていました。</p>
<h3 id="準備結果">準備結果</h3>
<p>午前過去問最高90以上点取ったことがあり、午後過去問の解き方を身につけました。</p>
<h3 id="感想">感想</h3>
<p>午前について、選択形式とはいえデータベースのみではなく、全般的な知識が問われ、最初ギリ合格で心配してたけど、基本ソフトウェアエンジニアであれば触ったことがある範囲内で、勘案して解いていけたり、一回知っていれば覚えやすい問題になりますので、模擬試験の点数基本は右肩上がりでした。</p>
<p>午後について、<a href="/ja/2021/09/07/information_security-management-examination-passed.html">情報セキュリティマネジメント試験の場合</a>と違って、参考書籍いきなり試験の対策から説明されて、受験したことがないとわかりにくいし、対策に力入れすぎ感があります。とはいえ、自分が受験とそれに対する対策が好きではないだけかもしれません。</p>
<p>大学専攻の基礎としてある程度勉強したことがあり、復習になってしまいましたので、最初の時のワクワク感がないですが、下記の内容についてわかりやすく説明されました。</p>
<ul>
<li>SQL</li>
<li>概念データモデル</li>
<li>関係スキーマ</li>
<li>重要キーワード</li>
</ul>
<p>一通り知識の復習ができてから直接午後の過去問を解いてみましたが、日本語力が不十分のせいかもしれませんが、解けないことはないけど、時間がかかりすぎるし、抜け漏れも多い印象でした。</p>
<p>ここで午後の過去問の解説を読み始めました。参考書籍の序章のように純粋の対策と違って、問題の解き方やどういう流れで答えを漏れなく導くことを丁寧に解説され、大変助かったし、この参考書籍一番価値あるところ(書籍に含まれたのが2回分のみだけど、別途PDFダウンロード可能)と感じました。</p>
<h2 id="受験">受験</h2>
<h3 id="受験結果">受験結果</h3>
<p>タイトル通りに合格しましたが、点数の記載がないみたいです。</p>
<h3 id="受験感想">受験感想</h3>
<p>午後の試験が筆記で、ある程度知識を身に付けないと答えしきれない量で、現実にありそうな実例でデータベース設計や改善の問題が出され、抽象化されたとはいえ、モバイル開発において必要なデータベース知識を超え、合格して結構達成感を得ました。ですのでデータベースについて興味あり、かつまだ業務でバリバリ触っていない人にデータベーススペシャリスト試験をおすすめします。</p>
<p>しかし準備するために個人開発やブログが止まり、モバイル開発が仕事以外に疎かになりました。その上<a href="https://www.moj.go.jp/isa/publications/materials/newimmiact_3_system_index.html">高度人材ポイント制</a>のポイントは資格二つまでしかカウントされませんので、ネットワークスペシャリストなど似たような試験があるものの、継続に受ける予定がありません。</p>
<h2 id="今後">今後</h2>
<p>試験参加する予定がないし、一旦モバイル開発に専念し、ブログも再開したいですが、自分が詳しい領域について試験に向けて勉強がすることがまたやってみたいと思います。</p>Jierong Lijierongli@proton.me受験動機情報セキュリティマネジメント試験に合格した2021-09-07T00:00:00+00:002021-09-07T00:00:00+00:00https://jierong.dev/ja/2021/09/07/information_security-management-examination-passed<h2 id="受験動機">受験動機</h2>
<ul>
<li>セキュリティについて興味がある</li>
<li>外国人として<a href="https://www.moj.go.jp/isa/publications/materials/newimmiact_3_system_index.html">高度人材ポイント制</a>のポイントが付く</li>
</ul>
<h2 id="準備前">準備前</h2>
<p>だいぶ時間が経ちましたが、大学では一応ちょっとだけ触れたことがあり、iOS・Androidエンジニアとして、仕事上も多少調べながら運用したことがあります。</p>
<p>模擬試験ではギリ合格でした。</p>
<h2 id="受験準備">受験準備</h2>
<h3 id="参考書籍">参考書籍</h3>
<p><a href="https://www.amazon.co.jp/dp/429711724X">令和03年 情報セキュリティマネジメント 合格教本</a></p>
<p>系統的に勉強したことがないため、一応初心者用の参考書籍を選びました。</p>
<h3 id="準備期間">準備期間</h3>
<p>毎日1時間ぐらいで1ヶ月で、前半参考書籍を読了し、後半ひたすら過去問を解いていました。</p>
<h3 id="準備結果">準備結果</h3>
<p>過去問最高90以上点取ったことがあります。</p>
<h3 id="感想">感想</h3>
<p>受験向けの参考書籍だけど、対策がメインではなく、セキュリティ関連知識をわかりやすく説明され、結構楽しいかったです。</p>
<p>下記の内容が含まれて、まだまだ浅いかもしれませんが、結構広く学習でき、幾つある程度わかっているけど、理解が曖昧なところもすっきりしました。</p>
<ul>
<li>情報セキュリティ基礎</li>
<li>情報セキュリティ管理</li>
<li>情報セキュリティ対策</li>
<li>情報セキュリティ関連法規</li>
<li>ネットワークとデータベース</li>
<li>経営とセキュアシステム主権</li>
</ul>
<p>キーワードが揃っていますので、それ以上知りたかった知識が気になるのであればWikipediaとかで継続の学習に繋ぎました。</p>
<h2 id="受験">受験</h2>
<h3 id="受験結果">受験結果</h3>
<p>記憶はちょっと曖昧になりましたが、午前・午後平均85点で、基本最後何回の過去問の平均点数と一致します。</p>
<h3 id="受験感想">受験感想</h3>
<p>受験に必要な知識を学習することに対して結構満足しましたが、過去問が豊富で、過去問を問う時と比べると万が一落ちたことに対するストレス以外にあまり変わらず、受験の達成感がほぼありません。</p>
<p>ですので、私のように<a href="https://www.moj.go.jp/isa/publications/materials/newimmiact_3_system_index.html">高度人材ポイント制</a>のポイントが必要ではなければ、参考書籍による学習はおすすめしますが、受験する必要があまりないかと思います。</p>
<h2 id="今後">今後</h2>
<p>個人開発で作ろうとしてたアプリデータベースの使用が必要で、ちょうど<a href="https://www.jitec.ipa.go.jp/1_11seido/db.html">データベーススペシャリスト試験</a>もあり、挑戦することにしました。</p>
<p>試験の辛さはそんなに変わらないと思いますので、学習の過程を楽しんでいきたいと思います。</p>Jierong Lijierongli@proton.me受験動機WIP: Embed Framework with a Higher Development Target in iOS2021-07-25T00:00:00+00:002021-07-25T00:00:00+00:00https://jierong.dev/2021/07/25/embed-framework-with-a-higher-development-target-in-ios<p>We may need to embed some frameworks with a higher development target, such as a framework using in the Widget Extension. However, <a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW3">it requires conditionally load by Objective-C</a>. Is there a way for swift?</p>
<p>I am still finding the best answer, but the current one is–no, it isn’t.</p>
<p>The framework will be load at launch and requires related swift runtime of set development target.</p>
<p>So we have to lower the development target to the same as the application target and add available annotations for codes using the new APIs like below.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@available</span><span class="p">(</span><span class="n">iOS</span> <span class="mf">14.0</span><span class="p">,</span> <span class="o">*</span><span class="p">)</span>
<span class="kd">struct</span> <span class="kt">MyProvider</span><span class="p">:</span> <span class="kt">TimelineProvider</span> <span class="p">{</span>
<span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>Jierong Lijierongli@proton.meWe may need to embed some frameworks with a higher development target, such as a framework using in the Widget Extension. However, it requires conditionally load by Objective-C. Is there a way for swift?Providing Default Value for Decodable Property by Property Wrapper2021-02-26T00:00:00+00:002021-02-26T00:00:00+00:00https://jierong.dev/2021/02/26/providing-default-value-for-decodable-property-by-property-wrapper<p>To provide a default value for a <code class="language-plaintext highlighter-rouge">Decodable</code> type’s properties, we need to define <code class="language-plaintext highlighter-rouge">CodingKeys</code> and use it to implement <code class="language-plaintext highlighter-rouge">init(from:)</code>.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">Bar</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">Decodable</span> <span class="p">{</span>
<span class="k">case</span> <span class="n">case1</span><span class="p">,</span> <span class="n">case2</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">Foo</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">array</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span>
<span class="k">let</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">let</span> <span class="nv">int</span><span class="p">:</span> <span class="kt">Int</span>
<span class="k">let</span> <span class="nv">double</span><span class="p">:</span> <span class="kt">Double</span>
<span class="k">let</span> <span class="nv">bar</span><span class="p">:</span> <span class="kt">Bar</span>
<span class="kd">private</span> <span class="kd">enum</span> <span class="kt">CodingKeys</span><span class="p">:</span> <span class="kt">CodingKey</span> <span class="p">{</span>
<span class="k">case</span> <span class="n">array</span><span class="p">,</span> <span class="n">string</span><span class="p">,</span> <span class="n">int</span><span class="p">,</span> <span class="n">double</span><span class="p">,</span> <span class="n">bar</span>
<span class="p">}</span>
<span class="nf">init</span><span class="p">(</span><span class="n">from</span> <span class="nv">decoder</span><span class="p">:</span> <span class="kt">Decoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">container</span><span class="p">(</span><span class="nv">keyedBy</span><span class="p">:</span> <span class="kt">CodingKeys</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
<span class="n">array</span> <span class="o">=</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">decodeIfPresent</span><span class="p">([</span><span class="kt">Int</span><span class="p">]</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">array</span><span class="p">)</span> <span class="p">??</span> <span class="p">[]</span>
<span class="n">string</span> <span class="o">=</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">decodeIfPresent</span><span class="p">(</span><span class="kt">String</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">string</span><span class="p">)</span> <span class="p">??</span> <span class="s">""</span>
<span class="n">int</span> <span class="o">=</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">decodeIfPresent</span><span class="p">(</span><span class="kt">Int</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">int</span><span class="p">)</span> <span class="p">??</span> <span class="mi">0</span>
<span class="n">double</span> <span class="o">=</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">decodeIfPresent</span><span class="p">(</span><span class="kt">Double</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">double</span><span class="p">)</span> <span class="p">??</span> <span class="o">-</span><span class="mi">1</span>
<span class="n">bar</span> <span class="o">=</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">decodeIfPresent</span><span class="p">(</span><span class="kt">Bar</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="o">.</span><span class="n">bar</span><span class="p">)</span> <span class="p">??</span> <span class="o">.</span><span class="n">case2</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s easy but troublesome since we have to write more than treble code even there is only one property we want to provide a default value.</p>
<p>Moreover, we have to define a normal initializer and provide default values again if we need.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Foo</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="p">{</span>
<span class="o">...</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">array</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span> <span class="o">=</span> <span class="p">[],</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span><span class="p">,</span> <span class="nv">int</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">double</span><span class="p">:</span> <span class="kt">Double</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="nv">bar</span><span class="p">:</span> <span class="kt">Bar</span> <span class="o">=</span> <span class="o">.</span><span class="n">case2</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">array</span> <span class="o">=</span> <span class="n">array</span>
<span class="k">self</span><span class="o">.</span><span class="n">string</span> <span class="o">=</span> <span class="n">string</span>
<span class="k">self</span><span class="o">.</span><span class="n">int</span> <span class="o">=</span> <span class="n">int</span>
<span class="k">self</span><span class="o">.</span><span class="n">double</span> <span class="o">=</span> <span class="n">double</span>
<span class="k">self</span><span class="o">.</span><span class="n">bar</span> <span class="o">=</span> <span class="n">bar</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This time the amount of code is not the only problem. We may set different default values than <code class="language-plaintext highlighter-rouge">init(from:)</code> by mistake.</p>
<p>Property wrapper comes to rescue.</p>
<p>Let’s define a protocol that holds a default value.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protocol</span> <span class="kt">Default</span> <span class="p">{</span>
<span class="kd">associatedtype</span> <span class="kt">Value</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">Value</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With this protocol, we can define the property wrapper to provide the default value.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@propertyWrapper</span>
<span class="kd">struct</span> <span class="kt">DefaultDecodable</span><span class="o"><</span><span class="kt">D</span><span class="p">:</span> <span class="kt">Default</span><span class="o">></span><span class="p">:</span> <span class="kt">Decodable</span> <span class="k">where</span> <span class="kt">D</span><span class="o">.</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">wrappedValue</span><span class="p">:</span> <span class="kt">D</span><span class="o">.</span><span class="kt">Value</span>
<span class="nf">init</span><span class="p">(</span><span class="n">from</span> <span class="nv">decoder</span><span class="p">:</span> <span class="kt">Decoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="k">try</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">singleValueContainer</span><span class="p">()</span>
<span class="n">wrappedValue</span> <span class="o">=</span> <span class="k">try</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">D</span><span class="o">.</span><span class="kt">Value</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
<span class="p">}</span>
<span class="nf">init</span><span class="p">()</span> <span class="p">{</span>
<span class="n">wrappedValue</span> <span class="o">=</span> <span class="kt">D</span><span class="o">.</span><span class="n">defaultValue</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You may notice that we only use the default value in <code class="language-plaintext highlighter-rouge">init()</code>, but how can we use it when decoding? Here is the magic.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">KeyedDecodingContainer</span> <span class="p">{</span>
<span class="kd">func</span> <span class="n">decode</span><span class="o"><</span><span class="kt">D</span><span class="o">></span><span class="p">(</span><span class="n">_</span> <span class="nv">type</span><span class="p">:</span> <span class="kt">DefaultDecodable</span><span class="o"><</span><span class="kt">D</span><span class="o">>.</span><span class="k">Type</span><span class="p">,</span>
<span class="n">forKey</span> <span class="nv">key</span><span class="p">:</span> <span class="kt">Key</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-></span> <span class="kt">DefaultDecodable</span><span class="o"><</span><span class="kt">D</span><span class="o">></span> <span class="p">{</span>
<span class="k">try</span> <span class="nf">decodeIfPresent</span><span class="p">(</span><span class="n">type</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="n">key</span><span class="p">)</span> <span class="p">??</span> <span class="o">.</span><span class="nf">init</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With this overload, we can provide the default value for the property using <code class="language-plaintext highlighter-rouge">DefaultDecodable</code>.</p>
<p>Let’s define a protocol to provide an empty default value.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protocol</span> <span class="kt">EmptyInitializable</span> <span class="p">{</span>
<span class="nf">init</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">Array</span><span class="p">:</span> <span class="kt">EmptyInitializable</span> <span class="p">{}</span>
<span class="kd">extension</span> <span class="kt">String</span><span class="p">:</span> <span class="kt">EmptyInitializable</span> <span class="p">{}</span>
</code></pre></div></div>
<p>Then implement <code class="language-plaintext highlighter-rouge">Default</code> for <code class="language-plaintext highlighter-rouge">EmptyInitializable</code>.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">EmptyDefault</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">EmptyInitializable</span><span class="o">></span><span class="p">:</span> <span class="kt">Default</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">Value</span> <span class="p">{</span>
<span class="o">.</span><span class="nf">init</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now we can use them this way.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Foo</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="p">{</span>
<span class="kd">@DefaultDecodable</span><span class="o"><</span><span class="kt">EmptyDefault</span><span class="o">></span> <span class="k">var</span> <span class="nv">array</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span>
<span class="kd">@DefaultDecodable</span><span class="o"><</span><span class="kt">EmptyDefault</span><span class="o">></span> <span class="k">var</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span>
<span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s a little tedious, let’s organize them.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">CommonDefault</span> <span class="p">{</span> <span class="c1">// Just for a namespace</span>
<span class="kd">struct</span> <span class="kt">Empty</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">EmptyInitializable</span><span class="o">></span><span class="p">:</span> <span class="kt">Default</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">Value</span> <span class="p">{</span>
<span class="o">.</span><span class="nf">init</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">enum</span> <span class="kt">DefaultBy</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Empty</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="o">&</span> <span class="kt">EmptyInitializable</span><span class="o">></span> <span class="o">=</span> <span class="kt">DefaultDecodable</span><span class="o"><</span><span class="kt">CommonDefault</span><span class="o">.</span><span class="kt">Empty</span><span class="o"><</span><span class="kt">Value</span><span class="o">>></span>
<span class="p">}</span>
</code></pre></div></div>
<p>Then we can use them this way.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Foo</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="p">{</span>
<span class="kd">@DefaultBy</span><span class="o">.</span><span class="kt">Empty</span> <span class="k">var</span> <span class="nv">array</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span>
<span class="kd">@DefaultBy</span><span class="o">.</span><span class="kt">Empty</span> <span class="k">var</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span>
<span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s better. Keep going for <code class="language-plaintext highlighter-rouge">Numeric</code> types.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">CommonDefault</span> <span class="p">{</span>
<span class="o">...</span>
<span class="kd">struct</span> <span class="kt">Zero</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Numeric</span><span class="o">></span><span class="p">:</span> <span class="kt">Default</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">Value</span> <span class="p">{</span>
<span class="o">.</span><span class="n">zero</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">One</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Numeric</span><span class="o">></span><span class="p">:</span> <span class="kt">Default</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">Value</span> <span class="p">{</span>
<span class="mi">1</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">MinusOne</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Numeric</span><span class="o">></span><span class="p">:</span> <span class="kt">Default</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">Value</span> <span class="p">{</span>
<span class="o">-</span><span class="mi">1</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">enum</span> <span class="kt">DefaultBy</span> <span class="p">{</span>
<span class="o">...</span>
<span class="kd">typealias</span> <span class="kt">Zero</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="o">&</span> <span class="kt">Numeric</span><span class="o">></span> <span class="o">=</span> <span class="kt">DefaultDecodable</span><span class="o"><</span><span class="kt">CommonDefault</span><span class="o">.</span><span class="kt">Zero</span><span class="o"><</span><span class="kt">Value</span><span class="o">>></span>
<span class="kd">typealias</span> <span class="kt">One</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="o">&</span> <span class="kt">Numeric</span><span class="o">></span> <span class="o">=</span> <span class="kt">DefaultDecodable</span><span class="o"><</span><span class="kt">CommonDefault</span><span class="o">.</span><span class="kt">One</span><span class="o"><</span><span class="kt">Value</span><span class="o">>></span>
<span class="kd">typealias</span> <span class="kt">MinusOne</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="o">&</span> <span class="kt">Numeric</span><span class="o">></span> <span class="o">=</span> <span class="kt">DefaultDecodable</span><span class="o"><</span><span class="kt">CommonDefault</span><span class="o">.</span><span class="kt">MinusOne</span><span class="o"><</span><span class="kt">Value</span><span class="o">>></span>
<span class="p">}</span>
</code></pre></div></div>
<p>With them, it would be enough for most of the cases.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Foo</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="p">{</span>
<span class="o">...</span>
<span class="kd">@DefaultBy</span><span class="o">.</span><span class="kt">Zero</span> <span class="k">var</span> <span class="nv">int</span><span class="p">:</span> <span class="kt">Int</span>
<span class="kd">@DefaultBy</span><span class="o">.</span><span class="kt">MinusOne</span> <span class="k">var</span> <span class="nv">double</span><span class="p">:</span> <span class="kt">Double</span>
<span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Finally, let’s handle the case that a type has its own default value.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protocol</span> <span class="kt">SelfDefault</span><span class="p">:</span> <span class="kt">Default</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="k">Self</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">Bar</span><span class="p">:</span> <span class="kt">SelfDefault</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">Bar</span> <span class="p">{</span> <span class="o">.</span><span class="n">case2</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">enum</span> <span class="kt">DefaultBy</span> <span class="p">{</span>
<span class="o">...</span>
<span class="kd">typealias</span> <span class="p">`</span><span class="nv">Self</span><span class="p">`</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="o">&</span> <span class="kt">SelfDefault</span><span class="o">></span> <span class="o">=</span> <span class="kt">DefaultDecodable</span><span class="o"><</span><span class="kt">Value</span><span class="o">></span>
<span class="p">}</span>
</code></pre></div></div>
<p>But this is impossible since the type implemented <code class="language-plaintext highlighter-rouge">SelfDefault</code> may be itself or its subclass.</p>
<p>Let’s use itself expressly.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protocol</span> <span class="kt">SelfDefault</span> <span class="p">{</span> <span class="c1">// No need to inherit `Default` anymore</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="k">Self</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">enum</span> <span class="kt">CommonDefault</span> <span class="p">{</span>
<span class="o">...</span>
<span class="kd">struct</span> <span class="p">`</span><span class="nv">Self</span><span class="p">`</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">SelfDefault</span><span class="o">></span><span class="p">:</span> <span class="kt">Default</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">Value</span> <span class="p">{</span>
<span class="kt">Value</span><span class="o">.</span><span class="n">defaultValue</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">enum</span> <span class="kt">DefaultBy</span> <span class="p">{</span>
<span class="o">...</span>
<span class="kd">typealias</span> <span class="p">`</span><span class="nv">Self</span><span class="p">`</span><span class="o"><</span><span class="kt">Value</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="o">&</span> <span class="kt">SelfDefault</span><span class="o">></span> <span class="o">=</span> <span class="kt">DefaultDecodable</span><span class="o"><</span><span class="kt">CommonDefault</span><span class="o">.</span><span class="k">Self</span><span class="o"><</span><span class="kt">Value</span><span class="o">>></span>
<span class="p">}</span>
</code></pre></div></div>
<p>That’s it. Now our <code class="language-plaintext highlighter-rouge">Foo</code> is far more straightforward, and the auto-generated initializer still exists.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Foo</span><span class="p">:</span> <span class="kt">Decodable</span> <span class="p">{</span>
<span class="kd">@DefaultBy</span><span class="o">.</span><span class="kt">Empty</span> <span class="k">var</span> <span class="nv">array</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span>
<span class="kd">@DefaultBy</span><span class="o">.</span><span class="kt">Empty</span> <span class="k">var</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span>
<span class="kd">@DefaultBy</span><span class="o">.</span><span class="kt">Zero</span> <span class="k">var</span> <span class="nv">int</span><span class="p">:</span> <span class="kt">Int</span>
<span class="kd">@DefaultBy</span><span class="o">.</span><span class="kt">MinusOne</span> <span class="k">var</span> <span class="nv">double</span><span class="p">:</span> <span class="kt">Double</span>
<span class="kd">@DefaultBy</span><span class="o">.</span><span class="k">Self</span> <span class="k">var</span> <span class="nv">bar</span><span class="p">:</span> <span class="kt">Bar</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The final result is <a href="https://github.com/myihsan/DefaultDecodableWrapper">here</a>.</p>
<h2 id="conclusions">Conclusions</h2>
<p>This kind of issue should be fixed by the server-side, but if we don’t control the server-side, a property wrapper can help us deal with it elegantly.</p>
<p>The power of property wrapper may be beyond our imagination, so when some issues may be solved by a getter or/and a setter, let’s give it a try to use property wrappers.</p>Jierong Lijierongli@proton.meTo provide a default value for a Decodable type’s properties, we need to define CodingKeys and use it to implement init(from:).Touch Events Not Firing In UITableViewCell2020-12-20T00:00:00+00:002020-12-20T00:00:00+00:00https://jierong.dev/2020/12/20/touch-events-not-firing-in-uitableviewcell<p>Many reasons cause prevent touch events from firing. Such as the button is outside the bounds of superview or there is a gesture recognizer that fired first. But do you have the experience that a view’s touch events work individually but will not fire in a <code class="language-plaintext highlighter-rouge">UITableViewCell</code>?</p>
<p>There is a button in <code class="language-plaintext highlighter-rouge">UITableViewCell</code>.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">TableViewCell</span><span class="p">:</span> <span class="kt">UITableViewCell</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">button</span> <span class="o">=</span> <span class="kt">UIButton</span><span class="p">()</span>
<span class="k">override</span> <span class="nf">init</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="kt">UITableViewCell</span><span class="o">.</span><span class="kt">CellStyle</span><span class="p">,</span> <span class="nv">reuseIdentifier</span><span class="p">:</span> <span class="kt">String</span><span class="p">?)</span> <span class="p">{</span>
<span class="k">super</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="n">style</span><span class="p">,</span> <span class="nv">reuseIdentifier</span><span class="p">:</span> <span class="n">reuseIdentifier</span><span class="p">)</span>
<span class="nf">addSubview</span><span class="p">(</span><span class="n">button</span><span class="p">)</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Just a single <code class="language-plaintext highlighter-rouge">UIButton</code>, but it’s touch events will not fire.</p>
<p>The reason is simple but may be hard to notice. Let’s find out why.</p>
<p>Override <code class="language-plaintext highlighter-rouge">hitTest(_:with:)</code> and add a breakpoint.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="kd">func</span> <span class="nf">hitTest</span><span class="p">(</span><span class="n">_</span> <span class="nv">point</span><span class="p">:</span> <span class="kt">CGPoint</span><span class="p">,</span> <span class="n">with</span> <span class="nv">event</span><span class="p">:</span> <span class="kt">UIEvent</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">UIView</span><span class="p">?</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">view</span> <span class="o">=</span> <span class="nf">hitTest</span><span class="p">(</span><span class="n">point</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="n">event</span><span class="p">)</span>
<span class="k">return</span> <span class="n">view</span> <span class="c1">// Breakpoint</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We will notice that the view is a <code class="language-plaintext highlighter-rouge">UITableViewCellContentView</code>. We added the button directly to <code class="language-plaintext highlighter-rouge">UITableViewCell</code> instead of it’s <code class="language-plaintext highlighter-rouge">contentView</code>.</p>
<p>This is indeed a fundamental mistake, but if you invest it in some other way first like me, it will take some unnecessary time. If there is something wrong with touch events, let’s try overriding <code class="language-plaintext highlighter-rouge">hitTest(_:with:)</code> of the view’s superview first.</p>Jierong Lijierongli@proton.meMany reasons cause prevent touch events from firing. Such as the button is outside the bounds of superview or there is a gesture recognizer that fired first. But do you have the experience that a view’s touch events work individually but will not fire in a UITableViewCell?Xcodeサーバーでの継続的インテグレーション2020-12-13T00:00:00+00:002020-12-13T00:00:00+00:00https://jierong.dev/ja/2020/12/13/continuous-integration-using-xcode-server<p>継続的インテグレーション、CI(continuous integration)といえばGithub Actions、Bitrise、Jenkinsなどのサードパーティサービスやツールが浮かびますよね。実際にXcodeを使うだけで、簡単にできます。</p>
<p>まずはCIの実行環境として使うMacからXcode Serverを立ち上げます。</p>
<p><img src="/assets/images/turning-on-xcode-server.png" alt="Xcode Serverの立ち上げ" /></p>
<p>Xcode > PreferencesのServers & Botsの右上のスイッチをオンにして、CIを実装するユーザーを選んだら、Xcode Serverの立ち上げが完了しました。</p>
<p>そして開発用のMacからXcode Serverの接続設定をします。</p>
<p><img src="/assets/images/adding-xcode-server.png" alt="Xcode Serverへの接続設定" /></p>
<p>Xcode > PreferencesのAccountsから追加します。</p>
<p>最後は実際にCIを実行するボットを設定します。</p>
<p>Xcodeでプロジェクトを開いた状態で、Product > Create Bot…を選びます。</p>
<p>リポジトリーのアクセス権限が確認され、プライベートリポジトリーの場合なぜか下記のエラーが出てきます。</p>
<p><img src="/assets/images/ssh-fingerprint-failed-to-verify-when-creating-bot.png" alt="SSH指紋検証失敗" /></p>
<p>私の環境がおかしいかもしれませんが、ViewボタンをクリックしてCancelすればアクセスの確認が再開し、今度は正常な認証失敗になります。</p>
<p><img src="/assets/images/credentials-missing-when-creating-bot.png" alt="認証失敗" /></p>
<p>Sign In…ボタンをクリックして認証情報を提供すれば、リポジトリーアクセス権限の確認できます<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>。</p>
<p>CIを実行するブランチの選択、build configurationの設定とインテグレーションの設定を経て、テストを実行するデバイスの設定になります。</p>
<p><img src="/assets/images/devices-for-bot-to-test-with.png" alt="テストを実行するデバイスの設定" /></p>
<p>全てのデバイスで同時にテストすることは可能ですが、XcodeはDepolyment Targetにより、実行できないデバイスを排除してくれないので、Specific iOS Devices and Simulatorsを選択し、実行可能なデバイスのみ選択する必要があります。</p>
<p>続きは署名とプロビジョニングの設定で、simulatorのみであれば不要になります。</p>
<p>xcodebuildの追加プロパティーと環境変数の設定を経て、triggersの設定になります。XcodeGenやCocoapodsなどを使ってビルドする前にスクリプトを実装する必要がある場合、ここで追加できます。</p>
<p>XcodeGenでは下記のPre-Intergration Scriptを追加する必要があります。</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># スクリプトがbotのルートから実行されるため、プロジェクト配下に移動</span>
<span class="nb">cd </span>ProjectName
<span class="c"># 多数のツールを使う場合exportしてからでも良い</span>
/usr/local/bin/xcodegen
</code></pre></div></div>
<p>これでbotの作成が完了で、初回のインテグレーションが始めます。今後設定された条件(間隔やコミット)で自動にインテグレーションが始めます。</p>
<p>Xcode上の操作を加え、<a href="https://developer.app">API</a>もありますので、ある程度の自動化カスタマイズが可能だと思います。</p>
<h2 id="結論">結論</h2>
<p>Xcodeサーバーでの継続的インテグレーションの設定は極めて簡単で、すぐできます。機能はそこまで多くないけど、インテグレーション前後スクリプトを実行することが可能ですので、大体の重要が満足でき、わりといい選択肢です。余分なMacがあればぜひ試してみてください。</p>
<h2 id="参考">参考</h2>
<p><a href="https://help.apple.com/xcode/mac/11.4/index.html?localePath=en.lproj#/dev466720061">Continuous integration using Xcode Server</a></p>
<hr />
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>ここはあくまでの確認で、実際の認証設定は実際にCIを実行するMacで設定する必要があります。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Jierong Lijierongli@proton.me継続的インテグレーション、CI(continuous integration)といえばGithub Actions、Bitrise、Jenkinsなどのサードパーティサービスやツールが浮かびますよね。実際にXcodeを使うだけで、簡単にできます。Repeating Alerts like Messages2020-12-06T00:00:00+00:002020-12-06T00:00:00+00:00https://jierong.dev/2020/12/06/repeating-alerts-like-messages<p>Messages will repeat using notification to alert us about new messages many times<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> to ensure that we are not missing new messages. Today, let’s talk about how to implement it by ourselves.</p>
<p>Suppose all we want to repeat is a remote notification. In that case, all we need to do is removing any keys that would trigger user interactions from the payload to silence the notification and scheduling a local notification along with a repeating notification created by a <code class="language-plaintext highlighter-rouge">UNTimeIntervalNotificationTrigger</code>.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">trigger</span> <span class="o">=</span> <span class="kt">UNTimeIntervalNotificationTrigger</span><span class="p">(</span><span class="nv">timeInterval</span><span class="p">:</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">5</span><span class="p">,</span> <span class="nv">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">uuidString</span> <span class="o">=</span> <span class="kt">UUID</span><span class="p">()</span><span class="o">.</span><span class="n">uuidString</span>
<span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="kt">UNNotificationRequest</span><span class="p">(</span><span class="nv">identifier</span><span class="p">:</span> <span class="n">uuidString</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span> <span class="nv">trigger</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">repeatingRequest</span> <span class="o">=</span> <span class="kt">UNNotificationRequest</span><span class="p">(</span><span class="nv">identifier</span><span class="p">:</span> <span class="n">uuidString</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span> <span class="nv">trigger</span><span class="p">:</span> <span class="n">trigger</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">userNotificationCenter</span> <span class="o">=</span> <span class="kt">UNUserNotificationCenter</span><span class="o">.</span><span class="nf">current</span><span class="p">()</span>
<span class="n">userNotificationCenter</span><span class="o">.</span><span class="nf">add</span><span class="p">(</span><span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="n">error</span> <span class="k">in</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="n">userNotificationCenter</span><span class="o">.</span><span class="nf">add</span><span class="p">(</span><span class="n">repeatingRequest</span><span class="p">)</span> <span class="p">{</span> <span class="n">error</span> <span class="k">in</span>
<span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Don’t forget to remove <code class="language-plaintext highlighter-rouge">repeatingRequest</code> after the user responded to the notification or launched the app.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">userNotificationCenter</span><span class="p">(</span><span class="n">_</span> <span class="nv">center</span><span class="p">:</span> <span class="kt">UNUserNotificationCenter</span><span class="p">,</span> <span class="n">didReceive</span> <span class="nv">response</span><span class="p">:</span> <span class="kt">UNNotificationResponse</span><span class="p">,</span> <span class="n">withCompletionHandler</span> <span class="nv">completionHandler</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">identifier</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">notification</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">identifier</span>
<span class="n">center</span><span class="o">.</span><span class="nf">removePendingNotificationRequests</span><span class="p">(</span><span class="nv">withIdentifiers</span><span class="p">:</span> <span class="p">[</span><span class="n">identifier</span><span class="p">])</span>
<span class="p">}</span>
</code></pre></div></div>
<p>A limit of <code class="language-plaintext highlighter-rouge">UNTimeIntervalNotificationTrigger</code> for repeating notifications is that the <code class="language-plaintext highlighter-rouge">timeInterval</code> must be 60 seconds or greater.</p>
<p>But how to repeat a notification created by <code class="language-plaintext highlighter-rouge">UNCalendarNotificationTrigger</code>?</p>
<p>The <code class="language-plaintext highlighter-rouge">repeats</code> in the initialization means to reschedule the notification request each time the notification is delivered to the next cycle, which has the same <code class="language-plaintext highlighter-rouge">dateComponents</code>, so it’s not what we want.</p>
<p>We have to change the approach. Add multiple requests to simulate the behavior of a repeating notification.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="k">let</span> <span class="nv">calendar</span> <span class="o">=</span> <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span>
<span class="k">for</span> <span class="n">offsetCount</span> <span class="k">in</span> <span class="mi">0</span> <span class="o">..<</span> <span class="mi">10</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">date</span> <span class="o">=</span> <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">byAdding</span><span class="p">:</span> <span class="o">.</span><span class="n">minute</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">offsetCount</span> <span class="o">*</span> <span class="mi">5</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="n">someDate</span><span class="p">)</span><span class="o">!</span>
<span class="k">var</span> <span class="nv">dateComponents</span> <span class="o">=</span> <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="nf">dateComponents</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="o">.</span><span class="n">current</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="n">date</span><span class="p">)</span>
<span class="c1">// http://www.openradar.me/35247464</span>
<span class="n">dateComponents</span><span class="o">.</span><span class="n">quarter</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="k">let</span> <span class="nv">trigger</span> <span class="o">=</span> <span class="kt">UNCalendarNotificationTrigger</span><span class="p">(</span><span class="nv">dateMatching</span><span class="p">:</span> <span class="n">dateComponents</span><span class="p">,</span> <span class="nv">repeats</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">repeatingRequest</span> <span class="o">=</span> <span class="kt">UNNotificationRequest</span><span class="p">(</span><span class="nv">identifier</span><span class="p">:</span> <span class="n">uuidString</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span> <span class="nv">trigger</span><span class="p">:</span> <span class="n">trigger</span><span class="p">)</span>
<span class="n">userNotificationCenter</span><span class="o">.</span><span class="nf">add</span><span class="p">(</span><span class="n">repeatingRequest</span><span class="p">)</span> <span class="p">{</span> <span class="n">error</span> <span class="k">in</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Only the last <code class="language-plaintext highlighter-rouge">repeatingRequest</code> was fulfilled. It seems like we cannot use the same identifier for every <code class="language-plaintext highlighter-rouge">repeatingRequest</code>.</p>
<p>So let’s use a different identifier for each <code class="language-plaintext highlighter-rouge">repeatingRequest</code>.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">repeatingRequest</span> <span class="o">=</span> <span class="kt">UNNotificationRequest</span><span class="p">(</span><span class="nv">identifier</span><span class="p">:</span> <span class="s">"</span><span class="se">\(</span><span class="n">uuidString</span><span class="se">)</span><span class="s">@</span><span class="se">\(</span><span class="n">date</span><span class="o">.</span><span class="n">timeIntervalSince1970</span><span class="o">.</span><span class="n">description</span><span class="se">)</span><span class="s">"</span><span class="p">,</span>
<span class="nv">content</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span> <span class="nv">trigger</span><span class="p">:</span> <span class="n">trigger</span><span class="p">)</span>
</code></pre></div></div>
<p>Also, update the code to remove it accordingly.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">userNotificationCenter</span><span class="p">(</span><span class="n">_</span> <span class="nv">center</span><span class="p">:</span> <span class="kt">UNUserNotificationCenter</span><span class="p">,</span> <span class="n">didReceive</span> <span class="nv">response</span><span class="p">:</span> <span class="kt">UNNotificationResponse</span><span class="p">,</span> <span class="n">withCompletionHandler</span> <span class="nv">completionHandler</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">uuidString</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">notification</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">identifier</span><span class="o">.</span><span class="nf">split</span><span class="p">(</span><span class="nv">separator</span><span class="p">:</span> <span class="s">"@"</span><span class="p">)</span><span class="o">.</span><span class="n">first</span><span class="o">!</span>
<span class="n">center</span><span class="o">.</span><span class="n">getPendingNotificationRequests</span> <span class="p">{</span> <span class="n">requests</span> <span class="k">in</span>
<span class="k">let</span> <span class="nv">identifiers</span> <span class="o">=</span> <span class="n">requests</span>
<span class="o">.</span><span class="nf">map</span><span class="p">(\</span><span class="o">.</span><span class="n">identifier</span><span class="p">)</span>
<span class="o">.</span><span class="n">filter</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="nf">starts</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">uuidString</span><span class="p">)</span> <span class="p">}</span>
<span class="n">center</span><span class="o">.</span><span class="nf">removePendingNotificationRequests</span><span class="p">(</span><span class="nv">withIdentifiers</span><span class="p">:</span> <span class="n">identifiers</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It just works, and it’s even able to break the limit of 60 seconds. However, there are some side effects of this approach.</p>
<p>Since we are using different identifiers, every notification is count as a new one. Therefore, we should consider using <code class="language-plaintext highlighter-rouge">threadIdentifier</code> to group related notifications.</p>
<p>Besides, the system only keeps the soonest 64 requests<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>, so we should add requests according to their priority.</p>
<p>For example, cut down the repeat times.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">userNotificationCenter</span><span class="o">.</span><span class="n">getPendingNotificationRequests</span> <span class="p">{</span> <span class="n">requests</span> <span class="k">in</span>
<span class="k">let</span> <span class="nv">remaining</span> <span class="o">=</span> <span class="mi">64</span> <span class="o">-</span> <span class="n">requests</span><span class="o">.</span><span class="n">count</span>
<span class="k">guard</span> <span class="n">remaining</span> <span class="o">></span> <span class="mi">0</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// Drop the most unimportant request or let the user decide</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">for</span> <span class="n">offsetCount</span> <span class="k">in</span> <span class="mi">0</span> <span class="o">..<</span> <span class="nf">min</span><span class="p">(</span><span class="n">remaining</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="conclusions">Conclusions</h2>
<p>For a remote notification, use <code class="language-plaintext highlighter-rouge">UNTimeIntervalNotificationTrigger</code> to repeat.</p>
<p>For a notification to be delivered at a specific date and time, also schedule the following notifications to make it look repeating. Be careful of the limit of the number of notification requests.</p>
<hr />
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>We can change the setting from Settings > Notifications > Messages > Repeat Alerts. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>This feature is not working on some of my devices. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p>It’s only documented in <a href="https://developer.apple.com/documentation/uikit/uilocalnotification">UILocalNotification</a>, but it seems still correct. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Jierong Lijierongli@proton.meMessages will repeat using notification to alert us about new messages many times1 2 to ensure that we are not missing new messages. Today, let’s talk about how to implement it by ourselves. We can change the setting from Settings > Notifications > Messages > Repeat Alerts. ↩ This feature is not working on some of my devices. ↩LAPolicy to use for authentication2020-11-29T00:00:00+00:002020-11-29T00:00:00+00:00https://jierong.dev/2020/11/29/lapolicy-to-use<p><a href="https://developer.apple.com/documentation/localauthentication/logging_a_user_into_your_app_with_face_id_or_touch_id">Logging a User with Face ID or Touch ID</a> is simple, all we need to do is to include the <code class="language-plaintext highlighter-rouge">NSFaceIDUsageDescription</code> key in Info.plist file and choose a LAPolicy to evaluate. But which one should we choose?</p>
<p>The document above uses <code class="language-plaintext highlighter-rouge">deviceOwnerAuthentication</code>, which will fallback to the device’s passcode when biometrics (Face ID or Touch ID) fails or is unavailable and indicates that we can use <code class="language-plaintext highlighter-rouge">deviceOwnerAuthenticationWithBiometrics</code> to avoid the fallback.</p>
<p>So we may consider using <code class="language-plaintext highlighter-rouge">deviceOwnerAuthenticationWithBiometrics</code> in some cases required higher security. But it’s not a good idea for security.</p>
<p>Firstly, we cannot change our biometrics by purpose, so once our biometric is compromised, it may not secure anymore.</p>
<p>Secondly, <code class="language-plaintext highlighter-rouge">deviceOwnerAuthenticationWithBiometrics</code> has almost the same security level as <code class="language-plaintext highlighter-rouge">deviceOwnerAuthentication</code>. Indeed <code class="language-plaintext highlighter-rouge">deviceOwnerAuthentication</code> will fallback to the passcode, making it look less secure, but we can change the biometric settings by the passcode from Settings.</p>
<p>For the devices using Face ID, we cannot prevent other people from using the passcode to change our biometric settings, but we can notice the change. The setting button has three states:</p>
<ol>
<li>Set Up Face ID</li>
<li>Set Up an Alternate Appearance</li>
<li>Reset Face ID</li>
</ol>
<p>To remove the alternate appearance, we have to reset Face ID. So once the state has been changed or cannot unlock by our face, we can notice that our biometric settings have changed.</p>
<p>However, for the devices using Touch ID, we can add a new fingerprint to pass the biometric verification and delete it later using the passcode. The only way to notice biometric settings change is to set up all five (max) fingerprints at first and check if the five fingerprints are still working.</p>
<h2 id="conclusions">Conclusions</h2>
<p>Using <code class="language-plaintext highlighter-rouge">deviceOwnerAuthentication</code> should be a better choice for authentication. And it may cause a positive side effect to let users keep their passcode safely.</p>
<p>If there are some reasons to use <code class="language-plaintext highlighter-rouge">deviceOwnerAuthenticationWithBiometrics</code>, consider providing a setting for the users who want to fallback to the passcode when biometrics fails or is unavailable, since we have to wear a mask every day nowadays.</p>Jierong Lijierongli@proton.meLogging a User with Face ID or Touch ID is simple, all we need to do is to include the NSFaceIDUsageDescription key in Info.plist file and choose a LAPolicy to evaluate. But which one should we choose?Safari機能拡張の作成2020-11-15T00:00:00+00:002020-11-15T00:00:00+00:00https://jierong.dev/ja/2020/11/15/building-a-safari-app-extension<p>Big Surにアップデートし、Safariをデフォルトブラウザーにしてみたら、結構サクサクで気に入りましたが、愛用してた<a href="https://www.wikiwand.com">Wikiwand</a>はSafariの機能拡張を提供していません。機能拡張とはいえ、ただWikipediaのURLを一定規則でWikiwandのURLに変更するものはずなので、自分で作りましょう!</p>
<h2 id="プロジェクト作成">プロジェクト作成</h2>
<p>macOS 10.14 からSafari機能拡張を提供するにはmacOSアプリを作成するしかないので、新規iOSプロジェクトを作成するようにXcodeから作成します。</p>
<p>「macOS」>「Safari Extension App」を選択し、次の画面にあるTypeをSafari App Extensionを選択し<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>、作成します。</p>
<h2 id="権限声明">権限声明</h2>
<p>テンプレートはすでに声明してくれましたが、必要な手順を確認しましょう。</p>
<p><code class="language-plaintext highlighter-rouge">Info.plist</code>にある<code class="language-plaintext highlighter-rouge">NSExtension</code>の配下に、値が辞書型の<code class="language-plaintext highlighter-rouge">SFSafariWebsiteAccess</code>を追加します。</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><key></span>SFSafariWebsiteAccess<span class="nt"></key></span>
<span class="nt"><dict></span>
// ここで声明
<span class="nt"></dict></span>
</code></pre></div></div>
<p>これからは権限の声明です。まずは権限レベルの声明です。</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><key></span>Level<span class="nt"></key></span>
<span class="nt"><string></span>Some<span class="nt"></string></span>
</code></pre></div></div>
<p>権限は<code class="language-plaintext highlighter-rouge">None</code>、<code class="language-plaintext highlighter-rouge">Some</code>、<code class="language-plaintext highlighter-rouge">All</code>で、今度はWikipediaのページをWikiwandにリダイレクトするので、<code class="language-plaintext highlighter-rouge">Some</code>で十分です。</p>
<p><code class="language-plaintext highlighter-rouge">Some</code>ですので、対象ドメインを声明する必要があります。</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><key></span>Allowed Domains<span class="nt"></key></span>
<span class="nt"><array></span>
<span class="nt"><string></span>*.wikipedia.org<span class="nt"></string></span>
<span class="nt"></array></span>
</code></pre></div></div>
<p>記事の言語により、サブドメインがかまりますので、ワイルドカードを使います。</p>
<p>ほとんどテンプレートに含まれ、実作業はドメインをWikipediaのドメインにしただけです。</p>
<h2 id="スクリプト追加">スクリプト追加</h2>
<p>同様にテンプレートは追加してくれましたが、必要な手順を確認しましょう。</p>
<p>同じく<code class="language-plaintext highlighter-rouge">Info.plist</code>にある<code class="language-plaintext highlighter-rouge">NSExtension</code>の配下に声明する必要があります。</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><key></span>SFSafariContentScript<span class="nt"></key></span>
<span class="nt"><array></span>
<span class="nt"><dict></span>
<span class="nt"><key></span>Script<span class="nt"></key></span>
<span class="nt"><string></span>script.js<span class="nt"></string></span>
<span class="nt"></dict></span>
<span class="nt"></array></span>
</code></pre></div></div>
<p>配列の中に辞書型で声明します。多数のスクリプトを追加することが可能で、配列は必要ですが、なぜかスクリプトファイルを声明するには、キーが<code class="language-plaintext highlighter-rouge">Script</code>で、値をbundleに含まれたスクリプトファイルのパスにする必要があります。ちょっと間違いやすい形式ですよね。</p>
<p>声明が完了したら、残りはスクリプトファイルの追加と実装だけです。Safari機能拡張と本体アプリは別ターゲットで、Safari機能拡張にスクリプトファイルを追加する必要があります。</p>
<p>最後はリダイレクトの実装です。</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">redirectToWikiwand</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">pathnameComponents</span> <span class="o">=</span> <span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">pathnameComponentsLength</span> <span class="o">=</span> <span class="nx">pathnameComponents</span><span class="p">.</span><span class="nx">length</span>
<span class="k">if</span> <span class="p">(</span> <span class="nx">pathnameComponents</span><span class="p">[</span><span class="nx">pathnameComponentsLength</span> <span class="o">-</span> <span class="mi">2</span><span class="p">]</span> <span class="o">!=</span> <span class="dl">'</span><span class="s1">wiki</span><span class="dl">'</span> <span class="p">)</span> <span class="k">return</span>
<span class="kd">const</span> <span class="nx">languageCode</span> <span class="o">=</span> <span class="nx">location</span><span class="p">.</span><span class="nx">hostname</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="kd">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="nx">pathnameComponents</span><span class="p">[</span><span class="nx">pathnameComponentsLength</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span> <span class="nx">title</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">Main_Page</span><span class="dl">'</span> <span class="p">)</span> <span class="k">return</span>
<span class="kd">const</span> <span class="nx">wikiwandURL</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">https://www.wikiwand.com/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">languageCode</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">title</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">hash</span>
<span class="nx">location</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="nx">wikiwandURL</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">redirectToWikiwand</span><span class="p">()</span>
</code></pre></div></div>
<p>JavaScriptは詳しくないし、Wikiwand公式の変換方法もわからないので、もっと改善できるはずですが、これでWikiwandへリダイレクトできます。</p>
<h2 id="使用">使用</h2>
<p>Apple Developer ProgramのアカウントがあればTeamを選び、Signing CertificateをDevelopmentにすれば、RunでSafari機能拡張を使えます<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>。</p>
<p>Apple Developer Programのアカウントがない場合、Runしても「Safari」>「環境設定」>「機能拡張」にありませんが、「Safari」>「環境設定」>「メニューバーに”開発”メニューを表示」をチェックし、「開発」 > 「未署名の機能拡張を許可」しすれば、出てきます<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>。</p>
<h2 id="結論">結論</h2>
<p>JavaScriptの知識があれば、Safari機能拡張自体は難なく作成できます。もちろん複雑になると自作するのは大変になりますが、Safariをデフォルトブラウザとして使うため、欲しい機能拡張がまだMac App Storeにない場合、自分で作ってみましょう!</p>
<hr />
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>Safari Web Extensionにすると、実装方法が変わります。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>「Safari」>「環境設定」>「機能拡張」で有効にする必要があります。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p>Safariを再起動したら、再設定する必要があります。 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Jierong Lijierongli@proton.meBig Surにアップデートし、Safariをデフォルトブラウザーにしてみたら、結構サクサクで気に入りましたが、愛用してたWikiwandはSafariの機能拡張を提供していません。機能拡張とはいえ、ただWikipediaのURLを一定規則でWikiwandのURLに変更するものはずなので、自分で作りましょう!kotlinx-datetime2020-11-08T00:00:00+00:002020-11-08T00:00:00+00:00https://jierong.dev/2020/11/08/kotlinx-datetime<p>KotlinX multiplatform date/time library–<a href="https://github.com/Kotlin/kotlinx-datetime"><code class="language-plaintext highlighter-rouge">kotlinx-datetime</code></a> is still under pre-release, but it has the potential to become the major date/time library in Kotlin world. So it’s worth having a peek.</p>
<p>Let’s go through some common cases using kotlinx-datetime.</p>
<h2 id="getting-date-components">Getting Date Components</h2>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">instant</span> <span class="p">=</span> <span class="nc">Clock</span><span class="p">.</span><span class="nc">System</span><span class="p">.</span><span class="nf">now</span><span class="p">()</span>
<span class="kd">val</span> <span class="py">localDateTime</span> <span class="p">=</span> <span class="n">instant</span><span class="p">.</span><span class="nf">toLocalDateTime</span><span class="p">(</span><span class="nc">TimeZone</span><span class="p">.</span><span class="nf">currentSystemDefault</span><span class="p">())</span>
<span class="kd">val</span> <span class="py">month</span> <span class="p">=</span> <span class="n">localDateTime</span><span class="p">.</span><span class="n">monthNumber</span>
</code></pre></div></div>
<p>A littile inconvenience since there is no <code class="language-plaintext highlighter-rouge">LocalDateTime.now()</code> like the <code class="language-plaintext highlighter-rouge">LocalDateTime</code> in Java.</p>
<h2 id="setting-date-components">Setting Date Components</h2>
<p>It seems like there the only way to set date components is from the constructor of the <code class="language-plaintext highlighter-rouge">LocalDateTime</code>.</p>
<p>Since <code class="language-plaintext highlighter-rouge">LocalDateTime</code> in kotlinx-datetime is a wrapper for <code class="language-plaintext highlighter-rouge">LocalDateTime</code> in Java, we should get this feature in a future release.</p>
<p>But if we only want to add an amount of date/time units for our date/time, we can use the <code class="language-plaintext highlighter-rouge">plus</code> function of <code class="language-plaintext highlighter-rouge">Instant</code>.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">otherInstant</span> <span class="n">instant</span><span class="p">.</span><span class="nf">plus</span><span class="p">(</span><span class="nc">DateTimePeriod</span><span class="p">(</span><span class="n">months</span> <span class="p">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">days</span> <span class="p">=</span> <span class="mi">7</span><span class="p">),</span> <span class="nc">TimeZone</span><span class="p">.</span><span class="nf">currentSystemDefault</span><span class="p">())</span>
</code></pre></div></div>
<h2 id="comparing-two-datetime">Comparing Two Date/Time</h2>
<pre><code class="language-Kotlin">instant.periodUntil(otherInstant, TimeZone.currentSystemDefault()) // DateTimePeriod(months = 2, days = 7)
</code></pre>
<p>Or get the difference of a particular unit like <code class="language-plaintext highlighter-rouge">Temporal</code> in Java.</p>
<pre><code class="language-Kotlin">instant.until(otherInstant), DateTimeUnit.MONTH, TimeZone.currentSystemDefault()) // 2
</code></pre>
<h2 id="conclutions-for-v01">Conclutions for v0.1</h2>
<p>It’s great that we finally have an official date/time library. However, its features are still limited.</p>
<p>If we compare it with <code class="language-plaintext highlighter-rouge">java.time</code>, we can tell that they are similar and <code class="language-plaintext highlighter-rouge">java.time</code> is handier for now. But <code class="language-plaintext highlighter-rouge">kotlinx-datetime</code> also has its things such as <code class="language-plaintext highlighter-rouge">DateTimePeriod</code>, so I think it’s expectable that <code class="language-plaintext highlighter-rouge">kotlinx-datetime</code> may catch up or even surpass <code class="language-plaintext highlighter-rouge">java.time</code> in the future.</p>
<p>Let’s start to keep an eye on <code class="language-plaintext highlighter-rouge">kotlinx-datetime</code> and contribute to it if we have a chance.</p>Jierong Lijierongli@proton.meKotlinX multiplatform date/time library–kotlinx-datetime is still under pre-release, but it has the potential to become the major date/time library in Kotlin world. So it’s worth having a peek.