All Medium stories moved here

I have a Medium profile available here https://medium.com/@damianmarkowski. I used to have 5 articles published there but today I decided to move 4 of them here, to damianmarkowski.com/blog and delete 1 of them permanently since it was written specifically for Medium, it was about using an OpenAI API in an educational mobile app but it was not providing a lot of value.

Why did I publish those stories on Medium in the first place? The only potential advantage of using Medium as an article publisher tool for me was hope that it may give me audience, people that may potentially read my stories. Let’s have a look at some data, if it really gave me a lot of readers.







Those numbers are not horrible but not amazing neither. I could get the same number of views on my blog, here, without Medium readers. There are also a few other advantages of publishing posts only here:

  1. Monetisation – I show Google AdSense ads here so every time someone reads one of my posts I make some money,
  2. Full control over my content – I don’t need to make any backups of my posts, I don’t need to be worried that maybe at some point Medium decides to shut down their service and all my posts will be gone,
  3. Personal brand – having all my posts here works much better for building my personal brand, here posts are a part of the whole damianmarkowski.com service and also a URL for every post starts with damianmarkowski.com/blog/ not with https://medium.com/@damianmarkowski/.
  4. More focus on blogging – I’ve been focused on investing quite a lot of money in my product in the last 2 years but I decided to stop that, ROI was not big enough unfortunately so I want to focus on sharing my knowledge now, I want to be investing my time instead of money now and that’s consistent with moving all my content to damianmarkowski.com/blog.

Just to conclude this post, here are the links to the posts that I moved from Medium:

  1. Why I stopped using SwiftUI and AWS in my personal project
  2. iOS Developer – preparing for a recruitment process, part 1: CV
  3. iOS Developer — preparing for a recruitment process, part 2: technical screening
  4. iOS Developer — preparing for a recruitment process, part 3: technical interview

 

TimeZone.current on iOS

Where does a value for TimeZone.current (https://developer.apple.com/documentation/foundation/timezone/2293341-current) come from in Swift? I spent some time on checking that this weekend. A list of the time zones changes at least several times a year, it’s not constant. There is this article https://zachholman.com/talk/utc-is-enough-for-everyone-right that explains the “why” for that in a very nice, fun and detailed way. I really recommend reading that article! It’s quite long but super interesting 😀 The time zone list changed on the 4th September 2024 last time (https://www.iana.org/time-zones) so just a bit over 3 months ago. So maybe TimeZone.current makes some network request to check a current version of that database? What if a user’s device isn’t connected to the Internet? What’s a default value then?

Let’s have a look at the Foundation framework’s source code here https://github.com/swiftlang/swift-corelibs-foundation and more specifically saying an NSTimeZone.swift file. So there is this line

_timeZone.abbreviation(for: aDate)

there which – if we follow a chain of calling the functions – calls a C function called __InitTZStrings() from a CFTimeZone.c file later on. And now, what does __InitTZStrings() do?

static void __InitTZStrings(void) {
    __tzZoneInfo = CFSTR(TZDIR);
    __tzDir = TZDIR "zone.tab";
}

It creates a path to a file with the time zone list! That’s it, we found it!! 💃 So there is a constant value for a directory path called TZDIR and its value is the following (you can find it in the same file, CFTimeZone.c):

#define TZDIR	"/usr/share/zoneinfo/"

and then a zone.tab filename is just appended to that path so the answers to the questions that I put at the beginning of this blog post are:

  1. TimeZone.current doesn’t make any network requests to get an updated list of the time zones.
  2. Apple keeps that value in a /usr/share/zoneinfo/zone.tab file on iOS.
  3. Given point 2 – if you don’t update a version of the iOS system on your iPhone – you may have an outdated time zone list on your phone.

We may just try to answer the very last question in this post: what is a .tab format?

The .tab format on iOS refers to the tab-separated values in a text file, represented by the UTTypeTabSeparatedText (https://developer.apple.com/documentation/uniformtypeidentifiers/uttypetabseparatedtext) identifier in Apple’s frameworks. That type of a file contains data where fields are separated by the tab characters (\t). That format is widely supported across the iOS, iPadOS and macOS apps for handling structured data and it’s often used for the spreadsheet exports or data transfers.

Linked list

Linked list is a data structure used to represent a sequence of data of a given type. It’s similar to an array but there are a few key differences between those 2 data structures. Both of them have their advantages and disadvantages.

Data structure Advantages Disadvantages
linked list – adding and removing items at any index has O(1) time complexity if we have a reference to an item at that index – adding and removing items at any index has O(n) complexity if we don’t have a reference to an item at that index

– accessing nth item has O(n) time complexity

array – accessing nth item has O(1) time complexity – adding and removing items at any index has O(n) complexity

Linked list items are called as nodes. A node may be defined as the following:

class Node<T> {
  let value: T
  var next: Node? 
  
  init(value: T) {
    self.value = value
  }  
}

and then the list itself:

class LinkedList<T> {

    private var head: Node<T>?

    var isEmpty: Bool {
        head == nil
    }

    var first: Node<T>? {
        head
    }

    func append(value: T) {
        let newNode = Node(value: value)
        if let lastNode = last() {
            lastNode.next = newNode
        } else {
            head = newNode
        }
    }

    private func last() -> Node<T>? {
        var currentNode = head
        while currentNode?.next != nil {
            currentNode = currentNode?.next
        }
        return currentNode
    }

    func remove(value: T) where T: Equatable {
        var currentNode = head
        var previousNode: Node<T>?

        while currentNode != nil {
            if currentNode?.value == value {
                if previousNode == nil {
                    head = currentNode?.next
                } else {
                    previousNode?.next = currentNode?.next
                }
                return
            }
            previousNode = currentNode
            currentNode = currentNode?.next
        }
    }

    func printValues() {
        var currentNode = head
        while currentNode != nil {
            print(currentNode!.value, terminator: " -> ")
            currentNode = currentNode?.next
        }
        print("nil")
    }
    
}

And now, once we defined Node and LinkedList, let’s try to use them:

let list = LinkedList<Int>()
list.append(value: 1)
list.append(value: 2)
list.append(value: 3)
list.append(value: 4)
list.append(value: 5)
list.printValues()
list.remove(value: 2)
list.remove(value: 4)
list.printValues()
print(list.first?.value as Any)
list.remove(value: 1)
list.remove(value: 3)
list.remove(value: 5)
print(list.isEmpty)

Here’s the output:

You can find all the code mentioned in this post here: https://github.com/DamianMarkowski/algorithms-and-data-structures/blob/master/Linked-list/Linked-list.playground/Contents.swift.

Dijkstra’s algorithm

What do the following apps and systems have in common

?

There is at least one such thing – there is a really high chance that all of them use Dijkstra’s algorithm to figure out the shortest distance between 2 locations on Earth, to track how fast and in what direction diseases spread around the world and how to efficiently distribute the phone calls throughout the network.

Speaking of that in a bit more technical way, an objective of Dijkstra’s algorithm is to find the shortest path between any 2 vertices in a graph.

Here’s a really good YouTube video explaining that algorithm in detail:

And here’s an implementation of that algorithm in Swift:

struct Edge {
    let destination: Int
    let weight: Int
}

struct Graph {
    let numberOfVertices: Int
    var adjacencyList: [[Edge]] = []

    init(numberOfVertices: Int) {
        self.numberOfVertices = numberOfVertices
        self.adjacencyList = Array(repeating: [], count: numberOfVertices)
    }

    mutating func addEdge(source: Int, destination: Int, weight: Int) {
        adjacencyList[source].append(Edge(destination: destination, weight: weight))
    }
}

func dijkstra(graph: Graph, source: Int) -> [Int] {
    var distances = Array(repeating: Int.max, count: graph.numberOfVertices)
    var priorityQueue = [(vertex: Int, distance: Int)]()
    distances[source] = 0
    priorityQueue.append((source, 0))

    while !priorityQueue.isEmpty {
        priorityQueue.sort { $0.distance < $1.distance }
        let current = priorityQueue.removeFirst()

        for edge in graph.adjacencyList[current.vertex] {
            let newDistance = current.distance + edge.weight
            if newDistance < distances[edge.destination] {
                distances[edge.destination] = newDistance
                priorityQueue.append((edge.destination, newDistance))
            }
        }
    }
    
    return distances
}

var graph = Graph(numberOfVertices: 5)

graph.addEdge(source: 0, destination: 1, weight: 6)
graph.addEdge(source: 0, destination: 3, weight: 1)
graph.addEdge(source: 1, destination: 0, weight: 6)
graph.addEdge(source: 1, destination: 3, weight: 2)
graph.addEdge(source: 1, destination: 2, weight: 5)
graph.addEdge(source: 1, destination: 4, weight: 2)
graph.addEdge(source: 2, destination: 1, weight: 5)
graph.addEdge(source: 2, destination: 4, weight: 5)
graph.addEdge(source: 3, destination: 0, weight: 1)
graph.addEdge(source: 3, destination: 1, weight: 2)
graph.addEdge(source: 3, destination: 4, weight: 1)
graph.addEdge(source: 4, destination: 1, weight: 2)
graph.addEdge(source: 4, destination: 2, weight: 5)
graph.addEdge(source: 4, destination: 3, weight: 1)

let distances = dijkstra(graph: graph, source: 0)

for (vertex, distance) in distances.enumerated() {
    print("Shortest distance from 0 to \(vertex) is \(distance)")
}

You can find that code in my GitHub repo here https://github.com/DamianMarkowski/algorithms-and-data-structures/blob/master/Dijkstras-algorithm/Dijkstras-algorithm.playground/Contents.swift as well.

GitHub profile cleanup

A few weeks ago, my friend sent me a few screenshots showing quite funny descriptions of my public GitHub repositories. Those descriptions were generated by https://github-roast.pages.dev. I had 19 public repos at that time. Those screenshots and descriptions reminded me how much out of date and untouched for a long time my GitHub profile was. So I decided to fix that.

After a cleanup, I have only 2 public repos now:

  1. https://github.com/DamianMarkowski/ios-security,
  2. https://github.com/DamianMarkowski/BuckSample.

I believe both of them may be useful for at least some people. Moving forward, I will be adding and hopefully maintaining more public repos with the code focused on algorithms and data structures.

My book recommendations

I like readings books related to building a company, managing people, big tech company stories and their founder autobiographies and biographies. Here’s a list of such books that I’ve read so far and I fully recommend reading them (I keep this list up to date, I update this blog post, I put the items below in a random order):

  1. “How to Turn Down a Billion Dollars: The Snapchat Story” by Billy Gallagher
  2. “The Spotify Play” by Sven Carlsson and Jonas Leijonhufvud
  3. “The Four Steps to the Epiphany” by Steve Blank
  4. “Become an Effective Software Engineering Manager” by James Stanier
  5. “Tesla. Inventor of the Electrical Age” by W. Bernard Carlson
  6. “How to Win Friends & Influence People” by Dale Carnegie
  7. “Amazon Unbound” by Brad Stone
  8. “Crossing the Chasm, 3rd Edition” by Geoffrey A. Moore
  9. “Super Pumped: The Battle for Uber” by Mike Isaac
  10. “No Rules Rules: Netflix and the Culture of Reinvention” by Reed Hastings and Erin Meyer
  11. “Tim Cook: The Genius Who Took Apple to the Next Level” by Leander Kahney
  12. “Think Twice” by Michael J. Mauboussin
  13. “The Influential Mind: What the Brain Reveals About Our Power to Change Others” by Tali Sharot
  14. “Walt Disney: An American Original” by Bob Thomas
  15. “The Paradox of Choice: Why More Is Less” by Barry Schwartz
  16. “The Google Guys: Inside the Brilliant Minds of Google Founders Larry Page and Sergey Brin” by Richard L. Brandt
  17. “Netflixed: The Epic Battle for America’s Eyeballs” by Gina Keating
  18. “The Making of a Manager” by Julie Zhuo
  19. “Hatching Twitter: A True Story of Money, Power, Friendship, and Betrayal” by Nick Bilton
  20. “No Filter: The Inside Story of Instagram” by Sarah Frier
  21. “Start With Why” by Sinek Simon
  22. “The Airbnb Story” by Leigh Gallagher
  23. “The Manager’s Path: A Guide for Tech Leaders Navigating Growth and Change” by Camille Fournier
  24. “The Facebook Effect” by David Kirkpatrick
  25. “Steve Jobs” by Walter Isaacson
  26. “Make It, Don’t Fake It” by Sabrina Horn
  27. “7 Powers: The Foundations of Business Strategy” by Hamilton Helmer
  28. “The Founders” by Jimmy Soni
  29. “Elon Musk” by Walter Isaacson
  30. “That Will Never Work: The Birth of Netflix and the Amazing Life of an Idea” by Marc Randolph

Introducing Mowyty AI

It’s been 2 months since I published Mowyty on the App Store (https://apps.apple.com/us/app/mowyty/id1622156106) and Google Play (https://play.google.com/store/apps/details?id=com.mowyty.app). There have been 15 updates in both app versions in total since then but I just released the biggest update so far: I added Mowyty AI – an AI driven feature that will help the users improve their reading and listening skills and at the same time they are given an opportunity to learn the interesting facts about Poland and life in Poland. 

Why?

There are 2 reasons why I decided to add an AI driven feature:

  1. Nowadays many people talk about AI and also they expect to see something related to AI in the digital products they use on a daily basis. So adding Mowyty AI is a great move from a marketing and a user acquisition perspective.
  2. Mowyty AI is available for the users who purchased a Mowyty Pro subscription so it’s making the Pro version of the app more interesting. Hopefully it will increase a number of the paying users.

What?

I spent quite a bit of time on reading articles and watching videos about how people leverage AI in their digital products because I was struggling to find the use cases that would be fully beneficial for the users, not only attractive from a technical perspective. I didn’t want to use AI just because it’s a hot topic at the moment in our industry. Eventually I came up with a great AI use case for Mowyty.

Here is a user flow:

  1. User enters a Mowyty AI screen.
  2. AI generates a list of the topics related to Poland and life in Poland. The topics are generated in Ukrainian.
  3. AI translates all the topics from Ukrainian to Polish so we can display both versions.
  4. User chooses one of those topics.
  5. AI generates a text about the topic chosen by the user in point 4.
  6. User may read the generated text but also may choose the app to read it for them (either a full text or the particular words).

How?

I used OpenAI API (https://platform.openai.com) to build Mowyty AI.

Mowyty – first A/B tested ad campaign – results

We’ve run the first A/B tested Facebook + Instagram (stories and reels) ad campaign for Mowyty and I would like to share the results.

We ran the ads using Facebook Ads Manager. We added 2 Ad sets: one driving the Android app downloads and the second one the iOS app downloads. After more or less a week, we noticed that the Ad set targeting the iOS users reaches around 21 times less users than the Android one so we decided to switch it off and investigate it later and leave only the Android one on.

The campaign’s duration was 13 days: 19th April – 2nd May 2023.

We A/B tested the following ads (I’m showing only the screenshots here, we had videos in the campaign though):

ad 1

ad 2

As a result, only ad 2 made the users download the app, there were no installs from ad 1. I think the reason might be that ad 1 had too much text, that Facebook and Instagram users didn’t want to spend time on reading that.

Stats for the users who downloaded the Mowyty app after seeing ad 1:

Gender: men (36%), women (64%).

Age: 18-24 (8%), 25-34 (40%), 35-44 (48%), 45-54 (4%).