<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Lynx</title>
    <description>The latest articles on DEV Community by Michael Lynx (@silvester).</description>
    <link>https://dev.to/silvester</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4194465%2Fa34fcec2-1500-468b-b4af-bca8a55296e9.png</url>
      <title>DEV Community: Michael Lynx</title>
      <link>https://dev.to/silvester</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/silvester"/>
    <language>en</language>
    <item>
      <title>Moving a notes library between iPhones without a server or iCloud</title>
      <dc:creator>Michael Lynx</dc:creator>
      <pubDate>Fri, 23 Oct 2026 16:46:51 +0000</pubDate>
      <link>https://dev.to/silvester/moving-a-notes-library-between-iphones-without-a-server-or-icloud-1kn5</link>
      <guid>https://dev.to/silvester/moving-a-notes-library-between-iphones-without-a-server-or-icloud-1kn5</guid>
      <description>&lt;p&gt;I work on Diple, a Markdown writing app with a local document library. This article looks at one part of its implementation: moving that library between devices.&lt;/p&gt;

&lt;p&gt;The transfer needs to preserve more than text files. Documents can have local images, notes and version history. Folders and Collections also carry relationships that need to survive the move.&lt;/p&gt;

&lt;p&gt;Here are the protocol and import decisions in the current design, including the limits of its duplicate handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the connection from the transfer
&lt;/h2&gt;

&lt;p&gt;The existing transport uses Multipeer Connectivity. Both devices advertise and browse for the same service, and the user selects a peer and confirms the connection.&lt;/p&gt;

&lt;p&gt;The session is configured to require encryption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MCSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;peerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;securityIdentity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;encryptionPreference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;required&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That configuration does not replace peer selection or user consent. Connecting the devices and agreeing to transfer a particular batch are separate steps.&lt;/p&gt;

&lt;p&gt;For a new implementation, review Apple's &lt;a href="https://developer.apple.com/documentation/technotes/tn3151-choosing-the-right-networking-api" rel="noopener noreferrer"&gt;networking API guidance&lt;/a&gt; and &lt;a href="https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy" rel="noopener noreferrer"&gt;local network privacy requirements&lt;/a&gt; against your deployment targets. This post describes an existing implementation rather than prescribing a transport for every app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send a manifest before sending files
&lt;/h2&gt;

&lt;p&gt;Small JSON messages, sent reliably, handle transfer requests, acceptance, the manifest, skipped items, cancellation and completion. Document packages travel separately through &lt;code&gt;sendResource(at:withName:toPeer:withCompletionHandler:)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The manifest describes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Folders in parent-before-child order.&lt;/li&gt;
&lt;li&gt;Documents, with temporary IDs, destination folder references, filenames, text hashes, sizes and sort positions.&lt;/li&gt;
&lt;li&gt;Collections and their references to those documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before resolving destinations, the receiver checks item counts, identifier uniqueness, supported document extensions and folder relationships. It also checks that filenames are valid.&lt;/p&gt;

&lt;p&gt;Temporary IDs identify items within a transfer. They are not filesystem paths supplied by the sender.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep related document data in one package
&lt;/h2&gt;

&lt;p&gt;Each document is encoded as a binary property-list package containing its text, creation and modification dates, optional note, local images and version-history data.&lt;/p&gt;

&lt;p&gt;This gives the receiver one resource to associate with a document. It does not make importing the entire library an atomic operation: documents are installed as their resources arrive, and the batch has no whole-library rollback.&lt;/p&gt;

&lt;p&gt;Cancellation and import failures therefore need explicit handling. A stopped batch cannot automatically be interpreted as having changed nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define what counts as a duplicate
&lt;/h2&gt;

&lt;p&gt;The receiver first resolves the intended destination path. If a document already exists at that path and its text hash matches the incoming descriptor, the document resource is skipped.&lt;/p&gt;

&lt;p&gt;That is a path-scoped comparison, not a search for matching text anywhere in the library.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Current behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same destination path and same text hash&lt;/td&gt;
&lt;td&gt;Skip the document resource&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same destination path but different text&lt;/td&gt;
&lt;td&gt;Import under an available filename&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matching text elsewhere in the library&lt;/td&gt;
&lt;td&gt;No global deduplication based on that match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same text but changed attachments or history&lt;/td&gt;
&lt;td&gt;The text-based skip rule can still apply&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is an important limit. Equal text hashes do not establish that two complete document packages are identical. This policy is useful for an explicit transfer workflow, but it should not be mistaken for a general synchronization algorithm.&lt;/p&gt;

&lt;p&gt;Collections have a separate rule. If the receiver already has a Collection with the same UUID, its existing Collection record is preserved. Repeating a migration does not overwrite that record to match the sender.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distinguish delivery from import completion
&lt;/h2&gt;

&lt;p&gt;The sender has a completion message for finishing resource transmission. The receiver has a separate acknowledgement after the expected, non-skipped and non-cancelled resources have finished importing.&lt;/p&gt;

&lt;p&gt;Keeping these states separate matters: sending a file successfully and installing it in the destination library are different operations. Skipped and cancelled items also affect what a completed batch means.&lt;/p&gt;

&lt;p&gt;For this kind of migration, the useful design questions are concrete: which items were requested, which were skipped, which were imported, and what happens to existing data on another attempt?&lt;/p&gt;

&lt;p&gt;Those rules deserve as much attention as the connection API.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Scope: this is a source-level walkthrough of the current migration design, not an end-to-end migration test report.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>localfirst</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
