<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/styles/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Su Ho | Engineering Lead</title><description>My personal blog and portfolio website for sharing technical insights, leadership experiences, and software engineering best practices.</description><link>https://suho.dev/</link><item><title>How I stay up to date as a software engineer</title><link>https://suho.dev/posts/how-to-keep-your-self-up-to-date/</link><guid isPermaLink="true">https://suho.dev/posts/how-to-keep-your-self-up-to-date/</guid><description>Build a calm learning routine with a few trusted sources, useful communities, and time to practice.</description><pubDate>Mon, 10 Feb 2025 13:12:12 GMT</pubDate><content:encoded>&lt;p&gt;“Am I falling behind the team?”&lt;/p&gt;
&lt;p&gt;Many developers quietly ask themselves this question. It can feel worse when teammates seem to know every new tool and practice.&lt;/p&gt;
&lt;p&gt;One of my direct reports once asked, “How often do you check technology news, such as library updates?”&lt;/p&gt;
&lt;p&gt;That question stayed with me because I had faced the same problem. Staying current can compete with the time we need to build deeper expertise.&lt;/p&gt;
&lt;p&gt;Over time, I built a small system that helped me grow from a junior developer into an engineering leader. It turns a noisy stream of updates into a manageable learning routine.&lt;/p&gt;
&lt;h2&gt;Start with a few reliable sources&lt;a href=&quot;#start-with-a-few-reliable-sources&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Start with sources that match your primary technology stack. Your stack might include &lt;a href=&quot;https://www.apple.com/ios&quot;&gt;iOS&lt;/a&gt;, &lt;a href=&quot;https://www.android.com/&quot;&gt;Android&lt;/a&gt;, &lt;a href=&quot;https://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt;, or another web framework.&lt;/p&gt;
&lt;p&gt;Follow developers, technical blogs, and maintainers whose work you trust. Medium, DEV, X, Substack, GitHub, and LinkedIn can help you find them.&lt;/p&gt;
&lt;p&gt;You can also follow engineering blogs from &lt;a href=&quot;https://netflixtechblog.com/&quot;&gt;Netflix&lt;/a&gt;, &lt;a href=&quot;https://blog.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt;, &lt;a href=&quot;https://developers.facebook.com/blog/&quot;&gt;Meta&lt;/a&gt;, and &lt;a href=&quot;https://developers.googleblog.com/en/&quot;&gt;Google&lt;/a&gt;. Communities such as &lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;Hacker News&lt;/a&gt; and &lt;a href=&quot;https://lobste.rs/&quot;&gt;Lobsters&lt;/a&gt; are also useful.&lt;/p&gt;
&lt;p&gt;For code changes, watch the repositories and release notes for your main frameworks. Keep the official documentation nearby when you need to verify a claim.&lt;/p&gt;
&lt;p&gt;Books and courses can help when you want a more structured path. YouTube and Udemy are two places to look.&lt;/p&gt;
&lt;aside&gt; &lt;p&gt; Why source quality matters &lt;/p&gt; &lt;div&gt; &lt;p&gt;Many posts now include content from large language models (LLMs), such as
ChatGPT and Claude. These models can produce &lt;a href=&quot;https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)&quot;&gt;incorrect
information&lt;/a&gt;.
Use reliable sources and verify claims that affect your work.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;aside&gt;  &lt;div&gt; &lt;p&gt;A Really Simple Syndication (RSS) reader can keep these sources in one place.
I use separate feeds for technical topics, leadership, and industry news.
&lt;a href=&quot;https://netnewswire.com/&quot;&gt;NetNewsWire&lt;/a&gt; is one option.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;h2&gt;Learn with other people&lt;a href=&quot;#learn-with-other-people&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Reading is only part of the system. Online and local communities give you a place to ask questions and compare experiences.&lt;/p&gt;
&lt;p&gt;Discord and Slack communities can make those conversations easier. Local meetups and conferences can help you build relationships outside your company.&lt;/p&gt;
&lt;p&gt;When you feel ready, try speaking or mentoring. Explaining an idea to someone else often shows you which parts you still need to learn.&lt;/p&gt;
&lt;figure&gt; &lt;figcaption&gt; &lt;p&gt;Connect online discussion with local relationships&lt;/p&gt; &lt;p&gt; Online spaces keep questions moving. Local events turn useful exchanges into stronger professional relationships. &lt;/p&gt; &lt;/figcaption&gt; &lt;div&gt; &lt;div&gt;          &lt;p&gt;
Ask and share
&lt;/p&gt; &lt;p&gt;
Bring insights back
&lt;/p&gt; &lt;section&gt; &lt;h4&gt;Online communities&lt;/h4&gt; &lt;div&gt;  &lt;span&gt;&lt;/span&gt; &lt;span&gt; &lt;strong&gt;Discussion forums&lt;/strong&gt; &lt;small&gt;Stack Overflow, Reddit&lt;/small&gt; &lt;/span&gt;  &lt;span&gt;&lt;/span&gt; &lt;span&gt; &lt;strong&gt;Chat platforms&lt;/strong&gt; &lt;small&gt;Discord, Slack&lt;/small&gt; &lt;/span&gt;  &lt;/div&gt; &lt;/section&gt;&lt;section&gt; &lt;h4&gt;Local events&lt;/h4&gt; &lt;div&gt;  &lt;span&gt;&lt;/span&gt; &lt;span&gt; &lt;strong&gt;Language meetups&lt;/strong&gt; &lt;small&gt;Practice with nearby peers&lt;/small&gt; &lt;/span&gt;  &lt;span&gt;&lt;/span&gt; &lt;span&gt; &lt;strong&gt;Conferences&lt;/strong&gt; &lt;small&gt;Compare broader experience&lt;/small&gt; &lt;/span&gt;  &lt;/div&gt; &lt;/section&gt; &lt;/div&gt; &lt;/div&gt; &lt;div&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt; &lt;p&gt;Discussion forums&lt;/p&gt; &lt;p&gt;Forums preserve searchable questions and detailed answers. They work well when you need context that others can reuse.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Try:&lt;/strong&gt; Write one precise question and include what you already tried.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/figure&gt; 
&lt;p&gt;The goal is not to join every community. Choose one or two places where the discussions help you learn.&lt;/p&gt;
&lt;h2&gt;Build a routine you can keep&lt;a href=&quot;#build-a-routine-you-can-keep&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A small routine is easier to keep than an ambitious study plan. Consistency matters more than intensity.&lt;/p&gt;
&lt;p&gt;Try spending 20 to 30 minutes each day on one activity. You can read an article, review an open-source repository, or watch a technical talk.&lt;/p&gt;
&lt;p&gt;Add one weekly goal when that routine feels comfortable. Study a concept, test a tool, or write about what you learned.&lt;/p&gt;
&lt;figure&gt; &lt;figcaption&gt; &lt;p&gt;Turn daily inputs into weekly practice&lt;/p&gt; &lt;p&gt; Small daily inputs create exposure. One focused weekly session converts a useful signal into understanding or practice. &lt;/p&gt; &lt;/figcaption&gt; &lt;div&gt; &lt;div&gt;           &lt;p&gt;Steady exposure&lt;br /&gt;builds depth&lt;/p&gt; &lt;section&gt; &lt;header&gt;  &lt;h4&gt;Daily&lt;/h4&gt; &lt;span&gt;20-30 min&lt;/span&gt;  &lt;/header&gt; &lt;div&gt;  &lt;span&gt;&lt;/span&gt; &lt;strong&gt;Read an article&lt;/strong&gt;  &lt;span&gt;&lt;/span&gt; &lt;strong&gt;Review open source&lt;/strong&gt;  &lt;span&gt;&lt;/span&gt; &lt;strong&gt;Watch a talk&lt;/strong&gt;  &lt;/div&gt; &lt;/section&gt;&lt;section&gt; &lt;header&gt;  &lt;h4&gt;Weekly&lt;/h4&gt; &lt;span&gt;One focused session&lt;/span&gt;  &lt;/header&gt; &lt;div&gt;  &lt;span&gt;&lt;/span&gt; &lt;strong&gt;Deep-dive a concept&lt;/strong&gt;  &lt;span&gt;&lt;/span&gt; &lt;strong&gt;Experiment with a tool&lt;/strong&gt;  &lt;span&gt;&lt;/span&gt; &lt;strong&gt;Write what you learned&lt;/strong&gt;  &lt;/div&gt; &lt;/section&gt; &lt;/div&gt; &lt;ol&gt; &lt;li&gt;Mon&lt;/li&gt;&lt;li&gt;Tue&lt;/li&gt;&lt;li&gt;Wed&lt;/li&gt;&lt;li&gt;Thu&lt;/li&gt;&lt;li&gt;Fri&lt;/li&gt;&lt;li&gt;Sat&lt;/li&gt;&lt;li&gt;Sun&lt;/li&gt; &lt;/ol&gt; &lt;/div&gt; &lt;div&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt; &lt;p&gt;Read an article&lt;/p&gt; &lt;p&gt;A short article keeps you exposed to new practices without taking over the day.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Try:&lt;/strong&gt; Save one useful point for your weekly session.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/figure&gt; 
&lt;p&gt;Start small, even if you only have 15 minutes. Keep a learning journal and review it each month. Adjust the routine when it stops working for you.&lt;/p&gt;
&lt;figure&gt; &lt;figcaption&gt; &lt;p&gt;Improve the routine with evidence&lt;/p&gt; &lt;p&gt; Start with a small commitment, record what happens, then use the evidence to shape the next version of your routine. &lt;/p&gt; &lt;/figcaption&gt; &lt;div&gt; &lt;div&gt;           &lt;p&gt;
A simple loop&lt;br /&gt;creates steady improvement
&lt;/p&gt;   &lt;span&gt;1&lt;/span&gt; &lt;strong&gt;Start small&lt;/strong&gt; &lt;small&gt;15 minutes&lt;/small&gt;    &lt;span&gt;2&lt;/span&gt; &lt;strong&gt;Track progress&lt;/strong&gt; &lt;small&gt;Learning journal&lt;/small&gt;    &lt;span&gt;3&lt;/span&gt; &lt;strong&gt;Adjust and iterate&lt;/strong&gt; &lt;small&gt;Review monthly&lt;/small&gt;   &lt;/div&gt; &lt;/div&gt; &lt;div&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt; &lt;p&gt;Start small&lt;/p&gt; &lt;p&gt;A small starting point lowers the cost of beginning and makes consistency more realistic.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Try:&lt;/strong&gt; Set a timer and begin one focused activity.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/figure&gt; 
&lt;h2&gt;What my system looks like&lt;a href=&quot;#what-my-system-looks-like&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started my career as a software developer who worked mainly on mobile applications. I later became a Senior Software Developer and then an Engineering Lead.&lt;/p&gt;
&lt;p&gt;As my role changed, my reading changed with it. I now follow iOS, mobile development, web development, leadership, and artificial intelligence (AI).&lt;/p&gt;
&lt;p&gt;These are some of the resources in my current system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/news/&quot;&gt;Apple Developer News&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.avanderlee.com/&quot;&gt;SwiftLee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://swiftrocks.com/&quot;&gt;SwiftRocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jetbrains.com/kotlin-multiplatform/&quot;&gt;Kotlin Multiplatform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://substack.com/@addyosmani&quot;&gt;Addy Osmani&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://newsletter.pragmaticengineer.com/&quot;&gt;The Pragmatic Engineer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.theengineeringmanager.com&quot;&gt;The Engineering Manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huyenchip.com/&quot;&gt;HuyenChip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.anthropic.com/news/&quot;&gt;Anthropic News&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt; &lt;p&gt; The GitHub dashboard feed &lt;/p&gt; &lt;div&gt; &lt;p&gt;One of my favorite ways to follow technical work is the &lt;a href=&quot;https://github.com/dashboard-feed&quot;&gt;GitHub dashboard
feed&lt;/a&gt;. I &lt;a href=&quot;https://github.com/suho?tab=following&quot;&gt;follow
people&lt;/a&gt; whose projects interest me,
then use the feed to find repositories worth studying.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;p&gt;I also participate in these communities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.webuild.community/&quot;&gt;We Build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ios-developers.io/&quot;&gt;iOS Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://slack-chats.kotlinlang.org/&quot;&gt;Kotlin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rubyonrails.org/community&quot;&gt;Ruby on Rails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Start with a small system&lt;a href=&quot;#start-with-a-small-system&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You do not need to copy my entire setup. Start with four small steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Choose three resources that you want to follow.&lt;/li&gt;
&lt;li&gt;Join one online community.&lt;/li&gt;
&lt;li&gt;Set aside 20 minutes daily for learning.&lt;/li&gt;
&lt;li&gt;Create a simple learning journal.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Staying current does not mean knowing everything. A useful system helps you notice what matters, learn at a steady pace, and protect time for deeper work.&lt;/p&gt;
&lt;p&gt;If you build your own version, I would enjoy hearing what works for you. Share it with me on &lt;a href=&quot;https://x.com/vansu_ho&quot;&gt;X&lt;/a&gt; or &lt;a href=&quot;https://www.linkedin.com/in/vansuho/&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>What is your leadership style?</title><link>https://suho.dev/posts/what-is-your-leadership-style/</link><guid isPermaLink="true">https://suho.dev/posts/what-is-your-leadership-style/</guid><description>Compare six leadership styles and reflect on how you make decisions, communicate, and support your team.</description><pubDate>Tue, 07 Jan 2025 09:28:45 GMT</pubDate><content:encoded>&lt;p&gt;Leading can be hard. Sometimes, it just sucks.&lt;/p&gt;
&lt;p&gt;I learned that quickly when someone asked me to become an &lt;a href=&quot;https://nimblehq.co/compass/team/roles/engineering-lead/&quot;&gt;Engineering Lead&lt;/a&gt;. I had little experience in people management, and I did not know how to describe my leadership style.&lt;/p&gt;
&lt;p&gt;Looking back, I had already used different approaches to handle team challenges. I just did not have clear names for them.&lt;/p&gt;
&lt;p&gt;This post introduces six leadership styles through software engineering examples. The reflection questions can help you recognize the styles that you already use.&lt;/p&gt;
&lt;aside&gt; &lt;p&gt; A quick map &lt;/p&gt; &lt;div&gt; &lt;ul&gt;
&lt;li&gt;Your leadership style appears in your decisions, communication, and team support.&lt;/li&gt;
&lt;li&gt;Common styles include transactional, transformational, laissez-faire, servant, autocratic, and democratic leadership.&lt;/li&gt;
&lt;li&gt;You can use more than one style.&lt;/li&gt;
&lt;li&gt;The situation and your team’s needs can change which style works best.&lt;/li&gt;
&lt;/ul&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;h2&gt;What is a leadership style?&lt;a href=&quot;#what-is-a-leadership-style&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A leadership style is the approach that you use to guide, influence, and support a team. It appears in how you make decisions, communicate, and manage the work.&lt;/p&gt;
&lt;figure&gt; &lt;figcaption&gt; &lt;p&gt;See your style in three daily behaviors&lt;/p&gt; &lt;p&gt; Leadership becomes easier to recognize when you inspect how you communicate, make decisions, and support the team. &lt;/p&gt; &lt;/figcaption&gt; &lt;div&gt; &lt;div&gt;       &lt;div&gt; &lt;strong&gt;Leadership style&lt;/strong&gt;&lt;span&gt;Your repeated choices&lt;/span&gt; &lt;/div&gt; &lt;div&gt;  &lt;span&gt;1&lt;/span&gt; &lt;span&gt;  &lt;strong&gt;Communication&lt;/strong&gt; &lt;small&gt;Share context and feedback&lt;/small&gt;  &lt;/span&gt;  &lt;span&gt;2&lt;/span&gt; &lt;span&gt;  &lt;strong&gt;Decision-making&lt;/strong&gt; &lt;small&gt;Involve, decide, and delegate&lt;/small&gt;  &lt;/span&gt;  &lt;span&gt;3&lt;/span&gt; &lt;span&gt;  &lt;strong&gt;Team support&lt;/strong&gt; &lt;small&gt;Build autonomy and growth&lt;/small&gt;  &lt;/span&gt;  &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;div&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt; &lt;p&gt;Communication&lt;/p&gt; &lt;p&gt;Communication shows how you explain the why, align on information, invite feedback, and handle conflict.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Try:&lt;/strong&gt; Ask whether the team has enough context to act without guessing.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/figure&gt; 
&lt;h3&gt;Reflect on how you lead&lt;a href=&quot;#reflect-on-how-you-lead&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You do not need a formal test to begin. Start with a few honest questions about how you work with your team.&lt;/p&gt;
&lt;h4&gt;Decision-making&lt;a href=&quot;#decision-making&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;How do you make choices?&lt;/li&gt;
&lt;li&gt;Who do you involve in a decision?&lt;/li&gt;
&lt;li&gt;Which decisions do you delegate?&lt;/li&gt;
&lt;li&gt;How much autonomy do team members have?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Communication&lt;a href=&quot;#communication&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Which communication channels do you use, and how often?&lt;/li&gt;
&lt;li&gt;How do you give and receive feedback?&lt;/li&gt;
&lt;li&gt;How do you share information with the team?&lt;/li&gt;
&lt;li&gt;How do you handle conflict?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Team support&lt;a href=&quot;#team-support&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;How do you motivate direct reports and team members?&lt;/li&gt;
&lt;li&gt;How do you approach performance reviews?&lt;/li&gt;
&lt;li&gt;How do you support each person’s development?&lt;/li&gt;
&lt;li&gt;How do you set goals with the team?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your answers will probably change with the situation. That does not make them inconsistent. Many leaders combine several styles and adjust them over time.&lt;/p&gt;
&lt;h2&gt;Leadership styles&lt;a href=&quot;#leadership-styles&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each style below has useful qualities and real limitations. As you read, think about the situations where you recognize your own behavior.&lt;/p&gt;
&lt;h3&gt;Transactional leadership&lt;a href=&quot;#transactional-leadership&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Transactional leadership focuses on clear exchanges between leaders and team members. Leaders define structured tasks, expected results, rewards, and consequences.&lt;/p&gt;
&lt;p&gt;These leaders set clear expectations and goals for their team. They monitor performance and provide feedback based on outcomes.&lt;/p&gt;
&lt;p&gt;This clarity can help with routine tasks, compliance work, and measurable goals. A rigid version of the style can limit creativity. Morale can also fall when output becomes the only measure of success.&lt;/p&gt;
&lt;figure&gt; &lt;figcaption&gt; &lt;p&gt;Create a clear performance loop&lt;/p&gt; &lt;p&gt; Transactional leadership defines the exchange, supports execution, reviews the result, and returns to a clearer expectation. &lt;/p&gt; &lt;/figcaption&gt; &lt;div&gt; &lt;div&gt;            &lt;div&gt; &lt;strong&gt;Transactional leadership&lt;/strong&gt;&lt;span&gt;Explicit exchange&lt;/span&gt; &lt;/div&gt;  &lt;span&gt;1&lt;/span&gt; &lt;span&gt;  &lt;strong&gt;Set expectations&lt;/strong&gt; &lt;small&gt;Task, target, owner&lt;/small&gt;  &lt;/span&gt;  &lt;span&gt;2&lt;/span&gt; &lt;span&gt;  &lt;strong&gt;Connect outcomes&lt;/strong&gt; &lt;small&gt;Rewards and consequences&lt;/small&gt;  &lt;/span&gt;  &lt;span&gt;3&lt;/span&gt; &lt;span&gt;  &lt;strong&gt;Focus execution&lt;/strong&gt; &lt;small&gt;Deliver known work&lt;/small&gt;  &lt;/span&gt;  &lt;span&gt;4&lt;/span&gt; &lt;span&gt;  &lt;strong&gt;Review performance&lt;/strong&gt; &lt;small&gt;Monitor and give feedback&lt;/small&gt;  &lt;/span&gt;  &lt;/div&gt; &lt;/div&gt; &lt;div&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt; &lt;p&gt;Set expectations&lt;/p&gt; &lt;p&gt;Clear expectations define what success looks like, which standards apply, and who is accountable.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Try:&lt;/strong&gt; Confirm the expected result before discussing the method.&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; &lt;/figure&gt; 
&lt;aside&gt; &lt;p&gt; Example &lt;/p&gt; &lt;div&gt; &lt;p&gt;A product manager (PM) sets clear sprint goals and expectations for the team. The PM explains which features the team must deliver. At the end of the sprint, the PM reviews completed features and adjusts the next goals. Clear goals make team velocity easier to understand.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;h3&gt;Transformational leadership&lt;a href=&quot;#transformational-leadership&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Transformational leadership uses a shared vision to inspire change. These leaders connect daily work with a larger purpose and encourage people to grow.&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;The four I’s of Transformational Leadership&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1. &lt;strong&gt;Idealized Influence&lt;/strong&gt;: Leaders act as &lt;strong&gt;role models&lt;/strong&gt; for their team, inspiring trust and respect.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2. &lt;strong&gt;Inspirational Motivation&lt;/strong&gt;: Leaders communicate &lt;strong&gt;a vision&lt;/strong&gt; for the future, motivating team members to achieve their best.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3. &lt;strong&gt;Intellectual Stimulation&lt;/strong&gt;: Leaders encourage &lt;strong&gt;creativity&lt;/strong&gt; and &lt;strong&gt;innovation&lt;/strong&gt;. They provide learning opportunities and welcome different perspectives and ideas.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4. &lt;strong&gt;Individualized Consideration&lt;/strong&gt;: Leaders focus on the &lt;strong&gt;unique&lt;/strong&gt; potential of each team member, providing personalized support and development opportunities.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;This style can support engagement, creativity, and meaningful change. It can be less effective during a crisis that requires a quick decision. An ambitious vision can also cause burnout when resources do not match it.&lt;/p&gt;
&lt;aside&gt; &lt;p&gt; Example &lt;/p&gt; &lt;div&gt; &lt;p&gt;A tech lead (TL) creates an innovative environment and encourages experiments with new technologies. Examples include &lt;a href=&quot;https://en.wikipedia.org/wiki/Generative_artificial_intelligence&quot;&gt;generative AI&lt;/a&gt; and &lt;a href=&quot;https://www.anthropic.com/research/building-effective-agents&quot;&gt;AI agents&lt;/a&gt;. The TL involves team members in decisions to support commitment to project goals. The TL also provides opportunities for skill development and career growth.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;h3&gt;Laissez-faire leadership&lt;a href=&quot;#laissez-faire-leadership&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Laissez-faire leadership gives team members significant autonomy and little direct supervision. Leaders provide resources, then let the team manage its work and decisions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Laissez-faire (&lt;em&gt;/ˌlesāˈfer/&lt;/em&gt;) is French for “let them do what they want.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This style empowers experienced team members and encourages innovation. It can also increase job satisfaction for self-motivated employees. However, less experienced team members might lack direction. Teams can also become misaligned with organizational goals without effective monitoring.&lt;/p&gt;
&lt;aside&gt; &lt;p&gt; Example &lt;/p&gt; &lt;div&gt; &lt;p&gt;A tech lead (TL) allows team members to choose tasks and work independently. The TL provides resources and support but trusts team members to make decisions. The TL encourages innovation and gives the team freedom to experiment with new ideas.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;aside&gt; &lt;p&gt; The Full Range of Leadership Model (FRLM) &lt;/p&gt; &lt;div&gt; &lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Full_range_leadership_model&quot;&gt;Full Range of Leadership Model
(FRLM)&lt;/a&gt; describes a
continuum from &lt;em&gt;laissez-faire&lt;/em&gt; through &lt;em&gt;transactional&lt;/em&gt; to &lt;em&gt;transformational&lt;/em&gt;
leadership. The model shows how a leader can use different styles as a
situation changes.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;h3&gt;Servant leadership&lt;a href=&quot;#servant-leadership&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Servant leadership puts the team’s needs before the leader’s authority or personal interests. These leaders focus on support, trust, well-being, and development.&lt;/p&gt;
&lt;figure&gt; &lt;img src=&quot;https://suho.dev/images/content/posts/leadership/servant-leadership.webp&quot; alt=&quot;A leader supports a platform that helps three team members move forward&quot; width=&quot;1600&quot; height=&quot;900&quot; loading=&quot;lazy&quot; /&gt; &lt;/figure&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Traditional Leader&lt;/th&gt;&lt;th&gt;Servant Leader&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Views leadership as a position of power and authority.&lt;/td&gt;&lt;td&gt;Views leadership as a chance to serve others.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Uses power and control to direct and manage team members.&lt;/td&gt;&lt;td&gt;Shares power and control to drive engagement.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Measures success through outcomes and results.&lt;/td&gt;&lt;td&gt;Measures success through growth and development.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Speaks&lt;/td&gt;&lt;td&gt;Listens&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;This style builds relationships and trust within the team. It can improve collaboration, cohesion, and performance. However, leaders might struggle to make decisions in high-pressure situations. Without clear direction, others might think that these leaders lack authority.&lt;/p&gt;
&lt;aside&gt; &lt;p&gt; Example &lt;/p&gt; &lt;div&gt; &lt;p&gt;An engineering manager (EM) focuses on team member well-being and development. The EM provides support and resources that help team members succeed. The EM creates a collaborative environment through open communication and feedback.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;h3&gt;Autocratic leadership&lt;a href=&quot;#autocratic-leadership&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Autocratic leadership gives one leader most of the decision-making authority. The leader assigns tasks and processes with little input from the team.&lt;/p&gt;
&lt;figure&gt; &lt;img src=&quot;https://suho.dev/images/content/posts/leadership/autocratic-leadership.webp&quot; alt=&quot;One elevated leader sends a one-way directive to three team members&quot; width=&quot;1600&quot; height=&quot;900&quot; loading=&quot;lazy&quot; /&gt; &lt;/figure&gt;
&lt;p&gt;This style supports quick decisions during crises or situations that require clarity. It also creates consistent task execution. However, it can cause low morale and high turnover when team members feel undervalued. It also limits creativity by excluding the team’s insights and expertise.&lt;/p&gt;
&lt;aside&gt; &lt;p&gt; Example &lt;/p&gt; &lt;div&gt; &lt;p&gt;A tech lead (TL) sets tasks and deadlines without consulting the team. The TL defines the technical approach and required processes. The TL controls project timelines and deliverables.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;h3&gt;Democratic leadership&lt;a href=&quot;#democratic-leadership&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Democratic leadership invites team members to participate in decisions. The leader gathers different views, helps the group build consensus, and guides the overall direction.&lt;/p&gt;
&lt;figure&gt; &lt;img src=&quot;https://suho.dev/images/content/posts/leadership/democratic-leadership.webp&quot; alt=&quot;Four equal team members contribute to one shared decision&quot; width=&quot;1600&quot; height=&quot;900&quot; loading=&quot;lazy&quot; /&gt; &lt;/figure&gt;
&lt;p&gt;This style increases team member support and commitment to decisions. Diverse input can create a culture of collaboration and innovation. However, consensus can make decisions slow. Conflicting opinions can also cause indecision without a clear resolution process.&lt;/p&gt;
&lt;aside&gt; &lt;p&gt; Example &lt;/p&gt; &lt;div&gt; &lt;p&gt;An engineering manager (EM) encourages team members to contribute ideas and feedback on project decisions. The EM gathers diverse input before making decisions. The EM builds consensus to support alignment and commitment to project goals.&lt;/p&gt; &lt;/div&gt; &lt;/aside&gt;
&lt;h2&gt;So, what is your leadership style?&lt;a href=&quot;#so-what-is-your-leadership-style&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After exploring these styles, return to &lt;a href=&quot;#reflect-on-how-you-lead&quot;&gt;the reflection questions&lt;/a&gt;. Your answers can show which approaches you use most often.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You do not need one permanent leadership style. A thoughtful leader adapts while staying clear and consistent with the team.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I am still learning which approach fits each situation. If you want to compare notes, mention me on &lt;a href=&quot;https://x.com/vansu_ho&quot;&gt;X&lt;/a&gt; or &lt;a href=&quot;https://www.linkedin.com/in/vansuho/&quot;&gt;LinkedIn&lt;/a&gt;. I would enjoy hearing what leadership looks like for you.&lt;/p&gt;</content:encoded></item><item><title>iOS security best practices for storing sensitive data</title><link>https://suho.dev/posts/security-best-practices-in-ios/</link><guid isPermaLink="true">https://suho.dev/posts/security-best-practices-in-ios/</guid><description>Protect sensitive iOS app data with safer local storage, controlled logging, and careful third-party SDK integration.</description><pubDate>Sun, 10 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sensitive data can leak from places that seem harmless during development. Local storage, debug logs, and third-party software development kits (SDKs) all deserve a careful review.&lt;/p&gt;
&lt;p&gt;This post covers the checks that I use as a practical starting point.&lt;/p&gt;
&lt;h2&gt;Local data storage&lt;a href=&quot;#local-data-storage&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;iOS applications often handle passwords, secret keys, and personally identifiable information (PII). Match the storage method to the sensitivity of each value.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do not save authentication tokens or credentials in &lt;a href=&quot;https://developer.apple.com/documentation/foundation/userdefaults&quot;&gt;UserDefaults&lt;/a&gt; without encryption.&lt;/li&gt;
&lt;li&gt;Do not store &lt;strong&gt;application programming interface (API) keys&lt;/strong&gt; or &lt;strong&gt;encryption keys&lt;/strong&gt; in &lt;code&gt;.plist&lt;/code&gt; files or hard-coded strings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Store sensitive values in &lt;a href=&quot;https://developer.apple.com/documentation/security/keychain-services&quot;&gt;Keychain&lt;/a&gt;. When cryptographic keys need hardware protection, consider the &lt;a href=&quot;https://developer.apple.com/documentation/security/protecting-keys-with-the-secure-enclave&quot;&gt;Secure Enclave&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For more advanced cases, consider &lt;a href=&quot;https://cloud.ibm.com/docs/key-protect?topic=key-protect-envelope-encryption&quot;&gt;envelope encryption&lt;/a&gt; and store the root key in Keychain.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Use &lt;code&gt;kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly&lt;/code&gt; with &lt;code&gt;SecAccessControlCreateWithFlags&lt;/code&gt; to restrict Keychain access to an unlocked, passcode-protected device.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Logging&lt;a href=&quot;#logging&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Logs are useful during development, but they can expose sensitive data. Review calls to &lt;code&gt;NSLog&lt;/code&gt;, &lt;code&gt;assert&lt;/code&gt;, &lt;code&gt;print&lt;/code&gt;, and any custom logging tools.&lt;/p&gt;
&lt;p&gt;Remove log statements that include credentials, tokens, personal data, or request contents that can contain those values.&lt;/p&gt;
&lt;p&gt;If logs are required, enable them only in &lt;code&gt;DEBUG&lt;/code&gt; or development builds.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;AppDelegate.swift&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; DEBUG&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;NSLog&lt;/span&gt;&lt;span&gt;(...)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2&gt;Third-party SDKs&lt;a href=&quot;#third-party-sdks&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have integrated &lt;a href=&quot;https://firebase.google.com/&quot;&gt;Firebase&lt;/a&gt;, &lt;a href=&quot;https://www.braze.com/&quot;&gt;Braze&lt;/a&gt;, and &lt;a href=&quot;https://www.appsflyer.com&quot;&gt;AppsFlyer&lt;/a&gt; into mobile applications. These services can process user behavior and advertising data.&lt;/p&gt;
&lt;p&gt;Before you add a third-party SDK, check which data your app sends to it. I use these review steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Review their code, requested permissions, and known vulnerabilities.&lt;/li&gt;
&lt;li&gt;Anonymize data before sending it to third-party services to prevent PII exposure.&lt;/li&gt;
&lt;li&gt;Encrypt data, such as email addresses, before sending it when required.&lt;/li&gt;
&lt;li&gt;Inspect all API requests for sensitive information. Use &lt;a href=&quot;https://proxyman.io/&quot;&gt;Proxyman&lt;/a&gt; to intercept and inspect traffic between the client and service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These checks will not replace a full security review. They will help you catch common data exposure problems before a release.&lt;/p&gt;</content:encoded></item><item><title>How I troubleshoot iOS Universal Links</title><link>https://suho.dev/posts/how-to-troubleshoot-universal-links/</link><guid isPermaLink="true">https://suho.dev/posts/how-to-troubleshoot-universal-links/</guid><description>Follow a practical set of checks when an existing iOS Universal Links integration stops opening your app.</description><pubDate>Wed, 23 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Universal Links can be frustrating when a working integration suddenly stops opening the app. My team ran into that exact problem.&lt;/p&gt;
&lt;p&gt;The project configuration had worked before, but the links started opening in Safari. We needed a way to narrow down the cause.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This guide focuses on an existing integration. If you are setting up the feature, start with &lt;a href=&quot;https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/#Support-universal-links&quot;&gt;Apple’s Universal Links documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Confirm the basic association&lt;a href=&quot;#confirm-the-basic-association&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before troubleshooting, confirm that your project has a two-way association between the app and website. This association specifies the URLs that your app handles.&lt;/p&gt;
&lt;p&gt;Host the &lt;code&gt;apple-app-site-association&lt;/code&gt; file at &lt;code&gt;/.well-known/apple-app-site-association&lt;/code&gt; on your website. Add the &lt;code&gt;Associated Domains&lt;/code&gt; capability with an &lt;code&gt;applinks&lt;/code&gt; configuration to your Xcode project.&lt;/p&gt;
&lt;h2&gt;Check where the link opens&lt;a href=&quot;#check-where-the-link-opens&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If the link opens your app’s home screen, the basic association is working. The remaining problem is probably in the app’s URL routing.&lt;/p&gt;
&lt;p&gt;Handle the URL in the app, then route it to the correct screen or flow for that endpoint.&lt;/p&gt;
&lt;p&gt;If the link opens Safari, inspect the Apple App Site Association (AASA) file. This check is useful when the same configuration worked before.&lt;/p&gt;
&lt;h2&gt;Test the link from Notes&lt;a href=&quot;#test-the-link-from-notes&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I use the Notes app for a quick check. Add each URL that you want to test. Then touch and hold a link to view its options.&lt;/p&gt;
&lt;p&gt;If Universal Links are configured correctly, you should see two options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Open in {Your App Name}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Open in Safari&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Your choice sets the default behavior for future Universal Links from that domain. Repeat the test to select the other option.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you do not see &lt;code&gt;Open in {Your App Name}&lt;/code&gt;, check whether the AASA file is publicly accessible.&lt;/p&gt;
&lt;h2&gt;Inspect the public AASA file&lt;a href=&quot;#inspect-the-public-aasa-file&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Run this command in Terminal:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://{your-domain}/.well-known/apple-app-site-association&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;code&gt;301&lt;/code&gt; or &lt;code&gt;302&lt;/code&gt; HTTP status code indicates a redirect. The hosted AASA file must be directly accessible.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A redirect is allowed when another app triggers the Universal Link. Safari opens first, then your app opens.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;code&gt;403&lt;/code&gt; or &lt;code&gt;404&lt;/code&gt; HTTP status code indicates an access problem. The file might not be public, or another rule might block it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I encountered this issue when Cloudflare blocked all bot traffic to the website. Cloudflare classified AASA requests as bot traffic. Ensure that your configuration permits direct AASA access from all geographic locations and Internet Protocol (IP) addresses.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Check Apple’s cached AASA file&lt;a href=&quot;#check-apples-cached-aasa-file&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Finally, verify which AASA file Apple’s content delivery network (CDN) has cached for your domain.&lt;/p&gt;
&lt;p&gt;Open this URL in your browser: &lt;code&gt;https://app-site-association.cdn-apple.com/a/v1/{your-domain}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Alternatively, run this command in Terminal:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://app-site-association.cdn-apple.com/a/v1/{your-domain}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Compare this response with the file on your website. The Apple CDN response is the version available to your app.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you install the app, the device immediately downloads the AASA file from the CDN. The device checks for updates approximately once a week. Reinstall the app to download a newer file because the CDN has no direct invalidation option.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;These checks helped my team separate an app routing problem from a website or CDN problem. I hope they make your next investigation a little easier.&lt;/p&gt;
&lt;p&gt;If problems remain, see Apple’s &lt;a href=&quot;https://developer.apple.com/documentation/technotes/tn3155-debugging-universal-links#Revision-History&quot;&gt;TN3155: Debugging universal links&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>How the two-pointer technique works</title><link>https://suho.dev/posts/two-pointers-in-algorithms/</link><guid isPermaLink="true">https://suho.dev/posts/two-pointers-in-algorithms/</guid><description>Learn when to use two pointers and how they can reduce a nested search to one pass.</description><pubDate>Tue, 10 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The two-pointer technique tracks two positions in the same collection. Each pointer moves according to the result of the current comparison.&lt;/p&gt;
&lt;p&gt;This technique often replaces a nested search with one pass. It works well with sorted arrays, linked lists, pairs, triplets, and subarrays.&lt;/p&gt;
&lt;h2&gt;When to consider two pointers&lt;a href=&quot;#when-to-consider-two-pointers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Look for two pointers when the input is already sorted. You can also use them when sorting does not remove information that you need.&lt;/p&gt;
&lt;p&gt;For example, suppose you need to find two values in a sorted array whose sum matches a target. Start one pointer at each end:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;hasPairWithSum.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hasPairWithSum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;[], &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;): &lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; - &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt; &amp;lt; &lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;] + &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt; === &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt; &amp;lt; &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt; += &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt; -= &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Move the left pointer when the sum is too small. Move the right pointer when the sum is too large.&lt;/p&gt;
&lt;p&gt;Each pointer visits the array at most once. The algorithm uses &lt;code&gt;O(n)&lt;/code&gt; time and &lt;code&gt;O(1)&lt;/code&gt; extra space.&lt;/p&gt;
&lt;p&gt;If you must return original indexes, keep them before sorting. Sorting an unsorted array changes those indexes.&lt;/p&gt;
&lt;h2&gt;Problems to practice&lt;a href=&quot;#problems-to-practice&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These problems use different forms of the same technique:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.com/problems/remove-duplicates-from-sorted-array/&quot;&gt;Remove Duplicates from Sorted Array&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.com/problems/valid-palindrome/&quot;&gt;Valid Palindrome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.com/problems/reverse-string/&quot;&gt;Reverse String&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.com/problems/3sum/&quot;&gt;3Sum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.com/problems/3sum-closest/&quot;&gt;3Sum Closest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find more examples in the &lt;a href=&quot;https://leetcode.com/tag/two-pointers/&quot;&gt;two-pointer problem list on LeetCode&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Libraries I consider for a new iOS project</title><link>https://suho.dev/posts/ios-libraries/</link><guid isPermaLink="true">https://suho.dev/posts/ios-libraries/</guid><description>A practical starting list for image loading, networking, secure storage, crash reporting, and Swift style checks.</description><pubDate>Tue, 23 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I do not add the same dependencies to every iOS project. Each dependency adds maintenance work, so it should solve a clear problem.&lt;/p&gt;
&lt;p&gt;Still, a short list helps when I start a project. These are the libraries and tools that I consider first.&lt;/p&gt;
&lt;h2&gt;User interface&lt;a href=&quot;#user-interface&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I prefer SwiftUI for new interfaces, so this section does not include UIKit-specific libraries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;&lt;code&gt;Kingfisher&lt;/code&gt;&lt;a href=&quot;#kingfisher&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/onevcat/Kingfisher&quot;&gt;Kingfisher&lt;/a&gt; handles common image downloading, caching, and display tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load images asynchronously.&lt;/li&gt;
&lt;li&gt;Cache images.&lt;/li&gt;
&lt;li&gt;Display placeholders, loading indicators, and fade-in effects for downloaded images.&lt;/li&gt;
&lt;li&gt;Load animated images, such as Graphics Interchange Format (GIF) files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I consider Kingfisher when an app loads many remote images or needs more control than a small custom loader provides.&lt;/p&gt;
&lt;h2&gt;Networking&lt;a href=&quot;#networking&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;Alamofire&lt;/code&gt;&lt;a href=&quot;#alamofire&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Alamofire/Alamofire&quot;&gt;Alamofire&lt;/a&gt; is a Swift Hypertext Transfer Protocol (HTTP) networking library. Its interface builds on Apple’s Foundation networking stack.&lt;/p&gt;
&lt;p&gt;Alamofire can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make network requests.&lt;/li&gt;
&lt;li&gt;Handle, validate, and cache responses.&lt;/li&gt;
&lt;li&gt;Handle authentication.&lt;/li&gt;
&lt;li&gt;Download and upload data.&lt;/li&gt;
&lt;li&gt;Adapt and retry requests.&lt;/li&gt;
&lt;li&gt;Apply security controls, such as Secure Sockets Layer (SSL) pinning.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Foundation can handle many networking needs without another dependency. I consider Alamofire when its request adapters, retries, or response handling remove meaningful project code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Security&lt;a href=&quot;#security&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;KeychainAccess&lt;/code&gt;&lt;a href=&quot;#keychainaccess&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;iOS apps must protect passwords, authentication tokens, and other credentials. &lt;a href=&quot;https://github.com/kishikawakatsumi/KeychainAccess&quot;&gt;KeychainAccess&lt;/a&gt; wraps &lt;a href=&quot;https://developer.apple.com/documentation/security/keychain_services/&quot;&gt;&lt;code&gt;Keychain&lt;/code&gt;&lt;/a&gt; with a smaller Swift interface.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;KeychainAccess&lt;/code&gt; provides these features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Store and retrieve sensitive data in the keychain.&lt;/li&gt;
&lt;li&gt;Synchronize &lt;code&gt;Keychain&lt;/code&gt; items with &lt;code&gt;iCloud&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Integrate with &lt;code&gt;Touch ID&lt;/code&gt; and &lt;code&gt;Face ID&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://developer.apple.com/documentation/security/shared_web_credentials/&quot;&gt;Shared Web Credentials&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Tools&lt;a href=&quot;#tools&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;Firebase/Crashlytics&lt;/code&gt;&lt;a href=&quot;#firebasecrashlytics&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://firebase.google.com/docs/crashlytics&quot;&gt;Firebase Crashlytics&lt;/a&gt; is a crash-reporting tool from Google’s Firebase platform. It helps teams investigate crashes that affect users.&lt;/p&gt;
&lt;p&gt;Firebase Crashlytics provides these capabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Curated crash reports with detailed stack traces.&lt;/li&gt;
&lt;li&gt;Real-time alerts for new issues, regressed issues, and growing issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;SwiftLint&lt;/code&gt;&lt;a href=&quot;#swiftlint&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/realm/SwiftLint&quot;&gt;SwiftLint&lt;/a&gt; enforces Swift style and conventions in your codebase. Its configurable rules can match your team’s coding standards and preferences.&lt;/p&gt;
&lt;p&gt;SwiftLint provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code style enforcement.&lt;/li&gt;
&lt;li&gt;Customizable rules and configurations.&lt;/li&gt;
&lt;li&gt;Integration with &lt;code&gt;Xcode&lt;/code&gt; and continuous integration and continuous delivery (CI/CD) pipelines.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Integrate the dependencies with CocoaPods&lt;a href=&quot;#integrate-the-dependencies-with-cocoapods&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This example uses &lt;a href=&quot;https://cocoapods.org/&quot;&gt;CocoaPods&lt;/a&gt; as the dependency manager. Add the dependencies that your project needs to its &lt;code&gt;Podfile&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;Podfile&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# User Interface&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pod &lt;/span&gt;&lt;span&gt;&apos;Kingfisher&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Networking&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pod &lt;/span&gt;&lt;span&gt;&apos;Alamofire&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Security&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pod &lt;/span&gt;&lt;span&gt;&apos;KeychainAccess&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Tools&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pod &lt;/span&gt;&lt;span&gt;&apos;Firebase/Crashlytics&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pod &lt;/span&gt;&lt;span&gt;&apos;SwiftLint&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Treat this list as a starting point, not a project template. Before you add a dependency, check its maintenance status and confirm that the project needs it.&lt;/p&gt;</content:encoded></item><item><title>Why my iOS team switched to Clean Architecture</title><link>https://suho.dev/posts/why-select-ios-clean-architecture/</link><guid isPermaLink="true">https://suho.dev/posts/why-select-ios-clean-architecture/</guid><description>Why my team moved from VIPER to Clean Architecture with MVVM, what improved, and which tradeoffs remained.</description><pubDate>Wed, 27 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Switching to Clean Architecture helped our mobile teams collaborate. It also added boilerplate and complexity, so the decision was not free.&lt;/p&gt;
&lt;p&gt;This post explains why we changed our iOS architecture and what happened afterward. If the approach is new to you, read &lt;a href=&quot;https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html&quot;&gt;The Clean Architecture&lt;/a&gt; first.&lt;/p&gt;
&lt;h2&gt;The problem we needed to solve&lt;a href=&quot;#the-problem-we-needed-to-solve&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before switching to Clean Architecture, we used the &lt;a href=&quot;https://www.objc.io/issues/13-architecture/viper/&quot;&gt;View, Interactor, Presenter, Entity, and Router (VIPER) architecture&lt;/a&gt;. VIPER is well known in the iOS community, and many companies have adopted it.&lt;/p&gt;
&lt;p&gt;Our iOS developers understood VIPER, but developers outside the team did not always know its terms. That difference mattered when we started rotating people across iOS, Android, and Flutter projects.&lt;/p&gt;
&lt;p&gt;Without a shared architecture, developers needed more time to understand code from another platform. Even familiar ideas had different names.&lt;/p&gt;
&lt;h2&gt;The architecture we chose&lt;a href=&quot;#the-architecture-we-chose&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After research and discussion, we chose Clean Architecture with model-view-viewmodel (MVVM). Google’s &lt;a href=&quot;https://developer.android.com/topic/architecture&quot;&gt;Guide to App Architecture&lt;/a&gt; inspired our implementation.&lt;/p&gt;
&lt;p&gt;The guide targets Android projects, but its boundaries and terms also made sense for our other mobile platforms.&lt;/p&gt;
&lt;h3&gt;Why Clean Architecture fit our team&lt;a href=&quot;#why-clean-architecture-fit-our-team&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After comparing Clean Architecture with VIPER, we found these differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;VIPER and Clean Architecture use different terms. For example, an iOS developer might say &lt;code&gt;Interactor&lt;/code&gt; while an Android developer says &lt;code&gt;UseCase&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clean Architecture and MVVM gave our mobile developers shared terms. Those terms made cross-platform discussions and code reviews easier.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VIPER was unfamiliar to some iOS developers. New team members had to learn its components, abstractions, and vocabulary before they could contribute comfortably.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What changed for us&lt;a href=&quot;#what-changed-for-us&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We switched to Clean Architecture with MVVM and now use it for new projects. Collaboration across iOS, Android, and Flutter has improved.&lt;/p&gt;
&lt;p&gt;Mobile developers can discuss a &lt;code&gt;UseCase&lt;/code&gt;, &lt;code&gt;Repository&lt;/code&gt;, &lt;code&gt;Domain Layer&lt;/code&gt;, or &lt;code&gt;Data Layer&lt;/code&gt; with the same meaning. They can also review code from other platforms with less context.&lt;/p&gt;
&lt;p&gt;We created a document that explains how to implement Clean Architecture with MVVM on iOS. Its sections cover the Presentation, Domain, and Data layers with code examples.&lt;/p&gt;

&lt;div&gt; &lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt; &lt;p&gt;&lt;img src=&quot;https://suho.dev/images/content/posts/architecture/app-architecture.png&quot; alt=&quot;Detailed iOS application
architecture&quot; /&gt;&lt;/p&gt; &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt; &lt;/div&gt;
&lt;p&gt;We also applied Clean Architecture and MVVM to our &lt;a href=&quot;https://github.com/nimblehq/ios-templates&quot;&gt;iOS templates&lt;/a&gt;. The templates give new projects the same starting structure.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Clean Architecture is not a default answer for every project. Discuss the extra boundaries and boilerplate before your team adopts it. For us, the shared cross-platform language made that cost worthwhile.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This change worked because it addressed a collaboration problem that our team could already see. If your team has a similar problem, the same architecture might be worth exploring.&lt;/p&gt;
&lt;p&gt;If you have questions, mention me on &lt;a href=&quot;https://x.com/vansu_ho&quot;&gt;X&lt;/a&gt; or &lt;a href=&quot;https://www.linkedin.com/in/vansuho/&quot;&gt;LinkedIn&lt;/a&gt; with this post.&lt;/p&gt;</content:encoded></item><item><title>My #GitHubUnwrapped</title><link>https://suho.dev/posts/github-unwrapped/</link><guid isPermaLink="true">https://suho.dev/posts/github-unwrapped/</guid><description>This video shows my GitHub activity in 2023.</description><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>  </content:encoded></item><item><title>Why a Next.js lint check can miss TypeScript errors</title><link>https://suho.dev/posts/typescript-nextjs-lint-and-build/</link><guid isPermaLink="true">https://suho.dev/posts/typescript-nextjs-lint-and-build/</guid><description>Add an explicit TypeScript check when Next.js linting passes but the production build still fails.</description><pubDate>Tue, 26 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Linting and type checking can look like the same safety net, but they catch different problems. I learned that when a passing workflow still produced a failed build.&lt;/p&gt;
&lt;h2&gt;Why linting was not enough&lt;a href=&quot;#why-linting-was-not-enough&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I had a continuous integration (CI) workflow for a Next.js and TypeScript project. The workflow only ran the linter:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;linting.yml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Install dependencies&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;npm ci&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Lint code&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;npm run lint&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The lint check passed, but the production build failed during type checking. The linter and TypeScript compiler were checking different problems.&lt;/p&gt;
&lt;h2&gt;Add an explicit type check&lt;a href=&quot;#add-an-explicit-type-check&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I fixed the gap by running the TypeScript compiler before the linter.&lt;/p&gt;
&lt;p&gt;First, add a &lt;code&gt;typecheck&lt;/code&gt; script to &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;package.json&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;typecheck&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tsc --noEmit&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Then add the new step to the CI workflow:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;linting.yml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Install dependencies&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;npm ci&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Add a type-checking step.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Typecheck&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;npm run typecheck&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Lint code&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;npm run lint&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The workflow now stops on type errors before it starts the production build. For more context, see the related &lt;a href=&quot;https://github.com/vercel/next.js/discussions/31439&quot;&gt;Next.js discussion&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>How I check Cloudflare image caching with Dr.FLARE</title><link>https://suho.dev/posts/cloudflare-image-caching/</link><guid isPermaLink="true">https://suho.dev/posts/cloudflare-image-caching/</guid><description>Use Dr.FLARE to inspect Cloudflare image caching and understand what the cache is doing.</description><pubDate>Fri, 15 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cloudflare image caching can be hard to inspect from response headers alone. I use &lt;a href=&quot;https://chromewebstore.google.com/detail/drflare/pibckhncbjabhlomohgcdedacopeooml?hl=en&quot;&gt;Dr.FLARE&lt;/a&gt;, a Chrome extension, to make that process easier.&lt;/p&gt;
&lt;p&gt;The extension presents cache information in a visual format. It can help you understand how Cloudflare handles each image request.&lt;/p&gt;
&lt;p&gt;For setup details, see the Cloudflare Community post about the &lt;a href=&quot;https://community.cloudflare.com/t/community-tip-dr-flare-debug-tool-for-cloudflare-chrome-extension/110166&quot;&gt;Dr.FLARE debugging extension&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>How to sign Git commits with GPG on macOS</title><link>https://suho.dev/posts/signing-with-gpg-on-mac/</link><guid isPermaLink="true">https://suho.dev/posts/signing-with-gpg-on-mac/</guid><description>Configure GNU Privacy Guard on macOS, create a signing key, and use it to sign Git commits.</description><pubDate>Wed, 29 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;GNU Privacy Guard (GPG) lets you sign Git commits with a cryptographic key. Git hosting services can then mark the commit as verified.&lt;/p&gt;
&lt;p&gt;The setup has a few moving parts on macOS. Here is the sequence that works for my configuration.&lt;/p&gt;
&lt;h2&gt;Install and configure GPG&lt;a href=&quot;#install-and-configure-gpg&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Install GPG and &lt;code&gt;pinentry-mac&lt;/code&gt; with Homebrew:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gnupg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pinentry-mac&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;pinentry-mac&lt;/code&gt; lets you enter the key passphrase in a macOS window instead of Terminal.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the GPG configuration directory with private permissions:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.gnupg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;700&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.gnupg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;pinentry-mac&lt;/code&gt; to the GPG agent configuration:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;pinentry-program $(&lt;/span&gt;&lt;span&gt;which&lt;/span&gt;&lt;span&gt; pinentry-mac)&quot;&lt;/span&gt;&lt;span&gt; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;~/.gnupg/gpg-agent.conf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Add this line to the startup file for your shell:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GPG_TTY&lt;/span&gt;&lt;span&gt;=$(&lt;/span&gt;&lt;span&gt;tty&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For Zsh, use &lt;code&gt;~/.zshrc&lt;/code&gt;. For Bash, use &lt;code&gt;~/.bash_profile&lt;/code&gt; or &lt;code&gt;~/.bashrc&lt;/code&gt;, depending on how you start the shell.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reload the startup file. This example reloads &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/.zshrc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Stop the current GPG agent. GPG will start it again when needed:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gpgconf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--kill&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpg-agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h2&gt;Create a signing key&lt;a href=&quot;#create-a-signing-key&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Start the interactive key generator:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gpg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--full-generate-key&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Choose a supported key type and size. Set an expiration period that fits how you plan to manage the key.&lt;/p&gt;
&lt;p&gt;Use an email address that you have verified on GitHub. GitHub uses this address when it verifies a commit signature.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;List your secret keys with their long IDs:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gpg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--list-secret-keys&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--keyid-format=long&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The output includes a line similar to this one:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sec&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;rsa4096/YOUR_LONG_KEY_ID&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2026-08-21&lt;/span&gt;&lt;span&gt; [SC]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Copy the value after the slash. The remaining examples use &lt;code&gt;YOUR_LONG_KEY_ID&lt;/code&gt; as a placeholder.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Export the public key:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gpg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--armor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;YOUR_LONG_KEY_ID&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Configure Git to use the key and sign commits by default:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.signingkey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;YOUR_LONG_KEY_ID&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit.gpgsign&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Create a signed test commit:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Test signed commit&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--allow-empty&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;pinentry-mac&lt;/code&gt; window asks for your passphrase. Enter it and select &lt;strong&gt;OK&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Add the public key to GitHub&lt;a href=&quot;#add-the-public-key-to-github&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Copy the public key:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gpg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--armor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;YOUR_LONG_KEY_ID&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;pbcopy&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Then sign in to GitHub. Open &lt;strong&gt;Settings &amp;gt; SSH and GPG keys&lt;/strong&gt; and add the copied public key.&lt;/p&gt;
&lt;p&gt;GitHub can now verify commits that use this key and a verified email address from your account.&lt;/p&gt;
&lt;p&gt;For more detail, see the official guides for &lt;a href=&quot;https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key&quot;&gt;creating a GPG key&lt;/a&gt;, &lt;a href=&quot;https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key&quot;&gt;configuring Git with that key&lt;/a&gt;, and &lt;a href=&quot;https://gnupg.org/documentation/manuals/gnupg26/gpg-agent.1.html&quot;&gt;configuring GPG Agent&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>How I rebase linked or chained pull requests</title><link>https://suho.dev/posts/git-rebase-onto/</link><guid isPermaLink="true">https://suho.dev/posts/git-rebase-onto/</guid><description>Use git rebase --onto and --update-refs to keep a chain of dependent branches aligned.</description><pubDate>Tue, 28 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Linked pull requests let me split a large change into smaller reviews. The tradeoff appears when an earlier branch changes or merges.&lt;/p&gt;
&lt;p&gt;Git provides two options that make this cleanup easier. I use &lt;code&gt;--onto&lt;/code&gt; for one branch and &lt;code&gt;--update-refs&lt;/code&gt; for a branch chain.&lt;/p&gt;
&lt;h2&gt;Move one branch with &lt;code&gt;--onto&lt;/code&gt;&lt;a href=&quot;#move-one-branch-with---onto&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Consider the following Git history:&lt;/p&gt;

      &lt;div&gt;
        &lt;pre&gt;gitGraph
    commit id: &quot;0-19as8su&quot;
    commit id: &quot;1-0a9s8ds&quot;
    branch feature-1
    checkout feature-1
    commit id: &quot;2-a0s9asd&quot;
    commit id: &quot;3-ss91820&quot; type: HIGHLIGHT
    branch feature-2
    checkout feature-2
    commit id: &quot;4-as98dam&quot;
    commit id: &quot;5-as0d9km&quot;
    checkout feature-1
    commit id: &quot;6-0as9d8s&quot;
    commit id: &quot;7-as90dia&quot;
    checkout main
    merge feature-1 id: &quot;8-0as9d8s&quot;&lt;/pre&gt;
      &lt;/div&gt;
    
&lt;p&gt;We create &lt;code&gt;feature-1&lt;/code&gt; from &lt;code&gt;main&lt;/code&gt;. We then create &lt;code&gt;feature-2&lt;/code&gt; from &lt;code&gt;feature-1&lt;/code&gt; and continue working on it.&lt;/p&gt;
&lt;p&gt;Later, we add two commits to &lt;code&gt;feature-1&lt;/code&gt; and merge that branch into &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this point, &lt;code&gt;feature-2&lt;/code&gt; still starts from the earlier &lt;code&gt;feature-1&lt;/code&gt; commit. Check out &lt;code&gt;feature-2&lt;/code&gt;, then move its commits onto &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;feature-2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--onto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3-ss91820&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Here, &lt;code&gt;3-ss91820&lt;/code&gt; is the commit where &lt;code&gt;feature-2&lt;/code&gt; branched from &lt;code&gt;feature-1&lt;/code&gt;. Git takes the later commits and reapplies them onto &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The new history looks like this:&lt;/p&gt;

      &lt;div&gt;
        &lt;pre&gt;gitGraph
    commit id: &quot;0-19as8su&quot;
    commit id: &quot;1-0a9s8ds&quot;
    branch feature-1
    checkout feature-1
    commit id: &quot;2-a0s9asd&quot;
    commit id: &quot;3-ss91820&quot; type: HIGHLIGHT
    checkout feature-1
    commit id: &quot;6-0as9d8s&quot;
    commit id: &quot;7-as90dia&quot;
    checkout main
    merge feature-1
    branch feature-2
    checkout feature-2
    commit
    commit&lt;/pre&gt;
      &lt;/div&gt;
    
&lt;h2&gt;Update a branch chain with &lt;code&gt;--update-refs&lt;/code&gt;&lt;a href=&quot;#update-a-branch-chain-with---update-refs&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;--update-refs&lt;/code&gt; option can update the branches in a chain during one rebase. Consider this history:&lt;/p&gt;

      &lt;div&gt;
        &lt;pre&gt;%%{init: { &apos;gitGraph&apos;: {&apos;showCommitLabel&apos;: false}} }%%
gitGraph
    commit
    commit
    branch feature-1
    checkout feature-1
    commit
    commit
    branch feature-2
    checkout feature-2
    commit
    commit
    checkout feature-2
    branch feature-3
    checkout feature-3
    commit
    commit
    commit
    checkout feature-1
    commit
    commit
    checkout main
    merge feature-1&lt;/pre&gt;
      &lt;/div&gt;
    
&lt;p&gt;After we merge &lt;code&gt;feature-1&lt;/code&gt; into &lt;code&gt;main&lt;/code&gt;, the remaining work has two parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rebase &lt;code&gt;feature-2&lt;/code&gt; onto &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rebase &lt;code&gt;feature-3&lt;/code&gt; onto &lt;code&gt;feature-2&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Check out the last branch in the chain. Then use &lt;code&gt;--update-refs&lt;/code&gt; to complete both steps:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Check out the top branch in the chain.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;feature-3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Rebase the branch onto main and update every branch in the chain.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--update-refs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The new history looks like this:&lt;/p&gt;

      &lt;div&gt;
        &lt;pre&gt;%%{init: { &apos;gitGraph&apos;: {&apos;showCommitLabel&apos;: false}} }%%
gitGraph
    commit
    commit
    branch feature-1
    checkout feature-1
    commit
    commit
    checkout feature-1
    commit
    commit
    checkout main
    merge feature-1
    branch feature-2
    checkout feature-2
    commit
    commit
    checkout feature-2
    branch feature-3
    checkout feature-3
    commit
    commit
    commit&lt;/pre&gt;
      &lt;/div&gt;
    
&lt;p&gt;This approach keeps the branch relationships intact without a separate rebase for every branch. I still review the new history before I force-push anything.&lt;/p&gt;</content:encoded></item><item><title>A better architecture for implementing forms in iOS applications</title><link>https://nimblehq.co/blog/better-form-architecture-for-ios-applications</link><guid isPermaLink="true">https://nimblehq.co/blog/better-form-architecture-for-ios-applications</guid><description>How to implement a clean, maintainable form architecture with UITableView.</description><pubDate>Tue, 04 May 2021 00:00:00 GMT</pubDate><content:encoded/></item><item><title>Creating a local authentication SDK on CocoaPods</title><link>https://nimblehq.co/blog/local-authentication-sdk-on-cocoapods</link><guid isPermaLink="true">https://nimblehq.co/blog/local-authentication-sdk-on-cocoapods</guid><description>A step-by-step guide to creating and publishing a biometric authentication SDK on CocoaPods.</description><pubDate>Wed, 24 Mar 2021 00:00:00 GMT</pubDate><content:encoded/></item></channel></rss>