<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ege Sucu on Medium]]></title>
        <description><![CDATA[Stories by Ege Sucu on Medium]]></description>
        <link>https://medium.com/@egesucu?source=rss-a279959eb187------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*dqnuWMNC94A1spoN5xWwsg.jpeg</url>
            <title>Stories by Ege Sucu on Medium</title>
            <link>https://medium.com/@egesucu?source=rss-a279959eb187------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 24 Apr 2026 05:06:00 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@egesucu/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Understanding SwiftUI]]></title>
            <link>https://egesucu.medium.com/understanding-swiftui-609fcaf08835?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/609fcaf08835</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[declarative-programming]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Wed, 11 Mar 2026 20:02:16 GMT</pubDate>
            <atom:updated>2026-03-11T20:02:16.900Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sqJWqCyK1SFUs0oQehIqaA.png" /></figure><p>Today, many iOS developers either build apps entirely in SwiftUI or use parts of SwiftUI without fully understanding how the framework works. Some developers misuse SwiftUI or apply UIKit patterns directly, which can lead to inefficient or confusing code. So, in this article, I will be basic and tell this terminologies as if you are beginners. This article may also help developers who migrated from UIKit</p><p>and still approach SwiftUI with UIKit-style thinking. Let’s begin.</p><h3><strong>View</strong></h3><p>Views in SwiftUI is the essential layer of the framework. A View is a protocol which you conform to any views to build your UI.</p><pre>struct ContentView: View {<br>    var body: some View {<br>        VStack {<br>            Text(&quot;Hello, world!&quot;)<br>        }<br>        .padding()<br>    }<br>}</pre><p>A SwiftUI View is conceptually closer to UIKit’s UIView rather than UIViewController. It describes a piece of UI and how it should react to state changes. You use modifiers, states, ViewBuilders &amp; computed variables to shape a UI and control the status of it.</p><p>Some people may prefer to keep all logics inside of the View, and that’s understandable since Views are conformed with struct types, which are value types. Because SwiftUI views are structs (value types), they are cheap to create and discard.</p><p>SwiftUI recreates view values frequently during state updates.</p><h4><strong>Why some View?</strong></h4><p>The reason why we declare the View as some in our views is that the View itself is a protocol. `some View` is an opaque return type. It means the function returns a specific concrete type that conforms to `View`, but the exact type is hidden from the caller.</p><p><strong>VStack</strong>? <strong>Text</strong>?, <strong>Button</strong>?, <strong>Group</strong>?</p><p>This means technically, you can write it like this:</p><pre>import SwiftUI<br><br>struct ContentView: View {<br>    var body: Text {<br>        Text(&quot;Hello, world!&quot;)<br>    }<br>}</pre><p>But this is not correct:</p><pre>import SwiftUI<br><br>struct ContentView: View {<br>    var body: View {<br>        Text(&quot;Hello, world!&quot;)<br>    }<br>}</pre><h3><strong>View Composition</strong></h3><p>How would you build your UI in SwiftUI? You have:</p><ul><li>ViewBuilders</li><li>Containers</li><li>Reusable Views</li></ul><h4><strong>ViewBuilders</strong></h4><p><strong>@ViewBuilder</strong> allows multiple views to be declared in a closure and combines them</p><p>into a single view type behind the scenes. This is useful when there’s a condition based UI part(i.e. if condition { <strong>ThisView()</strong> }). Containers in itself(<strong>VStack</strong>, <strong>HStack</strong> etc.) actually do use <strong>ViewBuilder</strong> in it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y0b0a1imrIiRLjiIQZK1Dg.png" /></figure><p>Actually, even <strong>var body: some View {}</strong> is a ViewBuilder. You just don’t see it directly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WNrJEYMasG4iTMZYFDo4eg.png" /></figure><h4><strong>Containers</strong></h4><p>Containers are layout views that arrange and group child views. We usually use <strong>VStack</strong>, <strong>HStack</strong> &amp; <strong>ZStack</strong>. <strong>LazyVStack</strong>, <strong>LazyHStack</strong>, <strong>LazyVGrid</strong> &amp; <strong>LazyHGrid</strong>.</p><p>You can also create your own container by defining the layout with types.</p><pre>struct Carousel&lt;Content: View&gt;: View {<br>    <br>    let content: Content<br>    <br>    init(@ViewBuilder content: () -&gt; Content) {<br>        self.content = content()<br>    }<br>    <br>    var body: some View {<br>        ScrollView(.horizontal) {<br>            HStack {<br>                content<br>            }<br>            .padding()<br>        }<br>    }<br>}</pre><h4><strong>Reusable Views</strong></h4><p>SwiftUI is better aligned with sub-views. Instead of creating a view with 6 <strong>HStack</strong>, those having some <strong>VStack</strong>, and even deeper, it’s best to wrap the view with subviews. You can introduce a local variable via:</p><pre>var statusView: some View {<br>        VStack {<br>            Text(&quot;&quot;)<br>        }<br>    }</pre><p>or</p><pre>func statusView(lastPosition: Int) -&gt; some View {<br>        VStack {<br>            Text(&quot;\(lastPosition)&quot;)<br>        }<br>    }</pre><p>If you want to use this sub-view with other screens, it’s best to move it under a View file with parameters to feed</p><pre>struct Status {<br>    let value: Int<br>}<br><br>struct StatusView: View {<br>    <br>    let lastStatus: Status<br>    <br>    var body: some View {<br>        Text(&quot;\(lastStatus.value)&quot;)<br>    }<br>}</pre><h3><strong>Modifiers</strong></h3><p>Modifiers are powerful in SwiftUI when developing any app. They get your view, apply the change you want, and returns a brand new View to be used.</p><p>You can customize your app with them, style with .font() &amp; .background() etc., lay them out with <strong>.padding()</strong>, <strong>.offset(x:y:)</strong>, <strong>frame(width:height:)</strong> etc., make it accessible with .<strong>accessibilityLabel()</strong>, add interactivity with <strong>.onTapGesture()</strong>, <strong>.onChange(of:)</strong> &amp; <strong>.disabled()</strong> &amp; present it with <strong>.sheet()</strong>, <strong>.alert()</strong>, <strong>.navigationTitle()</strong> etc.</p><p>They are pretty flexible, always create a new view for the SwiftUI view &amp; reusable with custom ViewModifiers you write them.</p><p>You can chain them together like:</p><pre>Text(&quot;Hello&quot;)<br>     .padding(4)<br>     .foregroundStyle(Color.black)<br>     .font(.headline)</pre><p>To create a custom modifier, we first define it via creating a Modifier struct, conforming it to ViewModifier protocol, use the body type to shape it &amp; write a View extension to convert it into a modifier to be used.</p><pre>struct BorderedCaption: ViewModifier {<br>    func body(content: Content) -&gt; some View {<br>        content<br>            .font(.caption2)<br>            .padding(10)<br>            .foregroundColor(Color.blue)<br>        // … other modifiers<br>    }<br>}<br>extension View {<br>    func borderedCaption() -&gt; some View {<br>        modifier(BorderedCaption())<br>    }<br>}<br>// Usage: Text(&quot;Example&quot;).borderedCaption()</pre><p>The order of them matters, since Swift checks them line by line from top to the bottom. It’s also important to use it when required.</p><p>Some developers encourage to use conditioned modifiers such as .if(condition) { apply this modifier } but it has some <a href="https://www.objc.io/blog/2021/08/24/conditional-view-modifiers/">serious drawbacks</a> on the animation &amp; state changes so I would encourage to not use it as such.</p><h3><strong>States</strong></h3><p>States are property wrappers to manage the view’s persistence while it’s available(it’s not a long term persistence solution since states gets killed with the view dismissal).</p><p>When we mark a variable or an observed object(iOS 17+) with @State, SwiftUI starts tracking the the value to persists locally, present to us via .wrappedValue &amp; updates it whenever there’s a change. Each state change essentially redraws the view since SwiftUI views are designed to be re-created. State holds value, so the change in a state does not reset other state value.</p><p>States are private to their View and it’s advised as such. SwiftUI animations works best with States to animate their changes such as:</p><pre>.scaleEffect(isPlaying ? 1 : 1.5)</pre><p>If you want to share it’s state with a subview, <strong>@Binding</strong> can be set into the child view, so that the state can be changed from child view.</p><h3><strong>States in Data Flow</strong></h3><p>In SwiftUI, there are some states to manage your data across the app.</p><p><strong>@State</strong> is essential on a view to manage the local state of a value.</p><p><strong>@Binding</strong> helps the two-way binding between the view &amp; the sub view to manage the data.</p><p><strong>@Observable</strong> state allows the custom models and its values to be tracked.</p><p>If you develop apps prior to iOS 17, <strong>@ObservableObject</strong> &amp; <strong>@StateObject</strong> manages the same work as <strong>@Observable</strong> does.</p><p><strong>@Environment</strong> &amp; <strong>@EnvironmentObject</strong> helps developers to share data across the view hierarchy. Environment is more practical to manage states such as colorScheme, locale, calendar &amp; others, where EnvironmentObject is used to share your custom model across the views, such as using a UserModel across different screens. The difference between them is, <strong>@Environment</strong> expects a struct with key-value relationship while <strong>@EnvironmentObject</strong> is expecting a class with ObservableObject conformance.</p><p>You can also create your own <strong>@Environment</strong> value as such:</p><pre>struct SunshineKey: EnvironmentKey {<br>    static var defaultValue: Double = 1.02<br>}<br>extension EnvironmentValues {<br>    var sunshine: Double {<br>        get { self[SunshineKey.self] }<br>        set { self[SunshineKey.self] = newValue }<br>    }<br>}<br><br>struct SunshineView: View {<br>    <br>    @Environment(\.sunshine) var sunshine<br>    <br>    var body: some View {<br>        Text(&quot;Welcome to the sunshine, \(sunshine)&quot;)<br>    }<br>}</pre><h3><strong>Layout</strong></h3><p>SwiftUI has a layout system that eases up UIKit’s AutoLayout system. It has some rules:</p><ul><li>Parent view proposes a size</li><li>Child View chooses its own size</li><li>Parent positions the child</li></ul><h4><strong>Key concepts in Layout</strong></h4><p><strong>Stacks</strong>: HStack, VStack &amp; ZStack are container views that are holding the subviews together. You can use alignment &amp; spacing to manage their positioning.</p><p><strong>Modifiers</strong>: You would use .frame() &amp; .padding() to assign spacings to any view. The order matters between them, so make sure to place them in the correct order.</p><p><strong>Spacer</strong>() is a flexible component which fills the available spacing between two items to be placed. Please note that, Spacer() is best used with the stacks &amp; does not fill the available width/height when a vertical/horizontal scrollview exists.</p><p><strong>GeometryReader</strong> is used to calculate exact size of the screen to run calculations such as <strong>let customWidth = geometry.size.width</strong> or <strong>safeAreaInsets</strong> will give you the insets to work with, or <strong>geometry.frame(in: .global).minY</strong> to get the position.</p><h3><strong>Identity is Important</strong></h3><p>SwiftUI wants things to be <strong>Identifiable</strong>. Whether you’re building a list or defining a view, id is useful to SwiftUI to evaluate which views should be re-rendered vs. which views should be stayed same.</p><p>That is why, SwiftUI wants you to define identifiable for ForEach or List blocks. It’s important to make sure the id value is unique, so that no two items should match.</p><pre>struct Stars: Identifiable {<br>    <br>    let id = UUID()<br>    <br>    let amount: Int<br>}<br><br>struct StarView: View {<br>    <br>    var stars: [Stars] = [<br>        .init(amount: 2),<br>        .init(amount: 3),<br>        .init(amount: 4)<br>    ]<br>    <br>    <br>    var body: some View {<br>        ForEach(stars) { star in<br>            Text(&quot;Vote for \(star.id) is \(star.amount)&quot;)<br>        }<br>    }<br>}</pre><h3><strong>Navigation</strong></h3><p>Navigation in SwiftUI can be achieved by using NavigationStack or NavigationSplitView, and provide a NavigationDestination to coordinate. However, programmatic navigation via UIKit is still relevant.</p><pre>struct ContentView: View {<br>    enum Route: Hashable {<br>        case profile<br>        case settings<br>        case details(String)<br>    }<br><br>    var body: some View {<br>        NavigationStack {<br>            List {<br>                NavigationLink(&quot;Go to Profile&quot;, value: Route.profile)<br>                NavigationLink(&quot;Go to Settings&quot;, value: Route.settings)<br>                NavigationLink(&quot;Go to Item Details&quot;, value: Route.details(&quot;SwiftUI Basics Item&quot;))<br>            }<br>            .navigationTitle(&quot;NavigationStack Demo&quot;)<br>            .navigationDestination(for: Route.self) { route in<br>                switch route {<br>                case .profile:<br>                    VStack(spacing: 12) {<br>                        Image(systemName: &quot;person.crop.circle.fill&quot;)<br>                            .font(.system(size: 48))<br>                            .foregroundStyle(.blue)<br>                        Text(&quot;Profile Screen&quot;)<br>                            .font(.title2)<br>                    }<br>                case .settings:<br>                    VStack(spacing: 12) {<br>                        Image(systemName: &quot;gearshape.fill&quot;)<br>                            .font(.system(size: 48))<br>                            .foregroundStyle(.orange)<br>                        Text(&quot;Settings Screen&quot;)<br>                            .font(.title2)<br>                    }<br>                case .details(let itemName):<br>                    VStack(spacing: 12) {<br>                        Text(&quot;Details Screen&quot;)<br>                            .font(.title2)<br>                        Text(&quot;Selected item: \(itemName)&quot;)<br>                            .foregroundStyle(.secondary)<br>                    }<br>                }<br>            }<br>        }<br>    }<br>}</pre><h3><strong>Lifecycle</strong></h3><p>SwiftUI does not have UIKit’s lifecycle, but it still supports:</p><ul><li>onAppear()</li><li>onDisappear()</li><li>onChange()</li><li>task()</li></ul><p>to handle lifecycle changes on the view.</p><h3><strong>Animations</strong></h3><p>Animations are handled in SwiftUI with two ways.</p><p>Either defining an <strong>.animation()</strong> modifier or with <strong>withAnimation{}</strong> block. Also, there are modifiers such as <strong>.scaleEffect()</strong>, <strong>.rotationEffect()</strong> and even <strong>.opacity()</strong> works nicely with it.</p><p>This article is hopefully opened up many concepts to you on the fundamentals of the SwiftUI and how you code it. If you have any questions, feel free to watch SwiftUI videos on Apple Developer’s website, read more articles on the deep threads and use the comment section down below. Happy coding.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=609fcaf08835" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2025 → 2026: Neler oluyor?]]></title>
            <link>https://egesucu.medium.com/2025-2026-neler-oluyor-3a9d0e13e1ca?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/3a9d0e13e1ca</guid>
            <category><![CDATA[new-years-resolutions]]></category>
            <category><![CDATA[journey]]></category>
            <category><![CDATA[year-in-review]]></category>
            <category><![CDATA[blogging]]></category>
            <category><![CDATA[travel]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Thu, 01 Jan 2026 21:17:30 GMT</pubDate>
            <atom:updated>2026-01-01T21:22:36.285Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wlEvRV7-bhaoSKyYvpklsQ.png" /></figure><p>Öncelikle bu uyarıyı önden yapmak istiyorum. Bu bir kişisel blog yazısıdır. Yazılımla alakalı paylaşımlarım için diğer paylaşımlarıma bakabilirsiniz, bu yazı ilginizi çekmeyebilir.</p><p>EN: First of all, I want to make the warning that this will be a personal article. If you want to interest software topics only, you may wish to skip this article. Also, this will be in Turkish.</p><p>Öncelikle <a href="https://medium.com/@egesucu/açıkçası-tutturamadım-0228bcaa8ca1">geçen sene</a> kendime koyduğum hedeflerde %20 civarı bir başarıya ulaştığımı söyleyebilirim. Finansal konularda %62.5 civarında bir azalma yaşandı. Bunun mümkün olabileceğine dair o zamanlar kafamda çok soru işareti vardı, fakat üstesinden gelmeyi başardım. Onun dışındaki pek çok hedefte baştan başarısız olup bıraktığımı söyleyebilirim.</p><p>2025 benim için nasıl geçti, göz atalım.</p><h3>Ocak 2025</h3><p>Ocak ayı güzel bir tatil ile başladı. Eşimle Abu Dhabi, UAE’e giderek Coldplay konseri izleme şansımız oldu. Kışın ortasında yaz tatili gibi gezmek, şehrin kültürünü yaşamak ve Coldplay’in eşsiz tecrübesini yaşamak güzel oldu.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7-RJ40m9goc_4Wv2cvSO6A.png" /><figcaption>Abu Dhabi ‘25</figcaption></figure><h3>Şubat 2025</h3><p>Bu ay işlerin yoğunluğu arası bir şehir dışı yapmaya karar verip 2 gün Edirne’yi gezmeye karar verdik. Osmanlı tarihinin önemli bir parçası olan bu şehir, geçmişten taşıdığı sanat eserleri ile bizlere güzel kareler bıraktı.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HLjjQjl3-qTynjnuwXwU5Q.png" /><figcaption>Edirne ‘25</figcaption></figure><h3>Mart 2025</h3><p>Bu ay benim için bir dönüm noktası oldu, çünkü yıllarca sürdürdüğüm kariyerimde gıpta ile baktığım Senior terfimi aldım, ve artık kendime Senior iOS Developer demeye başlayabildim.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/888/1*bPR19_bBSpK0iH-U9mo_ZA.gif" /></figure><p>Ayrıca yakınımdaki Feshane’yi de gezme fırsatını bulmuş oldum.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wmiR3QpqwtUhuZh5m6g-JA.jpeg" /><figcaption>Feshane</figcaption></figure><h3>Mayıs 2025</h3><p>Mayıs ayları güzeldir, bu sene de öyle oldu. Eşimle evlilik yıl dönümümüzü ve doğumgünümü(30 kulübüne girdim) kutladık, ve ayın sonunda Fethiye, Kumburnu plajı gibi harika yerleri görebildik.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2DgkD2KKJAOgJ1nRf_6Nlw.png" /><figcaption>Fethiye ‘25</figcaption></figure><h3>Haziran 2025</h3><p>Görece daha sakin bir aydı diyebilirim. Profesyonel fotoğraf çekimi ve F1 film gösterimi ile geçti. Bu dönem, daha çok şirketteki projenin bitirme süresine yaklaşıldığı için ona odaklandığım bir dönem olarak geçti.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O0z87RVvepMtHCR7vcRWQA.png" /></figure><h3>Temmuz 2025</h3><p>Bu ay, biraz daha yazlıkta çalışma modunun açıldığı ve Ağustos’ta yapacağım ilk Almanya Gezisine hazırlanmak ile geçti.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-2cNWsp42vjJaVJ7DBieXA.png" /><figcaption>Armutlu &amp; Gemlik ‘25</figcaption></figure><h3>Ağustos 2025</h3><p>Hallo Berlin, Hallo Hannover. Bu ay, ilk çıkan schengen vizesi ile kısa bir Almanya kaçamağı yaptık. Genel olarak güneşli veya hafif yağışlı geçen bu gezide Döner Kebap yediğimiz en lokal yemek olabilir. (Cidden, Five Guys neden Türkiye’de yok…)</p><p>İş olarak bu ay projenin müşteri kaynaklı durduğu bir dönemdi. Özellikle ayın geri kalanında bir sonraki projeme yerleşmeyi bekledim.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g2EoW4jAtnuWVO3HLsjRmQ.png" /><figcaption>Berlin ‘25</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LgZ2Ieta3mPx3LHS4B7irQ.png" /><figcaption>Berlin &amp; Hannover ‘25</figcaption></figure><h3>Eylül 2025</h3><p>Bu ay, yeni projenin başladığı, bazı eski iş arkadaşlarımla yeniden bir araya geldiğim keyifli bir ay oldu. Eşimin ve babamın doğumgünlerini kutlarken aylarca gezdikten sonra şehir dışına hiç çıkmadığım bir ay geçmiş olabilir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_ZvSItnMCdBF6Q-_Cpq7RA.png" /><figcaption>Istanbul ‘25</figcaption></figure><h3>Ekim 2025</h3><p>Ekim ayı da genel olarak sakin geçti. İş ile ilgilenmeye devam ettiğim bu dönemde ayrıca doğa &amp; park yürüyüşleri gerçekleştirme gibi etkinliklerde bulundum. Ayrıca, eşime güzel katlanır bir masa almış olduk, epey kullanışlı bir ürün.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nxCsUxh15uWyx2ofcLdQ2A.png" /><figcaption>Ekim ‘25</figcaption></figure><h3>Aralık 2025</h3><p>Aralık ayı biraz daha hareketliydi. İş gezisi için birkaç gün İzmir’e çıkartma yaptık.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FfbYNnm0WxPZ4pDLoYDyAA.png" /><figcaption>İzmir ‘25</figcaption></figure><p>Ardından noel’in gelmesi ile bir Almanya çıkartması daha yapmaya karar verdik, bu sefer rota Münih,</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DyIpYleJ7r3lQckzWdu41w.png" /><figcaption>Münih ‘25</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rvJ_crryERTB2zjlqGM7AQ.png" /><figcaption>Münih ‘25</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EhdacpPpKLyJudU7hugwXg.png" /><figcaption>Münih ‘25</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aiJT5pjdOB-YnQ2wKXvQIg.png" /><figcaption>Münih ‘25</figcaption></figure><p>Nürnberg</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d3P9NB3Ky5Nbyyqy7NU4Ew.png" /><figcaption>Nürnberg ‘25</figcaption></figure><p>ve Stuttgart’tı.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6-dqvYa7Rw-ouVaDnkQSYg.png" /><figcaption>Stuttgart ‘25</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_FNM-029MMhboZ8zhlhaqg.png" /><figcaption>Stuttgart ‘25</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CxjJy10PC7ROnfZWbQDVOg.png" /><figcaption>Stuttgart ‘25</figcaption></figure><h3>2026</h3><p>2025&#39;i dolu dolu bir yıl olarak geride bıraktığımı söyleyebilirim. Her ne kadar hedefleri tutturamamış olsam da, bu sene bana bir çok deneyim sundu.</p><p>Dolma kalem ile defter tutmaya 2024 sonu gibi başlamıştım, 2025&#39;te tam gaz devam ederek haftalarca yaptıklarımı kayda aldım.</p><p>Şimdi sıra 2026&#39;da. Bu sene için neler hedefleyeceğim ve neler yapacağım konularında epey düşündüm. Basit bir şekilde geçmiş sene yapamadıklarımı kopyala-yapıştır yapabilirdim. Fakat bunun yerine edindiğim tecrübeler ile daha farklı şeyler başarmaya karar verdim. Bazı hedeflerim geçmişten alma olacak, evet. Ama bazıları 30 yıldır sürdüreceğim bazı trendlere aykırı olarak gelişecek, çünkü artık daha farklı düşünmeye başlıyorum.</p><p><strong>Ana maddeleri ile yeni hedeflerim şunlar:</strong></p><ul><li>Kilo ile savaş &amp; daha fazla egzersiz (evet, bu hedefi öyle ya da böyle yapmak istediğimi hepimiz biliyoruz)</li><li>Daha fazla kişisel yazı</li><li>Birikime geçiş</li><li>10 Kitap Okuma</li><li>Gece Çalışmayı Bırakma</li></ul><h4><strong>Kilo &amp; Egzersiz</strong></h4><p>Eğer bu blogu uzun zamandır takip ediyorsan, bu konunun benim için yeni olmadığını biliyorsundur. Yine de, her sene olduğu gibi bu hedefe bu sene de asılmak istiyorum. Geçmiş hataların beni yıldırmasını istemiyorum. Artık bu konuda daha iyi bir Ege olmak istiyorum. Bakalım bu sene, bunu başarabilecek miyim.</p><h4>Daha Fazla Kişisel Yazı</h4><p>Bu konuda Medium blogum çok zayıf kalmıştı. Uzun bir süredir, kişisel yazılardan çok yazılım odaklı olmak istediğim için, yazıların sıklığı epey düşüyordu. Kişisel düşüncelerim de geri planda kalıyordu. Bu sebeple kişisel yazılarımı arttırmaya karar verdim. Medium’da artık yazılımın yanısıra, teknolojiye dair daha fazla kişisel görüşümü dile getirmeyi planlıyorum. Ayrıca, kısa yazı formatına da geri dönebilirim. Bu konuda kafamdaki planları netleştirince ortaya güzel şeyler çıkacağına inanıyorum.</p><h4>Birikime Geçiş</h4><p>Geçen seneki %60&#39;ı aşkın başarım henüz tamamlanmamış da olsa, artık bu sene için ciddi birikimler yapmaya geçmeyi planlıyorum. Belki başlangıçta ufak adımlar olabilir, fakat sene sonuna kadar iyi bir birikim yapmayı hedefliyorum.</p><h4>10 Kitap Okuma</h4><p>Geçtiğimiz sene toplamda 2 kitap ya okudum, ya okuyamadım. Bu konuda kendimi gaza getirmek, ve eşimin 40 kitabı devirdiğini görmek beni motive ediyor, bunu yapmak istiyorum.</p><h4>Gece Çalışmayı Bırakma</h4><p>Gece mesaisi, şirketin uyguladığı bir politika değil. Fakat ben bir süredir, gün içi daha boş takılıp akşamları ek çalışma ile onu telafi etmeye yönelik davranışlar sergiliyordum. Bu durum pek hoşuma gitmemeye başladı. Bundan dolayı mesai bitimi bilgisayardan uzak kalarak bu kötü alışkanlığı yenmeyi, ve gün içi işe daha odaklanmayı planlıyorum.</p><p>Bu hedefler dışında bazı ufak değişiklikler de yapacağım, bunlardan sizi etkileyecek olanı, <strong>blog domain’ini bırakma</strong> kararı almamdan geçiyor. 2020&#39;lerden bu yana kullandığım <strong>blog . egesucu . com . tr</strong> adresini(ve Premium Medium üyeliğini) bırakmaya karar verdim. Bunda faktör aboneliklere harcadığım para konusunda uygulayacağım diyetlerden ötürü oluyor. Medium’da güzel bir kitlem bulunuyor ve blog domain’in olmayışının beni çok derin etkileyeceğini düşünmüyorum(Google search kısmı hariç, ama çoğunuz zaten yapay zeka kullanarak makale arayıp bulur hale geldi). Zamanında yatırım ile aldığım bazı alan adlarını da bırakmayı planlıyorum(com.tr hiçbir yere gitmeyecek)</p><p>Bakalım bu hedefleri bu sene tutturabilecek, ve 2025 gibi mükemmel bir yılı 2026&#39;da yaşayabilecek miyim(tahminen daha az hareketli), göreceğiz.</p><p>Hepinize Sevdiklerinizle İyi Yıllar</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3a9d0e13e1ca" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Yabancı Çalışanlarla Uygulama Yazılım Süreci]]></title>
            <link>https://egesucu.medium.com/yabanc%C4%B1-%C3%A7al%C4%B1%C5%9Fanlarla-uygulama-yaz%C4%B1l%C4%B1m-s%C3%BCreci-60be70052d2b?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/60be70052d2b</guid>
            <category><![CDATA[teamwork]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[community]]></category>
            <category><![CDATA[project-management]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Sun, 12 Oct 2025 12:15:21 GMT</pubDate>
            <atom:updated>2025-10-12T12:15:21.382Z</atom:updated>
            <content:encoded><![CDATA[<h4>Yabancı Projeler Türk projelerden nasıl daha iyi yürüyor</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vUwmbM2Y8_59Ak_c" /><figcaption>Photo by <a href="https://unsplash.com/@anniespratt?utm_source=medium&amp;utm_medium=referral">Annie Spratt</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Uzun bir aradan sonra Türk yazılımcı ekosistemine yazacağım için Türkçe kaleme aldığım bir yazı ile karşınızdayım. Bu yazıda sizlere tecrübelerimi aktararak Türkiye’de neden uygulama yazılımı ve yönetiminde sorunlar bulunduğunu anlatmak istiyorum.</p><h3>Giriş</h3><p>Türkiye yazılım konusunda epey genç ve dinamik bir ekosisteme sahip. Genç nüfus kaynaklı ciddi bir yazılım kaynağımız var ve pek çok yazılım firmasına sahibiz. Bu firmaların:</p><ul><li>kimisi kendi yazılımını üretiyor</li><li>kimisi başka şirketin yazılımlarına yazılımcı kaynağı sağlıyor</li><li>kimisi de başka şirketin “teknoloji ekibi” adı altında yazılımlar gerçekleştiriyor.</li><li>Ayrıca indie Geliştiricileri de unutmayalım.(Bu yazı sizden ziyade kurumsallar için daha geçerli olacak.)</li></ul><p>Geçmişte küçük, orta ve büyük ölçekli projelerde bulundum. Çalıştığım ekiplerde yeteneklerine hayran kaldığım çok insan oldu(sadece benim üst seviyelerim değil) ve kendilerinden çok şey öğrendim.</p><p>Kariyerime başladığımdan beri 5 yılı aşkın süredir farklı scrum ekipleriyle çalıştım ve son 3 senedir Adesso ile birlikte yurtdışındaki ekipler ile farklı projeler üzerinde çalışıyorum.</p><p>Zamanla fark ettiğim şey, X(eski adıyla Twitter) içerisinde pek çok yazılımcının proje yönetimleri ile, süreçler ile ve yaşadıkları stres ile mücadele etmeleri ile dolmuş vaziyette. Bazı insanlar hala “mühendislik” ve “geliştirici” arasındaki fark üzerinde düşünürken, kimisi(bir dönem benim de yaptığım) hangi mimarinin ötekini döveceği gibi tartışmalara girmiş vaziyette.</p><p>Yazılım ekiplerinde süreç yönetimi sadece teknik bir konu değil; kültürel bir olgunluk meselesi. Bu farkı en net şekilde yabancı ekiplerle çalışmaya başladığımda gördüm</p><p>Bu yazı ile size yurtdışında yürüyen projelerin nasıl disiplinlerle çalıştığını, neden başarılı olduğunu ve morallerin yerinde olduğunu, kendi tecrübelerimden ve çalıştığım Alman iş arkadaşlarından yola çıkarak yazmak istedim.</p><h3>Gerçek Scrum</h3><p>Öncelikle scrum nasıl işleniyor, anlatayım.</p><p>Her 3 haftada bir yeni bir sprint koşuluyor. Sprint, bir planlama ile başlayıp, review ile sona eriyor. Ara süreçlerde;</p><ul><li>Her pazartesi bir refinement bulunuyor.</li><li>Her sabah bir daily bulunuyor.</li></ul><p>Bunun haricinde sprint review sonrası 2 aşamalı bir Planning süreci ve bir together-session yapılıyor.</p><ul><li>Planning 1: Herkesin sonraki sprint katkıda bulunabileceği günlerin hesabı</li><li>Think-Debate-Code: Alınacak yeniliğin, tartışılarak alt işlere parçalanması seansı</li><li>Planning 2: Hesaplanmış günlere göre iş yükü alınması</li></ul><blockquote>Ee, iyi de bu bizde de aynı(benzer) durumda</blockquote><p>Önemli olan bu toplantıların organizasyonu. Örneğin bir projede yer alan“<strong>proje dökümanında</strong>”(kod dökümanından farklı) bütün konsept kuralları yer alıyor. Örneğin:</p><p><strong>Refinement</strong></p><ul><li><strong>Süre</strong>: 60 dakika</li><li><strong>Yürüten kişi</strong>: Scrum Manager (isim: XYZ ABC)</li><li><strong>Konuların İşlenmesi: </strong>( a) hangi konuların tartışılacağı — 5dk, konuları tartışma — 55dk(konu başına 7 dakika maksimum))</li><li><strong>Toplantı Notlarını Tutacak Kişi:</strong> XYZ ABC</li><li><strong>Inputs</strong>: Değerlendirilmemiş hikayeler</li><li><strong>Çıktılar</strong>: Gerekliliklerin belirlenmesi, engelleyici konular, açık sorular, tahminler(Fibonacci ile)</li></ul><p>Uygulanması ise yazılana göre ilerliyor.</p><blockquote>Toplantının uzayacağı anlaşıldığında devamı için başka bir güne kısa bir toplantı ayarlanıyor.</blockquote><p><strong>Ayrıca</strong>:</p><ul><li>Bahsedilenler dışı bir konu konuşulmuyor(gereksiz sohbet edilmiyor)</li><li>Herkesin işi belli, kimse başkasının işini üstlenmeye kalkmıyor.</li><li>Toplantıya dair notlar, o gün içerisinde not yazılarak <strong>proje dökümanı</strong> içindeki yerine <strong>XYZ</strong> <strong>Tarih-Toplantı</strong> şeklinde yazılıyor, gelemeyen kişi bilgisiz kalmıyor.</li></ul><h3>Detaylı Döküman</h3><p>Evet, bir confluence sayfası ve alt sayfaları olduğunu fark etmiş olabilirsiniz. Peki neler döküman ediliyor?</p><pre># Proje Yönetimi Dökümantasyon Yapısı<br>## 1. Proje Yönetimi<br>- Takım<br>- Nasıl Çalışıyoruz<br>- İlk Aşama Hedef Yolu<br>- Handover  <br>  - Kişi tatile çıkma durumunda elindeki işlerin özetini yazar.  <br>  - Yerine bakacak veya kendisine durum soracak kişiye bilgi verir.<br>## 2. İhtiyaç Mühendisliği<br>- Özellikler  <br>  - Konunun özeti  <br>  - İstenilenler  <br>  - Tasarımlar  <br>  - Tartışma Alanı  <br>    - Soru-cevap üzerinden konuların netleştiği yorum bölümü<br>- Arayüz Elemanları  <br>  - Butonlar, metinler, özel carousel görünümü vb.  <br>  - Her özellikte bulunan tüm detaylar  <br>  - İlgili tartışmalar ve yorumlar<br>## 3. Toplantı Notları<br>- Örnek: `2025-xy-za Kickoff`<br>- Her toplantının amacı, katılımcılar, alınan kararlar ve aksiyon maddeleri<br>## 4. Proje Inputs<br>- Test kullanıcı hesapları<br>- Gerekli erişim bilgileri<br>- Çevresel değişkenler (örn. staging, dev ortam URL’leri)<br>## 5. Teslimat<br>- Scrum Yönetimi  <br>  - Toplantı düzen detayları (daily, review, retrospective vb.)<br>- Geliştirme Süreçleri  <br>  - Branch stratejileri  <br>  - Deployment URL’leri (örn. `xyz.com/releases`)<br>- Quality Assurance  <br>  - Testçilerin nasıl test yaptıklarına dair dokümantasyon  <br>  - Senaryolar, test raporları<br>- Sprint  <br>  - Sprint 1  <br>  - Sprint 2  <br>  - Sprint 3 …  <br>- Takımlar  <br>  - Takım X  <br>  - Takım Y<br>## 6. Yeni Takım Üyesini Onboard Etme<br>- Şirket kültürü ve süreçlere dair genel bilgilendirme  <br>- Kullanılan araçlar (Jira, GitLab, Slack, Notion vb.)  <br>- Proje yapısı ve çalışma biçimi  <br>- İlk görev planı (mentorluk, ilk sprint katılımı)<br>## 7. Jira Taktikleri<br>- Etiketleme ve issue kategorileri  <br>- Sprint planlama ipuçları  <br>- Story point tahmin yöntemleri  <br>- Task’lerin parçalanması ve önceliklendirme  <br>- Workflow kullanım rehberi<br>## 8. İletişimler<br>- Slack / Teams kanal kuralları  <br>- Toplantı frekansları  <br>- E-posta ve Jira yorumlarında iletişim dili  <br>- Takım içi ve dışı bilgilendirme süreçleri</pre><p>Bu yapı içerisinde her türlü dökümanın gideceği konum belli olduğundan karışan bir durum oluşmuyor. Ayrıca bir karara dair referans gösterilmek istendiğinde, burada kolayca bulunabiliyor.</p><h3>Mesai Kültürü ve Çalışma Disiplini</h3><p>Yapılan iş için müşteriye fatura kesildiği için ek mesaiyi unutabilirsiniz. Keza kendileri de tercih etmiyor.</p><blockquote>Bu saatte online ne işin var, hiç ekstra düşünme, bilgisayarı kapat ve eşinle keyifli bir akşam/hafta sonu geçir.</blockquote><p>Bu cümle, gece 4&#39;e kadar mesai yapmış biri olarak kendi isteğimle akşam 8&#39;de Teams’i açınca iş arkadaşım tarafından yollanmış bir mesajdı.</p><p>Almanların mesai kültürü standart 9:00–18:00(12:00–13:00 arası ara ile). Bu saatler dışında sizlerden bir beklentisi bulunmuyor.</p><ul><li>Bir iş son güne kadar uzadı mı? — Son günü haftaya erteliyorlar.</li><li>Elindeki iş eklediğinden uzun mu sürüyor? — Yardım etmeyi teklif ediyorlar, olmadığı durumda işi bir sonraki sprint’e atıyorlar, ya da iş ile alakalı arkadaşlarından teknik destek istiyorlar. (Neden yapamadın, geceye kadar otur hallet, konuyu başkasına verelim sen yapma gibi tavırlar bulunmuyor)</li></ul><p>Temelinde her işin süresini başında sen belirliyorsun, çok abartı veya çok kısa ise durumuna göre(ekibe yeni girdin ve koda yabancısın örneği) kabul ediyorlar veya daha uzatmanı öneriyorlar.</p><blockquote>Bu hafta işlere verdiğim tüm saat önerilerini “android tarafı daha uzun zaman vermiş, sen de uzat” diye arttırmışlardı.</blockquote><p>Peki bu uzatmalar ve rahatlık projenin gidişini bozmuyor mu?</p><p>Hayır. Bunun en büyük sebebi projenin genel gidişine dair tahminlerde bulunurken her zaman +- 3 haftaya kadar süre koymaları. Keza bir projemizin sürümü, Apple tarafında yaşanan değişikliklerden ötürü 1 ay ertelenmişti.</p><p><strong>Müşterilere bu güven nereden geliyor?</strong></p><p>Bir müşteri gözünden bakıldığında gecikme süreleri endişe verici olabilir. Buradaki sır Scrum içerisinde yürüyen Review toplantıları ve düzenli raporlama.</p><p>Öncelikle çok detaylı olmamakla birlikte günlük yapılan işler saatleri ile sisteme kaydoluyor.</p><blockquote>“ABC-202 Dökümana kod kuralları maddesi ekleme” gibi.</blockquote><p>Daha sonra bu raporlar aylık olarak sistemden alınıp, müşteriye aktarılıyor ve onaylandığı zaman fatura kesilerek işe devam ediliyor. Ayrıca, bu 1 aylık süre içerisinde 1–2 kez proje yöneticileri(geliştiriciler değil) müşteri firma temsilcileri ile bir araya gelerek gelişmeler, sürecin durumu ve scrum tablosunun durumu gibi bilgileri aktararak müşteriyi düzenli olarak bilgide tutuyor. Bu sayede müşteri, ekibin hangi zamanlarda ne iş yaptığını detaylı olarak biliyor.</p><h3>Test Eksik Bırakılmıyor</h3><p>Projelerin çoğu <strong>%70 total Unit test </strong>oranını şart koşuyor.</p><p>Bu koşula uyum sağlamak için yazılan her iş mantık kodu, her yeni özellik ve iyileştirme adına yazılan kodlar, yanlarında unit test de bulundurmak zorunda. Bu yapılan işi ilk etapta uzatıyor olsa da, eksik bırakılmasından doğacak pek çok hatayı önlüyor.</p><p>Uygulamaların kontrol edildiği ve deploy edilen beta sürümleri testçilere düzenli gönderiliyor ve uygulama kontrollerden de geçiyor.</p><p>Ayrıca pipeline bu testleri düzenli kontrol ederek, açılan PR test yüzdesinden fazla saptığında(yani test yazılmadığında ya da çok az yazıldığında) buna dair uyarıyı dile getiriyor ve bu durumda kod birleştirilmiyor.</p><p>Bir PR merge olduğu zaman snapshot build de çıkarılıyor ve testçiler bunun kontrollerini de sağlıyor. İnceleme yaparken emin olamadıkları noktada proje dokümantasyonunda işe dair tanımlanan yükümlülüklere bakarak yerine getirilmeyen noktaları sebep göstererek hata kaydı açabiliyor.</p><p>Normal Unit Testlerin yanısıra, projede uyulması gereken kurallara dair ADR(Architecture Design Records) da bulunuyor. Bu kurallar gerektiğinde değişiyor, ve uygulamanın kökünde yazılan <strong>Harmonize</strong> testleri ile projede bu kodlamaya uyulup uyulmadığına dair test koşuluyor.</p><blockquote>Örneğin; `import FeatureToggle` kodu yalnızda `Feature` modüllerinde kullanılabilir, aksinde hata verilir.</blockquote><p>Bu süreçler sayesinde uygulamanın develop ortamında bile hatasız bir şekilde ilerlemesi sağlanabiliyor(API istekleri hariç, onlar uygulamayı çökertmeyen türde hata verebilir).</p><h3>Neden</h3><p>Bu noktada sıkılıp soracağınız soru şu olabilir?</p><blockquote>Neden bütün bunlarla uğraşıyorsunuz, bunlarla uğraşmasanız o uygulama 3 ayda çıkar, kalan zamanda da iyileştirme yaparsınız, ne saçma?!</blockquote><p>Haklı olabilirsiniz. Keza bu proje kurulumları ve detayları da zaman alan işler. Fakat, Türkiye’de ortalama geliştirilen projelere bakıp, görmezden geldiğiniz absürd çökmeleri ve hataları umursamadığınızda bu durum doğru oluyor.</p><p>Testleri önemsemediğiniz her sürüm günün sonunda testçiye ek yük ve hata yaptırıyor, son kullanıcıya kadar da geliyor.</p><p>Mesaileri önemsemediğiniz her çalışma çalışma arkadaşlarınızı uzun periyotta yoruyor, ayrılmasına sebep oluyor ve yerine alacağınız kişinin alışma süresine ödeyeceğiniz ek maliyetler olarak geri dönüyor.</p><p>Proje dökümanını önemsemediğiniz her senaryoda, işi bilen insanlar ayrıldığında bir bilgi kaybına uğruyor, yeni gelen kişilerin aylarca bu bilgilere erişememesinden sorumlu oluyorsunuz.</p><p>Özetle;</p><blockquote>Ben cuma akşamı 5&#39;te açtığım sohbet kanalında keyifle havadan sudan konuşurken, siz aynı saatte açıp geceye kadar kapatamadığınız sohbet gurubunda son dakika çıkan sorunu anlamaya ve çözmeye uğraşıyorsunuz.</blockquote><p>Bu yazıda genel olarak Türkiye’deki yazılım geliştirme süreçlerinin yabancılarca nasıl ele alındığını ve bu yöntemin kendilerinde nasıl çıktılar ürettiğini göstererek Türk şirketlerindeki proje yöneticilerine ve yöneticilere açık çek sunmuş, geliştiricilere de her retro toplantısında “biz neden bunu yapamıyoruz” sorusuna binlerce cevap üretmemeniz adına yöneticilerinize sunabileceğiniz bir takım tavsiyelerde bulunmuş oluyorum. Türkiye’de bu süreçleri hala normal bulan kişilere son sözümü söyleyerek kapatıyorum.</p><ul><li>Bir banka uygulamasında 1.234,56 TL yapıştırıldığında 1,23456 olarak işlemeye çalışıp 1,23 olarak kırpmanız kabul edilebilir değil.</li><li>Bir uygulamada “not_translated_yet” metnini son kullanıcıya göstermeniz normal değil.</li><li>Bir projede çalışanları gece 4&#39;e kadar çalıştırıp sonraki gün 9&#39;da beklemeniz normal değil.</li><li>Verimini sömürdüğünüz yazılımcıları verimsizlikle suçlamanız adil değil.</li><li>Yazılımcınız uygulamanın analiz raporlarını, proje sunumunu ve iş planlamasını yürütme görevlerinden sorumlu değil. Proje/Ürün yöneticisi, Testçi ve Analiz görevlerinin bir sebebi var.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=60be70052d2b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Is 2025 the Year for Linux?]]></title>
            <link>https://egesucu.medium.com/is-2025-the-year-for-linux-4021e59c8c03?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/4021e59c8c03</guid>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[ubuntu]]></category>
            <category><![CDATA[gnome]]></category>
            <category><![CDATA[operating-systems]]></category>
            <category><![CDATA[linux-gaming]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Tue, 01 Jul 2025 17:51:41 GMT</pubDate>
            <atom:updated>2025-07-01T17:51:41.120Z</atom:updated>
            <content:encoded><![CDATA[<h4>It’s not.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kVV0fIWPQAuK8PT7" /><figcaption>Photo by <a href="https://unsplash.com/@6heinz3r?utm_source=medium&amp;utm_medium=referral">Gabriel Heinzer</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>I’ve been a follower of Linux way before I became a software developer. I was daily driving Ubuntu, Mint, Pardus(Turkish Linux distro), &amp; Fedora previously. I know Ubuntu all the way from 14.04 up until 25.04. While Linux is progressing year by year, thanks to the open source contributors &amp; passionate Linus Torvalds who still write today, the general progress is a slow one.</p><p>What made me convince to write about this is the fact that Linux oriented promotions &amp; advertisements seem to grow more than before. We are hearing that governments implementing Linux conversion, governments trying to rely on open source softwares &amp; EU moving on Open Source Solutions on nation-wise to YouTubers like PewDiePie switching and recommending Linux.</p><p>So, let’s discuss the year 2025 and if Linux really can throw some fears into closed systems such as Apple, Microsoft, Adobe and others.</p><h3><strong>Reason(s)</strong></h3><p>There are some reasons why individuals and organizations may want to switch into Linux &amp; open source softwares at this point.</p><ul><li>Microsoft increasing its support &amp; subscription services higher than anticipated.</li><li>Windows 11 being more “ad-oriented” than prior releases</li><li>Microsoft forcing users to Windows 11 by deprecating the Windows 10.</li><li>Increased number of AI additions which doesn’t help users but distract and push them into subscriptions.</li><li>Dominated apps such as Adobe softwares, bundles and charges their costs high, also making the cancellation process harder by implementing early cancellation fees.</li><li>Tension over US-based companies and their monopoly on their software standards &amp; support.</li></ul><p>As you see, on both sides there are fundamental issues which forwards users to think about the switch. But why now?</p><p>On institute side, we hear countries like <a href="https://www.windowscentral.com/software-apps/windows-11/denmark-will-stick-with-windows-government-still-plans-to-ditch-microsoft-office">Denmark ditching Office 365 solutions</a> in favor of LibreOffice &amp; OpenExchange solutions. Or France worked silently to adapt Linux distro on its policing systems.</p><p>On personal side, we see that O.G YouTubers like PewDiePie recommending Linux, open source solutions &amp; even De-Google the everyday tool he’s using. Linux forums on Reddit has an increased posts than before, and people have started to talk open source solutions more as centralized solutions have issues for them such as being blocked/suspended/limited/ad-filled solutions.</p><blockquote>So, how is Linux?</blockquote><h3><strong>How’s it going Linux?</strong></h3><p>During the writing of this article, we have Ubuntu 25.04 released, Gnome 48 is available, KDE Plasma 6.4 is here and Linux Kernel is on 6.15.4 is there. Does this mean anything to you? I don’t think so too.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hKLcRX3RyhWa4-dZ.jpg" /><figcaption>Ubuntu 25.04</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Z7r8p5OGab_MsRG7.jpeg" /><figcaption>Gnome 48</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PA81v4Ly4HCxc-ss.png" /><figcaption>KDE Plasma 6.4</figcaption></figure><p>Linux has been improving since years thanks to the open source contributions from all over the world. It works better on multiple PCs and notebooks.</p><p>One weak point is that the arm architecture support is not on all linux distributions and you need to check if your OS supports in case you have an arm notebook.</p><p>Apart from that, the memory requirement of the OS is still 4GB, although I will recommend a 8GB or more. Space is still at 25MB approx. a nice 80GB space will be sufficient for basic usage, and these days, computers are more than 128GB anyway if a full installation is considered.</p><p>Gaming side, Valve’s contributions have been helpful to make some people switch into Linux and We are not in the level of Wine software on Linux in 2014, it’s been enhanced a lot.</p><p>Tools like LibreOffice has been evolved and more compatible with Office file schemas than before. Usability of LibreOffice on all platforms also help people to move their stuff between Operating systems.</p><h3><strong>Can Linux become the trend in 2025?</strong></h3><p>Yeah, we came into the question readers could wonder. Can people begin to switch and we call this year “The year of Linux”?</p><blockquote><strong>No</strong>.</blockquote><p>While the OS and the platform grown up so much by bringing favorite apps via new package systems(flatpak, snaps, appimage etc.) people still are not 100% motivated to bother even with the installation process.</p><p>You still require to download the ISO file to burn into a USB to use it as a installer and then decide to dual-boot or full install to continue using their systems with an operating system which they are not used to it, will require some terminal work for 20% of the operations(it used to be 80–90%, that’s honestly better).</p><p>The problem with a mass is that their daily usage of subscriptions, apps and gestures/keyboard shortcuts are generic, they will not like a complete change.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*GBw4aJChqU4jJDhw" /><figcaption>People picturing when you mention Linux — Hyprland Config</figcaption></figure><p>While I’m expecting some people to switch and increase the overall usage, the trend in the enterprise &amp; personal side will likely stay same for a while. I’m not expecting a massive change in the next 10 years if something big has not happen(something like Apple &amp; Microsoft deciding to put ad on every single pixel of the screen).</p><p>Main Reasons people would not bother switching:</p><ul><li>UI is still behind from other platforms</li><li>Setup is still confusing for some(why distro owners are afraid of putting a script to run the usb-image-burning-process)?</li><li>Some frequently-used softwares have no place in Linux, and people would not bother switching that easily(There’s a reason why there are migration tools like Apple Music → Spotify which earns one-time fee)</li></ul><p>In conclusion, Linux distributions are still a “power user” operating systems. For a standard user to be frequent with it requires an early education or company policy to switch people into them. I hope for the future that this will be more adapted, have a better place on the OS distrubition charts and motivate the giants to think more of the users rather than their revenues. In the end, software is the bridge between a hardware(product) &amp; person. If you make an AI bloated Spam solutions, user will search for other connections. Remember browser menu bars? That disaster was the end of IE for many users and almost caused Firefox being disaster too… wait, Firefox is having similar issues with Privacy contradictions? Oh well…</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4021e59c8c03" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What iOS Apps do Wrong?]]></title>
            <link>https://egesucu.medium.com/what-ios-apps-do-wrong-3c75456cbea7?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/3c75456cbea7</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[software-failure]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-development]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Sat, 31 May 2025 19:08:11 GMT</pubDate>
            <atom:updated>2025-05-31T19:08:38.497Z</atom:updated>
            <content:encoded><![CDATA[<h4>Why users are seeing more and more failures these days?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*LzcE0bUqxDRFo8DX" /><figcaption>Photo by <a href="https://unsplash.com/@jstrippa?utm_source=medium&amp;utm_medium=referral">James Harrison</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>While App Store has seen amazing apps over the years, there are also apps which shows issues you as a user should never see. In this article, I’ll show you some aspects of the errors and how you can improve them on your apps when working on.</p><h3>Handling Errors</h3><p>You may seen some screens like this, time to time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/225/1*jxxutYRgit1q6nqcX3J7oA.png" /></figure><p>When you develop an app, you might expect errors you don’t know how to deal with. This could cause out of:</p><ul><li>Not talking through</li><li>Not planning scenarios</li><li>Depending on another source’s error handling</li></ul><p>On those cases, you need to remember that you don’t have to present everything you get from your source. While <strong>error.localizedDescription </strong>looks good, it wouldn’t give you enough solutions all the time.</p><p>Instead, handle your UI to provide the user enough errors they should know, and keep the details to you.</p><pre>import Foundation<br>import OSLog<br><br>enum KnownErrors: Error, CustomStringConvertible {<br>    case invalidData<br>    case serverError<br>    case networkError<br>    case unknown(String)<br>    <br>    var description: String {<br>        switch self {<br>        case .invalidData:<br>            return &quot;Invalid data received from server&quot;<br>        case .serverError:<br>            return &quot;Server returned an error&quot;<br>        case .networkError:<br>            return &quot;Network Error&quot;<br>        case .unknown(let string):<br>            return string<br>        }<br>    }<br>}<br><br>let error = KnownErrors.invalidData<br>let logger = Logger()<br><br>func handleData(_ data: Data) {<br>    // some handlings here...<br>    switch error {<br>    case .invalidData:<br>        handleError(message: error.localizedDescription)<br>    case .serverError:<br>        handleError(message: error.localizedDescription)<br>    case .networkError:<br>        handleError(message: error.localizedDescription)<br>    case .unknown(let string):<br>        handleError(message: &quot;There is an unexpected error occurred. Please try again later, or send us a feedback at support@example.com.&quot;)<br>        // Notice how I did not show the actual error on UI, but kept in our logging.<br>        logger.error(&quot;Unexpected error occurred: \(string)&quot;)<br>    }<br>}<br><br>func handleError(message: String) {<br>    // Some UI operations to show Error<br>}</pre><p>With this approach, your user will never see an error you did not expect to show, but you can still catch them and get a log(optionally make the users send one to you). This is especially useful if you get your errors from backend and they might introduce a new error type which you:</p><ul><li>implemented but your users from older versions are still going to see them</li><li>not implemented and now your users see this.</li></ul><p>If you want to further get help from your users without sharing any logs, you can also create error codes, and only provide error codes(MA324 as MyApp) inside of the “unknown” case, so that your users may contact you without requiring to send/collect a log to indicate which error they got.</p><h3>Defining the Namings</h3><p>When you enter a project, it can be tough to get familiar with it. When I joined our scout talent app <a href="https://www.linkedin.com/company/cuju-app/">CUJU</a>, as a not football fan, the most frustrating part of the app was the definitions. What’s a promoter? You might say it’s a role type who manages the young talents. But on the app, it might sometimes mean “Coach”, sometimes “promotee” sometimes “Promoter”. This is not the fault of the previous developers, but something that’s not been discussed enough in the beginning.</p><p>This is not only project’s fault either. I’ve seen this ambiguous namings over many apps I’ve worked on before. The problem lies on how namings should be decided. Let’s take an example:</p><p>The project is a simple todo app which supports many types of attachments on itself. Your first instinct should be to define what’s a todo:</p><ul><li>A type that has a title</li><li>has an optional description</li><li>has sub-items</li><li>may have a date</li><li>may have a deadline</li><li>may have an attachment</li></ul><p>So instantly, your models could look like this:</p><pre>import Foundation<br><br>struct Todo: Identifiable {<br>    let id = UUID()<br>    let createdAt: Date<br>    let title: String<br>    let content: String?<br>    let date: Date?<br>    let attachment: Attachment?<br>    let deadline: Date?<br>    let tasks: [SubTasks]<br>    let priority: Priority = .mid<br>}<br><br>enum Priority {<br>    case low, mid, high, highest<br>}<br><br>enum Attachment {<br>    case image(imageData: Data)<br>    case file(url: URL)<br>    case video(videoData: Data)<br>}<br><br>struct SubTasks {<br>    let title: String<br>}</pre><p>The point of it is to keep your namings simple but clarified as possible. You may overrate this and name something like “ESTodo”, came from Ege Sucu Todo which is pretty common on indie apps(or PROJTodo on corporate apps), but it doesn’t define the need of it. Why did we create it? Why did we simply not used the default model for it.</p><p>In those cases, a short documentation to explain the content and how it’s been used is more useful.</p><pre>/// This model has been created to support our TODO items to be transformed into desired output easily.<br>class ESTemple {<br>    /// Converts the content into PDF by defined operations.<br>    /// - Parameter desiredFormat: Format type as given, accepted ones are &quot;pdf&quot;, &quot;md&quot; &amp; &quot;txt&quot;<br>    func convertToPDF(<br>        desiredFormat: String = &quot;PDF&quot;<br>    ) {<br>        // Some Convert Logic occurred.<br>    }<br>}</pre><p>While this will create some extra time for you, it will, in return, create less time for your colleague’s onboarding.</p><h3>Lack of Testing &amp; Documenting</h3><p>For many projects, documentation lies on the developer who have been worked for the most of the time. Details such as “which architecture should be used”, “which colors do the app contain”, “Which screens do we have” left on the air when that developer is busy, not available or left the company all together. This happened on a banking app where 70% of the devs are left for other companies, leaving the newcomers a big app without any documentation, resulting creation of duplicate view components, mixed architecture choices and non-matching views between pages.</p><p>A documentation is both helpful for you and your future colleagues. Lack of the documentation on anywhere(on the code, on documentation places like Confluence) results higher research/onboard process for any devs, and creates unnecessary short meetings between your colleagues, or with the management to ask details.</p><p>The worse case for a developer is to be asked about a feature decision which results looking for the code changes, git blame to find devs who did it, and the research of asking wised employees about it. Happens too much.</p><p>Testing is another problem. On indie apps, testing might be nowhere and the 1.0 version of the app might upset many users.</p><p>The problem is simple. You as a developer have one environment. A 13&quot; M3 Macbook Air, an iPhone 14 Plus on iOS 18.4. But your users are on different environments. There are tons of devices, many sizes, different modes(dark, light, auto, high contrast etc.) If you want your apps to be heard more by people, you need to care about them too. Does your colors look nice together in Dark-light mode? Does your app support dynamic fonts?</p><p>Those things can be seen only by one option.</p><blockquote>Testing them one by one</blockquote><p>Luckily, your working environment has many testing options to show you this, whether it’s Simulator, or a 3rd party testing framework. This even applies to the corporate projects which surprise me whenever I came across them.</p><blockquote>There are less unit tests than the amount of app assets.</blockquote><p>App security and maintenance are seen as cost for most of the projects, hence the process of unit tests are not cared until some manager turns on the importance of it and creates week long writing process and bashes developers on “how it was never done before”. <strong>While 1.0 is a first release of an app, the real big next release is when the app is secured by tests &amp; security measures, not the big changes.</strong></p><h3>Not following the Software Features</h3><p>This is my biggest nit on developers. Apple doesn’t create WWDC for the show. There’s a reason why apps like Flighty, Halide, WaterMinder, Carrot, Things 3 &amp; others are successful. Not just because of their UI’s, but also their feature adaption sets. On every iOS release, they release their new versions with using Apple’s latest changes, offering fresh experiences to their users.</p><p>While they’re big apps, it doesn’t mean for you as a developer to just implement all new features of iOS. <strong>Do you support Swift 6?</strong> It’s been <strong>one year</strong> since Apple made it available. Do you even support Swift Concurrency? That was available since <strong>Swift 5.5</strong>, which is released in…<strong>2021</strong>. These little changes help your app to keep in updated.</p><p>There are probably many extensions you have written which is already been solved by Swift, like <strong>Date.now</strong> came in <strong>iOS 15</strong>, replaced the old static vars written. Or, <a href="https://developer.apple.com/documentation/foundation/formatstyle">FormatStyle API</a> which replaced most of your Date/Number/String formatting by dot endpoints like:</p><pre>let rounded = 123456789.formatted( .number<br>    .rounded(rule: .down, increment: 1000)<br>    .locale(Locale(identifier: &quot;fr_FR&quot;))) // &quot;123 456 000&quot;</pre><p>Measurement extensions, <strong>feet</strong> to <strong>cm</strong> converters. <strong>Why</strong>? We already have an <a href="https://www.hackingwithswift.com/example-code/system/how-to-convert-units-using-unit-and-measurement">API</a> from Apple for this since <strong>iOS 10</strong>… These things are not easy to find, but if you are curious, and love to dig deep, you’ll see that a <strong>8 years old project has many obsolete extensions/custom components written and still left.</strong></p><p>I like how on every minimum iOS Target increase, we remove one custom SwiftUI view in favor of the introduced with the new iOS version.</p><p>While some of those points are known by the developers, but looked upon, these are essentially why your apps, and the apps on the store, are failing. While there are other aspects to take care of such as ASO optimizations, marketing strategies etc., these problems can not hinder the success of those steps, so you still need to care about those points to have an app which can handle non-expected operations like you would like it to be.</p><p>Happy coding &amp; see you in the WWDC week 💻</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3c75456cbea7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Road to Senior: What iOS Developers do wrong?]]></title>
            <link>https://egesucu.medium.com/road-to-senior-what-ios-developers-do-wrong-56321f06a473?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/56321f06a473</guid>
            <category><![CDATA[development-and-growth]]></category>
            <category><![CDATA[career-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[improvement]]></category>
            <category><![CDATA[programmer]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Fri, 02 May 2025 08:32:07 GMT</pubDate>
            <atom:updated>2025-05-02T08:32:07.856Z</atom:updated>
            <content:encoded><![CDATA[<h4>Senior is not a title, it’s a responsibility.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GML01Fmce-83kPr_QKKM_Q.png" /><figcaption>Created with ChatGPT</figcaption></figure><p>Recently, I’ve promoted as a Senior iOS developer at my job after coding Swift for 8 years. You might say it’s a late promotion, since many expect a shorter term to become senior. The truth is, I had the same mind, but realized other aspects that lacked me to become one. Working with colleagues, and the past colleagues I’ve worked with, I wanted to create this article to guide people to level up in their game. While the advantages might not be only valid for iOS development, I wanted to give a focus on my community over this topic.</p><p>If you’re ready, let’s start with the first point.</p><h3>Rushing without Searching</h3><p>We all want to earn more, become more knowledgeable, be a TED talker, possibly work at Apple at some point or become an individual, hitting 100M$ MRR…</p><blockquote>Right?</blockquote><p>While this is a fantastic idea, came on my mind multiple times, people always forget what the title is for.</p><blockquote>People often focus on the title where instead, they need to focus what knowledge the title contains.</blockquote><p>When I was asking for being Senior, I often stopped myself on points I didn’t know.</p><blockquote>I possibly couldn’t become a senior without knowing this.</blockquote><p>And I was right. Being senior means that you need to have the knowledge about the aspects of an app.</p><ul><li>How does it send/retrieve the data?</li><li>How much time requests cost, can you minimize that?</li><li>How the security applies on the app?</li><li>How do you test the app, is it automated?</li><li>Do you know about some algorithms to help you out?</li><li><strong>Do you know how the code you’ve written works?</strong></li></ul><p>The latter was important even when you’re switching from junior to mid tier developer. Previously, we would see many <strong>StackOverflow</strong> code was pasted without the developer understanding the code, now it’s <strong>AI</strong> <strong>tools</strong>.</p><p>While AI tools are very beneficial on solving the problems and essentially helping you(not replacing), it’s important that you need to understand that help to how it solves your problem, how the code proceeds instead of copy-pasta(or auto implementation). It’s not going to solve your problem otherwise, and even it will break your project if you solely rely on copy-paste codes.</p><p>Other aspects I have mentioned will have the same step as this, it’s to understand the concepts of the app and how secure/strong your app can be. We have millions of apps on the App Store, many thousands are doing the same. Yet, the ones who took care of these steps carefully can reach the customer and make it stay, while other apps are famous by their 1 star reviews(even after eliminating out-of-value critics). You might think that, in a company, this wouldn’t be important since the value is already present.</p><p>You’ll be wrong. That’s what one of the things your manager looks for and you being evaluated.</p><p>For the indie developers, those points especially matter. Since it’s the lack of the people that gives indie developers hard time on developing or maintaining an app after the development. Even if you’re a team of one, being organized and checking every aspect of your app matters for the customer retain.</p><h3>Out of your Scope</h3><p>When we think of a mobile developer, the concern is usually:</p><ul><li>Be consistent on your code</li><li>Develop features, apply improvements and fix the damn bugs…</li><li>Log your days</li><li>Be happy</li></ul><p>While that’s true, sometimes you need to come out of your scope a little to help your teammates on the project to evolve.</p><ul><li>Giving a UI bug to your designer as a feedback is a ➕</li><li>Managing the Pipeline to make sure the app works fine is a ➕if you don’t have a DevOps on the team.</li><li>Suggesting new tasks, improvements on the description and gather with the mobile to plan ahead is a ➕ when working with a product owner since not all of them can understand the tech, or the importance of the job there.</li><li>Suggesting new ideas, features and showing extra bugs you found with the team helps the app become stronger, and so do you.</li><li>Suggesting alternative tools and coding techniques help your team to be innovative.</li></ul><p>These suggestions might seem to benefit your team, but those are also tracked by your managers when evaluating you.</p><h3>Fighting with Issues</h3><p>It’s important for any developers wanting to level themselves up to face the fact.</p><p>We all encounter problems. No developer is perfect, nor they or the system they use can be. And that’s fine.</p><p>While this is a general thing everyone has to deal with, the difference between a senior developer and a junior/mid one lies on how-to.</p><p>While a junior developer might pass the issue on it’s superiors, or lie through the “non-replication of the problem”, and a mid developer working on multiple days without defining the steps they’ve introduced, a senior developer must look for the root cause of the problem.</p><ul><li>Where is the root cause of the issue?</li><li>Can I fix it via adjustments, or is it a system failure in which I could find a workaround for it?</li><li>Should I ask others opinions?</li><li>Should I make myself vulnerable by asking that?</li><li>Should I rely on ChatGPT solution to apply without thinking much?</li></ul><p>Those questions are important to consider and valid for years. People tend to question their ability to fix that, so they hide. However, this doesn’t help anyone, themselves too. There’s a reason why you work with a team and not by yourself as a developer. The reality is:</p><blockquote>There is no silly question, but there’s a silly outcome when you don’t ask for it.</blockquote><p>This include prolonging a problem with days, undermining a workaround which breaks other part of your code and space between your colleagues when you want to help them later.</p><p>So, instead, feel free to ask everything. Discuss the possibilities, ask further when you don’t understand their answers. Every sensible developer is favoring couple minutes with 1–1 to answer questions. It’s not a 8 hour full job logging only.</p><p>Even when a problem is occurring on the system level, you can apply a workaround for it, such as warning the user about the inconsistency related to that issue, hiding that view for a time, using alternative components to mimic the approach and such other methods exist on many apps. As an example, SwiftUI did not have good UI components for Alert Controllers for a while(the current solution is still not good), so we wrote custom views instead previously.</p><h3>Using the AI tools</h3><p>We all have an AI tool we are using from time to time. It’s important however, how you’re using it. For most of the juniors, you’re using it wrong.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Ftenor.com%2Fembed%2F20431174&amp;display_name=Tenor&amp;url=https%3A%2F%2Ftenor.com%2Ftr%2Fview%2Fyoure-doing-it-wrong-saturday-night-live-weekend-update-youre-doing-the-wrong-thing-youre-not-doing-it-correctly-gif-20431174&amp;image=https%3A%2F%2Fmedia.tenor.com%2FcgwIa3SJMgsAAAAN%2Fyoure-doing-it-wrong-saturday-night-live.png&amp;type=text%2Fhtml&amp;schema=tenor" width="600" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/efba468350afb47d567e14bfa2f33325/href">https://medium.com/media/efba468350afb47d567e14bfa2f33325/href</a></iframe><p>People are relying on the AI tools to write things for themselves, where they don’t bother with how that code constructs. How does it work, why does it work etc.</p><p>Before the age of Internet, people used to have Bibliotheca to check for the information. Newspapers were selling those with coupons, libraries obtain them so people were visiting museums to get their answers, help their thesis etc. With the Internet age, people started to get lazy around the knowledge they seek for, while the technology offers them the ease of reaching that.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b2X7p3tpy7YrsHodhDQ9SA.png" /><figcaption>from a ChatGPT answer</figcaption></figure><p>It’s similar with the AI tools where a ChatGPT command would tell you what’s the code for and why it matters, but people rarely read them in favor of copy-paste. It’s always beneficial to ask further questions to clarify your knowledge on the topic, so you wouldn’t require gpt again for that problem in the future.</p><h3>Communicating with Other Developers</h3><p>One thing I see less is that the developer who wants to level up for the senior, and does not understand why they can’t, is also the person who doesn’t talk with senior.</p><p><strong>Why would you talk with a senior?</strong></p><p>Because he has the idea why you might be not suited for, can give you advices on the improvement, and can lead you to see how they are operating in the same conditions you operate. Also, showing yourself for wanting to be better is a ➕ for your higher ups to see your motivation too.</p><h3>Creating small Apps</h3><p>People might assume that in order to make an app, or create an article, you need to be an experienced developer.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Ftenor.com%2Fembed%2F23252753&amp;display_name=Tenor&amp;url=https%3A%2F%2Ftenor.com%2Ftr%2Fview%2Ffalse-wrong-fake-incorrect-untrue-gif-23252753&amp;image=https%3A%2F%2Fmedia.tenor.com%2Fz8IxIGwkSo0AAAAN%2Ffalse-wrong.png&amp;type=text%2Fhtml&amp;schema=tenor" width="600" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/5116bd9e7d4644f943c179e103f5b527/href">https://medium.com/media/5116bd9e7d4644f943c179e103f5b527/href</a></iframe><p>When I started to blog about iOS development, I was learning the language fresh, I started blogging in 2018 where I was months old as a personal iOS developer. My app, <a href="https://apps.apple.com/tr/app/pet-reminder/id1313470810?l=tr">Pet Reminder</a> was came out in 2018 too and being developed ever since.</p><p>Writing small apps and articles, helps you to be better about that topic. It also helps you to understand topics you don’t use at work, for the possibility of using them later.</p><p>For example, our work project is not Swift 6, not in async-await fully and only supports iOS 15+, so no NavigationPath, no SwiftData, no Observation really. But my personal app is on iOS 18, have all of them(except a good NavigationPath which I’m thinking off). My work app also has some features that my personal app doesn’t have(modularization, dependency injection etc.), but this is important to understand that, I could be able to have many coding disciplines understood because I worked on it for a big/small app, or wrote a small app for an article on it, to understand better.</p><p>You can’t understand combine without learning and creating content about it. You would have no idea about Xcode 16’s enormous amount of warnings of main actor isolation if you don’t start working on a small swift 6 apps with all feature toggles enabled. That’s how every dev learns it.</p><h3>Conclusion</h3><p>Becoming a higher level developer is not about having the title. I see some friends where they became app manager in 2 years on a small company without proper management. I see many people who have been working for Swift but did not go further to understand the aspects of the app and only focused on what task they have taken from the sprint, so their information is not different someone with 2 years of iOS experience in that manners.</p><p>I used to joke around some of my colleagues that was more experienced than me as a title, but asking me questions about the topics which I used to expect them to know better than me. It’s about being open to learn all the time, improving yourself day by day like productivity gurus do and be the best version of you. And if after all of this, and your company is not recognizing that, there will be other companies to recognize, so you can talk with others to switch too.</p><p>I’m wondering your opinions, comments from people wiser than my thoughts, and newbies who applied their own methods to be better. Let’s meet on the comment section below. Happy coding.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=56321f06a473" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Swift Actors: What Are They For?]]></title>
            <link>https://egesucu.medium.com/swift-actors-what-are-they-for-fd40b4264d9a?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/fd40b4264d9a</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[apple]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[asynchronous-programming]]></category>
            <category><![CDATA[swift-concurrency]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Thu, 17 Apr 2025 19:44:29 GMT</pubDate>
            <atom:updated>2025-04-17T19:44:29.741Z</atom:updated>
            <content:encoded><![CDATA[<h4>No, I’m not talking about `MainActor`.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L0sk7L-iEy6ZaWdZt9Qj5g.png" /></figure><p>An actor is a reference type, like a class, built to manage mutable state safely in a multithreaded environment. Introduced in Swift 5.5, actors help developers write thread-safe and more predictable code when working with asynchronous tasks and shared mutable data.</p><h3>How Are Actors Different from Classes?</h3><p>Actors guarantee that their mutable state is accessed by only one thread at a time. This prevents data corruption and ensures integrity.</p><blockquote><strong><em>Wait, what?</em></strong></blockquote><p>Let’s look at a simple example using a class called Dinner. It has one variable and one method, eat(), which increases the counter of dinners made in a month:</p><pre>class Dinner {<br>    var value = 0<br><br>    func eat() {<br>        value += 1<br>    }<br>}<br><br>let dinner = Dinner()<br><br>// Simulate concurrent access<br>Task {<br>    await withTaskGroup(of: Void.self) { group in<br>        for i in 0..&lt;30 {<br>            if i % 2 == 0 { dinner.value -= 1 }<br>            group.addTask {<br>                dinner.eat() // ⚠️ Not thread-safe!<br>            }<br>        }<br>    }<br>    print(&quot;Final value (class): \(dinner.value)&quot;)<br>}</pre><p>Here, I intentionally added a condition to mess with the counter. While this is a simple example, in a real-world app, more complex cases could cause similar issues. Instead of getting 30, you might end up with 15 or even less. That’s because the value is being accessed and modified from multiple threads at the same time.</p><p>Even if you make ‎`value` private and only change it through a function, you still can’t prevent race conditions in asynchronous code.</p><h3>How Actors Solve This</h3><p>With actors, this problem goes away. You simply can’t access the actor’s internal state directly from outside. The compiler won’t let you</p><pre>actor SafeMeal {<br>    var value = 0<br><br>    func eat() {<br>        value += 1<br>    }<br><br>    func getValue() -&gt; Int {<br>        return value<br>    }<br>}<br><br>let safeMeal = SafeMeal()<br><br>// Safe concurrent access<br>Task {<br>    await withTaskGroup(of: Void.self) { group in<br>        for _ in 0..&lt;30 {<br>            // if i % 2 == 0 { safeMeal.value = 1 } ⚠️ Not allowed, will cause a compile-time error<br>            group.addTask {<br>                await safeMeal.eat() // ✅ Safe access<br>            }<br>        }<br>    }<br>    let finalValue = await safeMeal.getValue()<br>    print(&quot;Final value (actor): \(finalValue)&quot;)<br>}</pre><p>If you try to change the value directly, you’ll get an error like:<br><strong>“Actor-isolated property ‘value’ cannot be mutated from a non-isolated context.”</strong></p><p>To modify the value, you must define a method inside the actor, then call it with ‎<strong>await</strong>:</p><pre>func reduceCount() {<br>    value -= 1<br>}</pre><pre>if i % 2 == 0 {<br>    await safeMeal.reduceCount()<br>}</pre><p>This ensures the actor is accessed safely, one task at a time.</p><h3>A More Advanced Example: Chat Server</h3><pre>// Define a message model<br>struct Message {<br>    let user: String<br>    let content: String<br>    let timestamp: Date<br>}<br><br>// Actor to manage chat history<br>actor ChatServer {<br>    private var messages: [Message] = []<br><br>    func postMessage(from user: String, content: String) {<br>        let message = Message(user: user, content: content, timestamp: Date())<br>        messages.append(message)<br>    }<br><br>    func getMessages() -&gt; [Message] {<br>        return messages.sorted { $0.timestamp &lt; $1.timestamp }<br>    }<br>}<br><br>struct Main {<br>    static func chat() async {<br>        let server = ChatServer()<br>        let users = [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;, &quot;Diana&quot;]<br><br>        print(&quot;Starting to post messages...&quot;)<br><br>        await withTaskGroup(of: Void.self) { group in<br>            for user in users {<br>                group.addTask {<br>                    for i in 1...5 {<br>                        print(&quot;Posting message \(i) from \(user)&quot;)<br>                        await server.postMessage(from: user, content: &quot;Message \(i) from \(user)&quot;)<br>                        try? await Task.sleep(nanoseconds: UInt64.random(in: 10_000_000...50_000_000))<br>                    }<br>                }<br>            }<br>        }<br><br>        print(&quot;Finished posting messages. Retrieving all messages...&quot;)<br><br>        let allMessages = await server.getMessages()<br>        for message in allMessages {<br>            print(&quot;[\(message.timestamp)] \(message.user): \(message.content)&quot;)<br>        }<br><br>        print(&quot;All messages printed.&quot;)<br>    }<br>}</pre><p>In this example, the actor ensures that messages are updated safely, even when multiple users are posting at the same time.</p><pre>Starting to post messages...<br>Posting message 1 from Alice<br>Posting message 1 from Bob<br>Posting message 1 from Charlie<br>Posting message 1 from Diana<br>Posting message 2 from Alice<br>Posting message 2 from Bob<br>Posting message 2 from Diana<br>Posting message 2 from Charlie<br>Posting message 3 from Alice<br>Posting message 3 from Bob<br>Posting message 3 from Charlie<br>Posting message 3 from Diana<br>Posting message 4 from Charlie<br>Posting message 4 from Diana<br>Posting message 4 from Bob<br>Posting message 4 from Alice<br>Posting message 5 from Diana<br>Posting message 5 from Charlie<br>Posting message 5 from Bob<br>Posting message 5 from Alice<br>Finished posting messages. Retrieving all messages...<br>[2025-04-17 19:14:11 +0000] Alice: Message 1 from Alice<br>[2025-04-17 19:14:11 +0000] Bob: Message 1 from Bob<br>[2025-04-17 19:14:11 +0000] Charlie: Message 1 from Charlie<br>[2025-04-17 19:14:11 +0000] Diana: Message 1 from Diana<br>[2025-04-17 19:14:11 +0000] Alice: Message 2 from Alice<br>[2025-04-17 19:14:11 +0000] Bob: Message 2 from Bob<br>[2025-04-17 19:14:11 +0000] Diana: Message 2 from Diana<br>[2025-04-17 19:14:11 +0000] Charlie: Message 2 from Charlie<br>[2025-04-17 19:14:11 +0000] Alice: Message 3 from Alice<br>[2025-04-17 19:14:11 +0000] Bob: Message 3 from Bob<br>[2025-04-17 19:14:11 +0000] Charlie: Message 3 from Charlie<br>[2025-04-17 19:14:11 +0000] Diana: Message 3 from Diana<br>[2025-04-17 19:14:11 +0000] Charlie: Message 4 from Charlie<br>[2025-04-17 19:14:11 +0000] Diana: Message 4 from Diana<br>[2025-04-17 19:14:11 +0000] Bob: Message 4 from Bob<br>[2025-04-17 19:14:11 +0000] Alice: Message 4 from Alice<br>[2025-04-17 19:14:11 +0000] Diana: Message 5 from Diana<br>[2025-04-17 19:14:11 +0000] Charlie: Message 5 from Charlie<br>[2025-04-17 19:14:11 +0000] Bob: Message 5 from Bob<br>[2025-04-17 19:14:11 +0000] Alice: Message 5 from Alice<br>All messages printed.</pre><h3>What If We Used a Class Instead?</h3><p>Now, imagine doing this with a class instead of an actor. With 100 messages per user, things break down:</p><pre>// Message model remains the same<br>class Message {<br>    let user: String<br>    let content: String<br>    let timestamp: Date<br><br>    init(user: String, content: String, timestamp: Date) {<br>        self.user = user<br>        self.content = content<br>        self.timestamp = timestamp<br>    }<br>}<br><br>// Class to manage chat history (NOT thread-safe)<br>class ChatServer {<br>    private var messages: [Message] = []<br><br>    func postMessage(from user: String, content: String) {<br>        let message = Message(user: user, content: content, timestamp: Date())<br>        messages.append(message) // ⚠️ Potential data race<br>    }<br><br>    func getMessages() -&gt; [Message] {<br>        return messages.sorted { $0.timestamp &lt; $1.timestamp }<br>    }<br>}<br><br>// Main simulation<br>struct Main {<br>    static func chat() async {<br>        let server = ChatServer()<br>        let users = [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;, &quot;Diana&quot;]<br><br>        print(&quot;Starting to post messages...&quot;)<br><br>        await withTaskGroup(of: Void.self) { group in<br>            for user in users {<br>                group.addTask {<br>                    for i in 1...100 {<br>                        print(&quot;Posting message \(i) from \(user)&quot;)<br>                        server.postMessage(from: user, content: &quot;Message \(i) from \(user)&quot;)<br>                        try? await Task.sleep(nanoseconds: UInt64.random(in: 10_000_000...50_000_000))<br>                    }<br>                }<br>            }<br>        }<br><br>        print(&quot;Finished posting messages. Retrieving all messages...&quot;)<br><br>        let allMessages = server.getMessages()<br>        for message in allMessages {<br>            print(&quot;[\(message.timestamp)] \(message.user): \(message.content)&quot;)<br>        }<br><br>        print(&quot;All messages printed.&quot;)<br>    }<br>}</pre><p>You’ll see messages posted out of order, or even missing, because multiple threads are modifying the array at the same time.</p><pre>…<br>[2025-04-17 19:20:06 +0000] Bob: Message 98 from Bob<br>[2025-04-17 19:20:06 +0000] Bob: Message 99 from Bob<br>[2025-04-17 19:20:06 +0000] Alice: Message 99 from Alice<br>[2025-04-17 19:20:06 +0000] Diana: Message 94 from Diana<br>[2025-04-17 19:20:06 +0000] Bob: Message 100 from Bob<br>[2025-04-17 19:20:06 +0000] Alice: Message 100 from Alice<br>[2025-04-17 19:20:06 +0000] Diana: Message 95 from Diana<br>[2025-04-17 19:20:06 +0000] Diana: Message 96 from Diana<br>[2025-04-17 19:20:06 +0000] Diana: Message 97 from Diana<br>[2025-04-17 19:20:06 +0000] Diana: Message 98 from Diana<br>[2025-04-17 19:20:06 +0000] Diana: Message 99 from Diana<br>[2025-04-17 19:20:06 +0000] Diana: Message 100 from Diana<br>All messages printed.</pre><h3><strong>When Should You Use Actors?</strong></h3><p>Use actors when your app needs to handle shared data across multiple concurrent tasks. They’re especially helpful in highly asynchronous environments, where class-based managers can be replaced with actors that safely mutate state.</p><blockquote><strong>Why Not Use Structs?</strong></blockquote><p>Structs are value types, and copying them avoids shared state. The real concurrency issues happen with reference types like classes. That’s why actors matter.</p><p>Are you planning to use actors in your next project? Let me know in the comments. Happy coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd40b4264d9a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Clean Architecture for Swift]]></title>
            <link>https://egesucu.medium.com/the-clean-architecture-for-swift-6889c6b80a18?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/6889c6b80a18</guid>
            <category><![CDATA[the-clean-architecture]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Wed, 02 Apr 2025 20:13:34 GMT</pubDate>
            <atom:updated>2025-04-02T20:13:34.119Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*MGw2W8cz1oI5C2Eg" /><figcaption>Photo by <a href="https://unsplash.com/@halacious?utm_source=medium&amp;utm_medium=referral">Hal Gatewood</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>The Clean architecture came out in 2012 by Robert C. Martin to solve the problem with project designs, where he believed a project should:</p><ul><li>Be independent of frameworks</li><li>Be testable &amp; maintainable</li><li>Separate of concerns-driven</li><li>Highly decoupled</li></ul><p>Today, we will dive on how a swift project can be supported by this architecture.</p><h3>Design</h3><p>On the foundation, The Clean Architecture separates the project into layers. Each layer is responsible for a specific task &amp; have a clear dependency rule. Inner layers do represent high level business rules, while outer layers represent low level implementation details such as UI, DB and others. Inner layers do not know anything about outer layers. This helps developers to implement new features without breaking the core layer of the app and be able to test things on their own sub packages.</p><p>These layers are break down into four parts:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yTIWRK0rI6RbVrMibJH7lQ.png" /></figure><ul><li>Interface Adapters → Convert data came by use cases to feed the UI</li><li>Use Cases → App Specific business rules</li><li>Entities → Enterprise-wide business</li><li>Frameworks &amp; Drivers → UI, Database, external frameworks</li></ul><p>In SwiftUI’s terms, those will be ViewModel, Classes, Structs &amp; SwiftUI Views/SwiftData(or CoreData) models.</p><h3>Implementation</h3><p>For a showcase, I have created a humor app which is responsible to show a random meme and a random joke in the app’s foreground. It also has a search joke by keywords feature in which we can execute a search on the Humor API. Source codes are available on <a href="https://github.com/egesucu/Humor-TCA">GitHub</a>, and it is written with Swift 6 to showcase current Swift Structure practices with The Clean Architecture.</p><p><strong>Entities</strong></p><p>With this layer, I have implemented necessary models and enums that will be widely used inside of the app.</p><pre>struct Joke: Identifiable, Codable {<br>    let id: Int<br>    let joke: String<br>}</pre><pre>enum APIErrors: Error {<br>    case invalidURL<br>    case invalidResponse<br>    case decodingFailed<br>    case emptySearchQuery<br>}<br><br>extension APIErrors: LocalizedError {<br>    var errorDescription: String? {<br>        switch self {<br>        case .invalidURL:<br>            &quot;The given URL is invalid&quot;<br>        case .invalidResponse:<br>            &quot;The response is not valid.&quot;<br>        case .decodingFailed:<br>            &quot;Decoding has failed.&quot;<br>        case .emptySearchQuery:<br>            &quot;The search query is empty.&quot;<br>        }<br>    }<br>}</pre><p><strong>Repositories</strong></p><p>This layer covers a repository which handles various functions that we will use. Note that, there is no implementation or a dependency on the repository itself. It will conform into many other use cases.</p><pre>protocol HumorRepository: Sendable {<br>    <br>    func searchJokes(keywords: String, number: Int) async throws -&gt; [Joke]<br>    <br>    func randomJoke() async throws -&gt; Joke<br>    <br>    func randomMeme() async throws -&gt; Meme<br>}</pre><p><strong>Implementations</strong></p><p>This is the place where this repository will be conform to. While you could write something like “HumorRepositoryImpl.” or similar, I have opted for “DefaultHumorRepository” which is more clear way to represent the implementation.</p><pre>final class DefaultHumorRepository: HumorRepository {<br>    private let apiKey: String?<br>    private let baseURL: String<br>    <br>    init() {<br>        self.apiKey = Self.loadAPIKey()<br>        self.baseURL = &quot;https://api.humorapi.com&quot;<br>    }<br>    <br>    /// This is a test case init, which uses apiKey to intentionally make apiKey nil<br>    init(<br>        fakeApiKey: String = &quot;&quot;,<br>        fakeBaseURL: String = &quot;&quot;<br>    ) {<br>        if fakeApiKey.isNotEmpty {<br>            self.apiKey = nil<br>            self.baseURL = &quot;https://api.humorapi.com&quot;<br>        } else if fakeBaseURL.isNotEmpty {<br>            self.baseURL = &quot;&quot;<br>            self.apiKey = Self.loadAPIKey()<br>        } else {<br>            self.apiKey = Self.loadAPIKey()<br>            self.baseURL = &quot;https://api.humorapi.com&quot;<br>        }<br>    }<br>    <br>    private static func loadAPIKey() -&gt; String? {<br>            guard let fileURL = Bundle.main.url(forResource: &quot;api_key&quot;, withExtension: &quot;txt&quot;),<br>                  let key = try? String(contentsOf: fileURL, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines),<br>                  !key.isEmpty else {<br>                return nil<br>            }<br>            return key<br>        }<br>    <br>   <br>    <br>    func searchJokes(keywords: String, number: Int) async throws(APIErrors) -&gt; [Joke] {<br>        guard keywords.trimmingCharacters(in: .whitespacesAndNewlines).isNotEmpty else {<br>            print(&quot;Search query is empty, skipping the search&quot;)<br>            throw .emptySearchQuery<br>        }<br>        let searchQuery = keywords.replacingOccurrences(of: &quot; &quot;, with: &quot;,&quot;)<br>        <br>        guard let apiKey,<br>              let url = URL(string: &quot;\(baseURL)/jokes/search?api-key=\(apiKey)&amp;number=\(number)&amp;keywords=\(searchQuery)&quot;) else {<br>            throw .invalidURL<br>        }<br>        <br>        do {<br>            let (data, _) = try await URLSession.shared.data(from: url)<br>            <br>            do {<br>                let searchResponse = try JSONDecoder().decode(SearchResponse.self, from: data)<br>                return searchResponse.jokes<br>            } catch {<br>                print(&quot;Decoding has failed: \(error)&quot;)<br>                throw APIErrors.decodingFailed<br>            }<br>            <br>        } catch {<br>            throw .invalidResponse<br>        }<br>    }<br>    <br>    func randomJoke() async throws(APIErrors) -&gt; Joke {<br>        guard let apiKey,<br>              let url = URL(string: &quot;\(baseURL)/jokes/random?api-key=\(apiKey)&quot;) else {<br>            throw .invalidURL<br>        }<br>        <br>        do {<br>            let (data, _) = try await URLSession.shared.data(from: url)<br>            do {<br>                return try JSONDecoder().decode(Joke.self, from: data)<br>            } catch {<br>                print(&quot;Decoding has failed: \(error)&quot;)<br>                throw APIErrors.decodingFailed<br>            }<br>            <br>        } catch {<br>            throw .invalidResponse<br>        }<br>    }<br>    <br>    func randomMeme() async throws(APIErrors) -&gt; Meme {<br>        guard let apiKey,<br>              let url = URL(string: &quot;\(baseURL)/memes/random?api-key=\(apiKey)&quot;) else {<br>            throw .invalidURL<br>        }<br>        do {<br>            let (data, _) = try await URLSession.shared.data(from: url)<br>            do {<br>                return try JSONDecoder().decode(Meme.self, from: data)<br>            } catch {<br>                print(&quot;Decoding has failed: \(error)&quot;)<br>                throw APIErrors.decodingFailed<br>            }<br>            <br>        } catch {<br>            throw .invalidResponse<br>        }<br>    }<br>}</pre><p><strong>Use Cases</strong></p><p>This layer is supposed to have different scenarios that we use for the repository implementation. We have three cases on this example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/428/1*OxIjktk7UhFqbsCb4_Hbtw.png" /></figure><pre>struct SearchJokeUseCase {<br>    <br>    private let repository: any HumorRepository<br>    <br>    struct Constants {<br>        static let defaultNumberOfJokes: Int = 10<br>    }<br>    <br>    init(repository: any HumorRepository) {<br>        self.repository = repository<br>    }<br>    <br>    func execute(searchTerms: String, number: Int = Constants.defaultNumberOfJokes) async throws -&gt; [Joke] {<br>        try await repository.searchJokes(keywords: searchTerms, number: number)<br>    }<br>}</pre><pre>import Foundation<br><br>struct RandomJokeUseCase {<br>    private let repository: any HumorRepository<br>    <br>    init(repository: any HumorRepository) {<br>        self.repository = repository<br>    }<br>    <br>    func execute() async throws -&gt; Joke {<br>        try await repository.randomJoke()<br>    }<br>}</pre><p><strong>ViewModel</strong></p><p>This layer is pretty familiar with SwiftUI developers, as this is the bridge(or ViewController for UIKit devs) between data &amp; UI. What’s different with TCA is, we are not reaching for Repositories, but for use cases which is needed for that specific view model.</p><pre>import SwiftUI<br>import Observation<br><br>@MainActor<br>@Observable<br>class HumorViewModel {<br>    var searchQuery: String = &quot;&quot;<br>    var jokes: [Joke] = []<br>    var joke: Joke? = nil<br>    var meme: Meme? = nil<br>    var isLoading: Bool = false<br>    var errorMessage: String? = nil<br>    <br>    private let searchJokeUseCase: SearchJokeUseCase<br>    private let randomMemeUseCase: RandomMemeUseCase<br>    private let randomJokeUseCase: RandomJokeUseCase<br>    <br>    init(<br>        randomJokeUseCase: RandomJokeUseCase,<br>        searchJokeUseCase: SearchJokeUseCase,<br>        randomMemeUseCase: RandomMemeUseCase<br>    ) {<br>        self.randomJokeUseCase = randomJokeUseCase<br>        self.searchJokeUseCase = searchJokeUseCase<br>        self.randomMemeUseCase = randomMemeUseCase<br>    }<br>    <br>    func searchJokes() async {<br>        do {<br>            async let jokesResult = searchJokeUseCase.execute(searchTerms: searchQuery)<br>            jokes = try await jokesResult<br>        } catch {<br>            print(error.localizedDescription)<br>            errorMessage = error.localizedDescription<br>        }<br>    }<br>    <br>    @Sendable func loadRandomData() async {<br>        isLoading = true<br>        errorMessage = nil<br>        do {<br>            async let jokeResult = randomJokeUseCase.execute()<br>            async let memeResult = randomMemeUseCase.execute()<br>            <br>            joke = try await jokeResult<br>            meme = try await memeResult<br>            isLoading = false<br>        } catch {<br>            isLoading = false<br>            errorMessage = error.localizedDescription<br>        }<br>    }<br>}</pre><p><strong>View</strong></p><p>This is the obvious last layer that will be showcased on our app. The point is, the only knowledge of this layer is the VM, and not others.</p><pre>import SwiftUI<br>import Observation<br><br>struct HumorView: View {<br>    @State var viewModel = HumorViewModel(<br>        randomJokeUseCase: RandomJokeUseCase(repository: DefaultHumorRepository()),<br>        searchJokeUseCase: SearchJokeUseCase(repository: DefaultHumorRepository()),<br>        randomMemeUseCase: RandomMemeUseCase(repository: DefaultHumorRepository())<br>    )<br>    <br>    var jokeOfDay: some View {<br>        Group {<br>            Text(&quot;Joke of the Day&quot;)<br>                .font(.title)<br>            VStack(alignment: .center) {<br>                if let joke = viewModel.joke?.joke {<br>                    Text(joke)<br>                        .jokeStyle()<br>                } else {<br>                    Text(&quot;No Joke For you :( &quot;)<br>                        .multilineTextAlignment(.center)<br>                }<br>            }<br>        }<br>    }<br>    <br>    var memeOfDay: some View {<br>        Group {<br>            Text(&quot;Meme of the Day&quot;)<br>                .font(.title)<br>            <br>            if let meme = viewModel.meme,<br>               let memeURL = meme.url {<br>                AsyncImage(url: memeURL) { phase in<br>                    if let image = phase.image {<br>                        image<br>                            .resizable()<br>                            .aspectRatio(contentMode: .fit)<br>                            .frame(maxHeight: 300)<br>                            .clipShape(.rect(cornerRadius: 10))<br>                            .shadow(radius: 4)<br>                    } else if phase.error != nil {<br>                        Text(&quot;Error loading meme&quot;)<br>                    } else {<br>                        ProgressView()<br>                    }<br>                }<br>                .padding()<br>            }<br>        }<br>    }<br>    <br>    var searchJokeView: some View {<br>        Group {<br>            TextField(&quot;Enter search query...&quot;, text: $viewModel.searchQuery)<br>                .textFieldStyle(RoundedBorderTextFieldStyle())<br>                .padding()<br>            <br>            Button(&quot;Search&quot;) {<br>                Task {<br>                    await viewModel.searchJokes()<br>                }<br>            }<br>            .buttonStyle(.borderedProminent)<br>            .padding()<br>            <br>            if viewModel.isLoading {<br>                ProgressView(&quot;Loading...&quot;)<br>            } else if let error = viewModel.errorMessage {<br>                Text(&quot;Error: \(error)&quot;)<br>                    .foregroundColor(.red)<br>            } else {<br>                ForEach(viewModel.jokes) { joke in<br>                    VStack(alignment: .center) {<br>                        Text(joke.joke)<br>                            .jokeStyle()<br>                            .padding()<br>                    }<br>                }<br>            }<br>        }<br>    }<br>    <br>    var body: some View {<br>        NavigationStack {<br>            ScrollView {<br>                VStack(alignment: .leading, spacing: 10) {<br>                    jokeOfDay<br>                    <br>                    memeOfDay<br>                    <br>                    searchJokeView<br>                }<br>                .padding()<br>            }<br>            .navigationTitle(&quot;Humors&quot;)<br>        }<br>        .task(viewModel.loadRandomData)<br>    }<br>}</pre><p><strong>Testability</strong></p><p>Since TCA aims to be testable, we can create some mock cases to test for Unit/UI vise.</p><p>Here, we can create a MockRepo:</p><pre>import Foundation<br><br>final class MockHumorRepository: HumorRepository {<br>    <br>    func searchJokes(keywords: String, number: Int) async throws -&gt; [Joke] {<br>        return (1...number).map { number in<br>            Joke(id: number, joke: &quot;Mock joke \(number) for keyword &#39;\(keywords)&#39;&quot;)<br>        }<br>    }<br>    <br>    func randomJoke() async throws -&gt; Joke {<br>        return Joke(id: 999, joke: &quot;Why don&#39;t skeletons fight each other? Because they don&#39;t have the guts.&quot;)<br>    }<br>    <br>    func randomMeme() async throws -&gt; Meme {<br>        return Meme(<br>            id: 123,<br>            urlString: &quot;https://i.imgflip.com/1bij.jpg&quot;, // Sample meme image URL<br>            type: &quot;meme&quot;<br>        )<br>    }<br>}</pre><p>And we can use this on our Views via #Preview</p><pre>#Preview {<br>    let mockRepo = MockHumorRepository()<br>    let viewModel = HumorViewModel(<br>        randomJokeUseCase: RandomJokeUseCase(repository: mockRepo),<br>        searchJokeUseCase: SearchJokeUseCase(repository: mockRepo),<br>        randomMemeUseCase: RandomMemeUseCase(repository: mockRepo)<br>    )<br>    HumorView(viewModel: viewModel)<br>}</pre><p>Also, we can use the Swift Testing framework for Unit Tests</p><pre>import Testing<br>@testable import Humor_TCA<br>import Foundation<br><br>struct HumorViewModelTests {<br>    <br>    struct MockUseCases {<br>        struct Success: Sendable {<br>            let search = SearchJokeUseCase(repository: MockHumorRepository())<br>            let joke = RandomJokeUseCase(repository: MockHumorRepository())<br>            let meme = RandomMemeUseCase(repository: MockHumorRepository())<br>        }<br>    }<br><br>    @Test<br>    @MainActor<br>    func testSearchJokesUpdatesJokesArray() async throws {<br>        let useCases = MockUseCases.Success()<br>        let vm = HumorViewModel(<br>            randomJokeUseCase: useCases.joke,<br>            searchJokeUseCase: useCases.search,<br>            randomMemeUseCase: useCases.meme<br>        )<br><br>        vm.searchQuery = &quot;test&quot;<br>        await vm.searchJokes()<br><br>        #expect(vm.jokes.count == 10)<br>        #expect(vm.jokes[0].joke == &quot;Mock joke 1 for keyword &#39;test&#39;&quot;)<br>    }<br><br>    @Test<br>    func testLoadRandomDataSetsJokeAndMeme() async throws {<br>        let useCases = MockUseCases.Success()<br>        let vm = await HumorViewModel(<br>            randomJokeUseCase: useCases.joke,<br>            searchJokeUseCase: useCases.search,<br>            randomMemeUseCase: useCases.meme<br>        )<br><br>        await vm.loadRandomData()<br><br>        await #expect(vm.joke?.joke == &quot;Why don&#39;t skeletons fight each other? Because they don&#39;t have the guts.&quot;)<br>        await #expect(vm.meme?.urlString == &quot;https://i.imgflip.com/1bij.jpg&quot;)<br>        await #expect(vm.isLoading == false)<br>        await #expect(vm.errorMessage == nil)<br>    }<br>}</pre><p>It’s actually pretty easy when you have a mock repo to test anything, use cases too:</p><pre>import Testing<br>import Foundation<br>@testable import Humor_TCA<br><br>struct RandomJokeUseCaseTests {<br>    struct MockHumorRepository: HumorRepository {<br>        var jokeToReturn: Joke<br><br>        func searchJokes(keywords: String, number: Int) async throws -&gt; [Joke] {<br>            []<br>        }<br><br>        func randomJoke() async throws -&gt; Joke {<br>            jokeToReturn<br>        }<br><br>        func randomMeme() async throws -&gt; Meme {<br>            throw NSError(domain: &quot;Not needed&quot;, code: 0)<br>        }<br>    }<br><br>    @Test<br>    func returnsJokeFromRepository() async throws {<br>        // Given<br>        let expected = Joke(id: 123, joke: &quot;Mocked random joke&quot;)<br>        let repo = MockHumorRepository(jokeToReturn: expected)<br>        let useCase = RandomJokeUseCase(repository: repo)<br><br>        // When<br>        let result = try await useCase.execute()<br><br>        // Then<br>        #expect(result.id == expected.id)<br>        #expect(result.joke == expected.joke)<br>    }<br><br>    @Test<br>    func throwsIfRepositoryFails() async {<br>        struct FailingRepo: HumorRepository {<br>            func searchJokes(keywords: String, number: Int) async throws -&gt; [Joke] { [] }<br>            func randomJoke() async throws -&gt; Joke {<br>                throw NSError(domain: &quot;test&quot;, code: 1)<br>            }<br>            func randomMeme() async throws -&gt; Meme {<br>                throw NSError(domain: &quot;irrelevant&quot;, code: 0)<br>            }<br>        }<br><br>        let useCase = RandomJokeUseCase(repository: FailingRepo())<br><br>        await #expect(throws: (any Error).self) {<br>            try await useCase.execute()<br>        }<br>    }<br>}</pre><p>We can even test the Default Implementation</p><pre>import Testing<br>@testable import Humor_TCA<br><br>struct DefaultHumorRepositoryTests {<br>    @Test<br>    func searchJokes_withInvalidKeyword_shouldThrowEmptySearchQuery() async {<br>        let repo = DefaultHumorRepository()<br>        <br>        await #expect {<br>            try await repo.searchJokes(keywords: &quot;   &quot;, number: 3)<br>        } throws: { error in<br>            guard let apiError = error as? APIErrors else { return false }<br>            return apiError == .emptySearchQuery<br>        }<br>    }<br><br>    @Test<br>    func searchJokes_withInvalidURL_shouldThrowInvalidURL() async {<br>        let repo = DefaultHumorRepository(fakeApiKey: &quot;dfds&quot;)<br><br>        await #expect {<br>            try await repo.searchJokes(keywords: &quot;funny&quot;, number: 3)<br>        } throws: { error in<br>            guard let apiError = error as? APIErrors else { return false }<br>            return apiError == .invalidURL<br>        }<br>    }<br>    <br>    @Test<br>    func searchJokes_withInvalidURL_showThrowInvalidResponse() async {<br>        let repo = DefaultHumorRepository(fakeBaseURL: &quot;https://www.google.com&quot;)<br>        <br>        await #expect {<br>            try await repo.searchJokes(keywords: &quot;funny&quot;, number: 3)<br>        } throws: { error in<br>            guard let apiError = error as? APIErrors else { return false }<br>            return apiError == .invalidResponse<br>        }<br>    }<br>}</pre><p>And that’s it. You can <a href="https://github.com/egesucu/Humor-TCA">download the source code</a>, add your own api key into <strong>api_key.txt</strong> file you’ll create and test it out!</p><p>While The Clean Architecture has a nice way of maintaining the code in layers, it can bother a simple project with so many setups, hence it is usually recommended an already built mid-to-big size projects, although creating one for our example did not take too much time either.</p><p>What do you think about the architecture? Would you be interested in to discuss this with your team, or do you have another opinion on it. Feel free to discuss down below. Happy coding.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6889c6b80a18" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What is MVVM truly for SwiftUI?]]></title>
            <link>https://egesucu.medium.com/what-is-mvvm-truly-for-swiftui-0bb7cbdd6863?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/0bb7cbdd6863</guid>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[mvvm]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[reactive-programming]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Sat, 08 Feb 2025 11:35:00 GMT</pubDate>
            <atom:updated>2025-02-08T11:35:50.493Z</atom:updated>
            <content:encoded><![CDATA[<h4>While we know and use some of the architecture, do we really know why it’s designed like that, and why many of us use it wrong?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y6QQvjjv05_9Ko74rVI-UQ.png" /></figure><p><strong>Short answer first</strong>: MVVM stands in combination of Model, View, ViewModel. Which means we have 3 layer in the project:</p><ul><li><strong>Model</strong> — Handling the models</li><li><strong>View</strong> — Handles the UI</li><li><strong>ViewModel</strong> — Handles the connection between View and Model, hence the name.</li></ul><p>With that short introduction done, I would like to detail how the code looks like in practice.</p><h3>View</h3><pre>struct UserListView: View {<br>        <br>    @StateObject private var viewModel = UserListViewModel(service: UserService.shared)<br>    <br>    var body: some View {<br>        NavigationStack {<br>            List(viewModel.users) { user in<br>                NavigationLink {<br>                    UserDetailView(user: user, service: UserService.shared)<br>                        .onDisappear {<br>                            viewModel.loadUsers()<br>                        }<br>                } label: {<br>                    HStack {<br>                        Text(user.name)<br>                            .font(.headline)<br>                        Spacer()<br>                        Text(viewModel.provideUserAge(age: user.age))<br>                            .foregroundStyle(.gray)<br>                    }<br>                }<br>            }<br>            .navigationTitle(Text(&quot;Users&quot;))<br>            .onAppear {<br>                viewModel.loadUsers()<br>            }<br>        }<br>    }<br>}</pre><p>The View contains the viewModel it is getting the data and operations from. It shows the data and manages the navigation.</p><h3>ViewModel</h3><p>ViewModels job is to fetch/save data into the model layer, modify the content in behalf of the view and provides its actions to the view. While ViewModels are good to go, it can lead to big viewModels, and many duplication codes when the project go big, so a protocol layer like UserService used to manage calls which can be shared between multiple viewModels.</p><pre>protocol UserServiceProtocol {<br>    func fetchUsers() -&gt; [User]<br>    func update(user: User)<br>}<br><br>class UserService: UserServiceProtocol {<br>    <br>    static let shared = UserService()<br>    <br>    private var users: [User] = [<br>        User(name: &quot;Ali&quot;, age: 25),<br>        User(name: &quot;Ege&quot;, age: 29),<br>        User(name: &quot;Öykü&quot;, age: 28)<br>    ]<br>    <br>    func fetchUsers() -&gt; [User] {<br>        // I did not lean on persistence in this topic.<br>        return users<br>    }<br>    <br>    func update(user: User) {<br>        if let index = users.firstIndex(where: { $0.id == user.id }) {<br>            users[index] = user<br>        }<br>    }<br>}</pre><p>And then, we can provide our ViewModel.</p><pre>import Foundation<br><br>class UserListViewModel: ObservableObject {<br>    <br>    private let service: UserServiceProtocol<br>    <br>    @Published var users: [User] = []<br>    <br>    init(service: UserServiceProtocol) {<br>        self.service = service<br>        Task {<br>            await loadUsers()<br>        }<br>    }<br>    <br>    func provideUserAge(age: Int) -&gt; String {<br>        service.provideAgeText(age: age)<br>    }<br>    <br>    @MainActor<br>    func loadUsers() {<br>        users = service.fetchUsers()<br>    }<br>}</pre><h3>Model</h3><p>Model is responsible for the handling of variables. While A struct is recommended for models, for this example, I went for class for it’s reference type being useful to update data on my non-persistent example.</p><pre>class User: Identifiable {<br>    let id = UUID()<br>    var name: String<br>    var age: Int<br>    <br>    init(name: String, age: Int) {<br>        self.name = name<br>        self.age = age<br>    }<br>}</pre><p>While these are the basics of a MVVM project, let’s dive in into the real reasons why we use MVVM with SwiftUI projects.</p><h3>Testability</h3><p>Testing is important to make sure your app will work more robust and error-prune than before. With the approach like having a service layer, it helps us to write mock models and services to communicate with the viewModels.</p><pre>import Foundation<br>@testable import Swift_MVVM_Example<br><br>class MockUserService: UserServiceProtocol {<br>    <br>    var mockUsers: [User] = [<br>        User(name: &quot;Test User 1&quot;, age: 28),<br>        User(name: &quot;Test User 2&quot;, age: 35)<br>    ]<br><br>    func fetchUsers() -&gt; [User] {<br>        return mockUsers<br>    }<br>    <br>    func update(user: User) {<br>        if let index = mockUsers.firstIndex(where: { $0.id == user.id }) {<br>            mockUsers[index] = user<br>        }<br>    }<br>}</pre><p>This helps us to work on the service implementations easier. Here is an example of using Swift Testing with Mock Service to test operations.</p><pre>import Testing<br>@testable import Swift_MVVM_Example<br><br>struct UserDetailViewModelTests {<br>    <br>    var viewModel: UserDetailViewModel<br>    var mockService: MockUserService<br>    <br>    init() {<br>        self.mockService = MockUserService()<br>        self.viewModel = UserDetailViewModel(user: mockService.mockUsers[0], service: mockService)<br>    }<br>    <br>    @Test<br>    func userInitialization() throws {<br>        #expect(viewModel.user.name == &quot;Test User 1&quot;)<br>        #expect(viewModel.user.age == 28)<br>    }<br>    <br>    @Test<br>    func saveChanges() throws {<br>        viewModel.user.name = &quot;New Name&quot;<br>        viewModel.user.age = 50<br>        <br>        viewModel.saveChanges()<br>        <br>        #expect(mockService.mockUsers[0].name == &quot;New Name&quot;)<br>        #expect(mockService.mockUsers[0].age == 50)<br>    }<br>    <br>}</pre><p>This is another example on how we fetch the mock data.</p><pre>import Testing<br>@testable import Swift_MVVM_Example<br><br>struct UserListViewModelTests {<br>    <br>    var viewModel: UserListViewModel<br>    var mockService: MockUserService<br>    <br>    init() {<br>        self.mockService = MockUserService()<br>        self.viewModel = UserListViewModel(service: mockService)<br>    }<br><br>    @Test func fetchUsers() async throws {<br>        await viewModel.loadUsers()<br>        #expect(viewModel.users.count == 2)<br>        #expect(viewModel.users.first?.name == &quot;Test User 1&quot;)<br>    }<br><br>}</pre><p>Same Mocking data can also be used with SwiftUI Previews to test them on the Xcode Preview Panel.</p><p>Now, let’s look at what we must be carefull to comply with the architecture.</p><h3><strong>Clear Separation of Concerns</strong></h3><p>The model must be independent from view.</p><p><strong>Example</strong>: We moved the age text out from the View into the ViewModel.</p><h3><strong>The ViewModel Should Be UI-Agnostic</strong></h3><p>Sometimes, we tend to import <strong>UIKit</strong>, <strong>SwiftUI</strong> etc. into the <strong>ViewModel</strong>. This is simply wrong. Those frameworks should be used within the View files. You have <strong>@ Published</strong> with the Foundation Framework when you conform the class into ObservableObject, or if you use Observable Framework, you don’t even need to use <strong>@ Published</strong> or any <strong>@ File</strong>.</p><p>Sometimes, you might be tempting to add UIImage, or Image into the Model, which is also not the correct way to do on MVVM. You could simply have the data as Data, and do the conversion inside of the View on a function.</p><h3><strong>Dependency Injection for Testability</strong></h3><p>Instead of initializing the service with UserService(), we injected it into the initializer of the ViewModel, so that we can use the mock service on Unit Tests.</p><pre>protocol UserService {<br>    func fetchUser() -&gt; User<br>}<br><br>class UserViewModel: ObservableObject {<br>    private let service: UserService<br><br>    init(service: UserService) {<br>        self.service = service<br>    }<br><br>    func loadUser() {<br>        _ = service.fetchUser()<br>    }<br>}<br><br>// Mock Data Creation<br>class MockUserService: UserService {<br>    func fetchUser() -&gt; User {<br>        return User(name: &quot;Test User&quot;)<br>    }<br>}<br><br>// On Testing<br>let viewModel = UserViewModel(service: MockUserService())</pre><h3><strong>Use Combine for Reactive Data Flow</strong></h3><p>Although you can use Async-await and it’s the recommended logic to use for most operations, Combine offers more reactive oriented functional programming. Swift Concurrency has AsyncStreams, but those are only providing new values to update the view, while Combine Publishers/Subscribers can share more.</p><p>Example of a Data Fetch with</p><pre>class UserViewModel: ObservableObject {<br>    @Published var username: String = &quot;&quot;<br><br>    private var cancellables = Set&lt;AnyCancellable&gt;()<br><br>    func startListening() {<br>        NotificationCenter.default.publisher(for: NSNotification.Name(&quot;UserUpdated&quot;))<br>            .compactMap { $0.object as? String }<br>            .assign(to: &amp;$username)<br>    }<br>}</pre><p>While AsyncStream has:</p><h3><strong>ViewModel Should Handle Business Logic, Not Views</strong></h3><p>Handling of the business logic belongs to ViewModel. Hence we moved out the age text from View to our ViewModel.</p><p>Here’s a good example:</p><pre>class ContentViewModel: ObservableObject {<br>    @Published var formattedDate: String = &quot;&quot;<br><br>    private let formatter: DateFormatter = {<br>        let formatter = DateFormatter()<br>        formatter.dateStyle = .long<br>        return formatter<br>    }()<br><br>    func loadDate() {<br>        formattedDate = formatter.string(from: Date())<br>    }<br>}</pre><p>And the Bad One:</p><pre>struct ContentView: View {<br>    let date = Date()<br><br>    var body: some View {<br>        Text(dateFormatter.string(from: date)) // ❌ View handles formatting<br>    }<br><br>    private var dateFormatter: DateFormatter {<br>        let formatter = DateFormatter()<br>        formatter.dateStyle = .long<br>        return formatter<br>    }<br>}</pre><h3><strong>Views Should Be Stateless Whenever Possible</strong></h3><p>We need to avoid having too much state on the View to handle View logics, but move them over to the ViewModel layer. There are 3 ways for this:</p><ul><li>For ViewModels that should be owned by a View, we use StateObject</li><li>For ViewModels passed from a parent View, we use ObservedObject .</li><li>For app-wide dependencies, we use EnvironmentObject.</li></ul><pre>struct ParentView: View {<br>    @StateObject private var viewModel = UserViewModel()<br><br>    var body: some View {<br>        ChildView(viewModel: viewModel)<br>    }<br>}<br><br>struct ChildView: View {<br>    @ObservedObject var viewModel: UserViewModel<br><br>    var body: some View {<br>        Text(viewModel.username)<br>    }<br>}</pre><p>You can find the final project here.</p><p><a href="https://github.com/egesucu/Swift-MVVM-Example">GitHub - egesucu/Swift-MVVM-Example</a></p><p>While SwiftUI talks a lot on the MVVM architecture, some developers and projects are shifted into another architectures to use more Async-await orientation then the old reactive MVVM approaches. MVVM has it’s own con’s and pro’s in the end. Here’s a table gpt friend:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gYiP32MVseqR5OCfLdxR5A.png" /></figure><p>Overall, Swift 6 looks like to be more oriented into Actor Based MV and Clean Architecture which you can find articles/examples online. Do you have any questions? Feel free to comment down below.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0bb7cbdd6863" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Açıkçası, Tutturamadım]]></title>
            <link>https://egesucu.medium.com/a%C3%A7%C4%B1k%C3%A7as%C4%B1-tutturamad%C4%B1m-0228bcaa8ca1?source=rss-a279959eb187------2</link>
            <guid isPermaLink="false">https://medium.com/p/0228bcaa8ca1</guid>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[personal-development]]></category>
            <category><![CDATA[psychology]]></category>
            <category><![CDATA[development]]></category>
            <dc:creator><![CDATA[Ege Sucu]]></dc:creator>
            <pubDate>Wed, 01 Jan 2025 20:13:34 GMT</pubDate>
            <atom:updated>2025-01-01T20:13:34.025Z</atom:updated>
            <content:encoded><![CDATA[<p>Bir insan verdiği hedeflerde bu kadar mı tutarlı kalamaz 😅</p><p>Bu yazı geçen sene yazdığım yazının yıllık serisine aittir. Geçen seneki yazıya <a href="https://medium.com/@egesucu/2024-daha-iyi-bir-ege-8cb218464594">bu bağlantıdan</a> erişebilirsiniz.</p><p>Geçtiğimiz seneki yazıda odaklandığım belirli noktalar, ve bu sene içinde kendimi görmek istediğim belirli noktalar vardı. Peki bu sonuçlar ne oldu?</p><ul><li>90 kiloya ulaşma ❌</li><li>Her gün en az 45 dakika yürüyüş/spor yapma ❌</li><li>Ayda en az 1 Medium Yazısı Yazma ❌ (Mayıs, Haziran ve Ekim eksik)</li><li>1 yeni mobil uygulama yazma — Yetenek ❌</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FvX9WcCiWwUF7G%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fgiphy.com%2Fgifs%2Fmrw-mods-bethesda-vX9WcCiWwUF7G&amp;image=https%3A%2F%2Fmedia4.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExaGlwdmYzcnA4NnlhYm91MnpnYW03cDQ4bHozNnI4bXhlNTdtaTNuMCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FvX9WcCiWwUF7G%2Fgiphy_s.gif&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="244" frameborder="0" scrolling="no"><a href="https://medium.com/media/11a02d2c44c5a16c86f5c3a8956e3e3e/href">https://medium.com/media/11a02d2c44c5a16c86f5c3a8956e3e3e/href</a></iframe><p>Evet, epey tutarsız bir şekilde devam ettim.</p><p><strong>Peki bu süreçte neler yaşandı?</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/333/1*Ow3wcVPbVOcnLJGxrd5uoA.jpeg" /></figure><p>Mayıs’ta nişanlım ile evlendik.👰🏻‍♀️🤵🏻</p><p>Aradan geçen onca ay sonunda hayatımda olduğumdan çok daha keyif aldığım, ve adeta en büyük hedefimi gerçekleştirmiş gibi hissettiğim bir dönem oluyor. Doğru kişiyi seçtiğimi çok iyi biliyorum.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/533/1*X0A3Sk5WUE6-QPPU4yWgsg.jpeg" /><figcaption>Efes Antik Kenti’nden</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/375/1*n9u9YCl6IncIMAi7YGTMxQ.jpeg" /><figcaption>Bergama’dan</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/533/1*_-TvERM4JteqQienmRxFkQ.jpeg" /><figcaption>Troya’dan</figcaption></figure><p>Ağustos’ta araba ile ilk uzun yola çıkarak; Troya, Cunda, Bergama, Efes ve Şirince gibi pek çok tarihi yerleri gezdik.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*jAvNU67Me7mvJhJD_rqZSA.jpeg" /></figure><p>Eylül’de ilk defa Formula 1 yarışını yakından ve gerçekten tecrübe etme şansını buldum. (#CS55)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/533/1*NWIyEjs8dYoQ5HYkBHWi_g.jpeg" /><figcaption>Aziz Sava Katedrali — Belgrad/Sırbistan</figcaption></figure><p>Aralık ayında ailecek Sırbistan’a giderek Belgrad’ı gezdik.</p><p>Bu süreçte hedeflerimde aksamalar oldu. Kimi hedefim ilk ayda, kimisi dördüncü ayda hata verdi. Hedeflerin büyük bir kısmını zor olmayacak şekilde belirtmeme rağmen, tutarsızlık ve iş hayatı/evlilik hazırlığı sırasında yaşadığım sıkıntıların da bunda rol oynadığını belirtmem gerekiyor. Bu süreçte en tutarlı olduğum kısım Medium olurken, en tutarsız olduğum kısım mobil uygulama oldu.</p><h3>Neden hata yaptım?</h3><p>Bu süreçte yaptığım hatalar, bana epey çıktı sağladı. Bunları öğrendiğimi umuyorum ve birkaçına değinmek isterim.</p><p><strong>Kilo</strong></p><p>Her ne kadar evlilik sonrası 7 kiloya kadar zayıflamış olsam da, bu kaybın bir kısmını geri aldım ve hiçbir zaman 90 kiloya ulaşamadım. Dışarıda yediğim yemekleri bırakmam ev içi atıştırmalıkları arttırdı. Bahsettiğim bel ağrıları ve iş uğraşları sporumu kesti ve bu da çok yardımcı olmadı. Ama 90 kilo hedefi de iddialıydı. Ayda düzenli olarak 4 kilo vermemden geçiyordu ki, o düzen pek olmadı.</p><p><strong>Uygulama</strong></p><p>Bu beni üzen başka bir konu oldu. Girişte bir fikir olmasına ve bunun üzerinde başlamama rağmen iş kısmındaki gelişmeler bu projeyi çok erken bir süreçte çöpe attı. Her ne kadar yazılıma dair ufak projeler yapmış olsam da, hiçbir zaman bir uygulama çıkaramadım, hatta kendime dair kullanabileceğim küçük bir PoC(Proof of Concept) bile yapamadım. Bu konuda uygulama yaparken kafamda kurmaya çalıştığım “mükemmeliyetçi” düşünce ve “eşsiz fikir bulamama” konuları çok etkili oldu.</p><p>Öyle ki tasarım değişikliğindeki kararsızlığım sebebiyle bu sene uzun yıllardır güncellemesini yaptığım “Pet Reminder” uygulamasını da etkiledi, güncellemeyi yapamadım. Bu konuda çok sorun yaşadım.</p><p><strong>Yürüyüş</strong></p><p>Bu alanda %100 hata yaptığımı söyleyemem. Evet, dışarıda yürüyüşler yapıyorum, fakat hedeflediğimden epey az şekilde. Bu süreçte akıllı saati bırakıp klasik saate geçmem pek bir şeyi değiştirmedi. Ancak dışarı çıkmaya ve yürümeye dair motivasyonu sene başından beri nadiren bulabiliyorum ve bu da hedefe yakından uzaktan geçemememe neden oldu.</p><p>2024&#39;ü bu şekilde kapatıyorum. İlişkide maksimum mutluluk ve iş/kariyer alanında verimsiz ve keyifsiz bir dönem olarak.</p><h3>2025&#39;te Odaklandığım Hedefler Neler?</h3><p>Bu sene 4 hedef koyuyorum.</p><ul><li>Güzel bir Miktar Kilo verme(sayısız — Eşimle ortak hedefimiz)</li><li>Fiziksel Yaşama Odaklanma(Az ekran süresi, bol fiziksel çıktılar)</li><li>Telefonumda Günlük Kullanacağım Bir Log Uygulaması</li><li>Finansalları Düzene Çekme</li></ul><p>Bu hedefler üzerinde bu sene nasıl ilerlediğimi de ayrıca kaydetmeyi düşünüyorum(dijital değil).</p><p>Fiziksel yaşama odaklanmaya dair, daha öncesinde Lamy’nin dolma kalemini almış ve bir süredir kullanmaya başlıyordum. Klasik saat geçişi sonrası gündelik aktivitelerimde 2 dijital noktayı çıkardıktan sonra geriye en büyük meselelerimden biri kaldı.</p><p><strong>Telefon</strong></p><p>Bu konuda da kullanımı epey azaltmayı planlıyorum. Bakalım bunu başarabilecek miyim…</p><p>Günlük kullanacağım Log uygulamasında başlangıç düzeyine geldim. Bunu ilerletip telefonumda kullanacağım ve sonra yayınlayabilirim de. Bu sene kendime GPT’lerin de katkılarıyla daha verimli bir kod süreci yaşatmak istiyorum.</p><p>Finansal konularda da daha birikim, az kart bol kredi skoru derecelerinde kendimi ilerletmek istiyorum. Harcamaları kontrol edip çekidüzen vermek de bunun alt hedefleri arasında yer alıyor.</p><p><strong>Bakalım, bu sefer daha istikrarlı olacak mı.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0228bcaa8ca1" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>