Karhin’s Blog
Apps, Design and Music
It's me!

Hello hello

I'm Mikalaj Karhin

I create apps and music, and write about both.

Telegram Instagram

Letuciennaść: 10th anniversary remix

Letuciennaść (Belarusian) means “dreaminess”. I made this remix to celebrate the tenth anniversary of the original track. Time really does fly, it’s hard to believe how long ago that was.

This time I decided to release the track the way I used to in the good old days: only on SoundCloud and Bandcamp. It’s even available for download (just in case 😁). This distribution model actually has a big advantage: there’s nothing to worry about and no need to plan weeks in advance, you can publish everything in just a few minutes. I made the remix a few months ago and had almost forgotten about it, but right before releasing it I still managed to make a few small edits.

You can listen to the original version via the link. I updated part of the drums and added more synthesizers, trying to preserve the original vibe of the composition while making it “faster”. Unfortunately, the original project files no longer exist (not even the WAV versions). I had to use a stem splitter to extract at least some tracks from the original. A similar situation happened with the remix of “Siena”.

Sometimes I like going back to very old tracks, even though they can be a bit strange and rough. And I’m sure that in ten years I’ll say the same thing about the tracks I’m making now. For me, it works like a time machine. These tracks still contain fragments of my memories from Minsk in 2015-2016, from a completely different world in every sense.

How to cancel withObservationTracking

Starting in iOS 17, Apple introduced a new framework called Observation. It does make life a bit easier in the context of SwiftUI, but integration with UIKit (or simply the task of wiring together different Observable objects) can cause some problems, since the only tool available is withObservationTracker.

If you are going to build an app for iOS 26 or later, take a look at Observations; you do not need to read the rest of the article. As an alternative, you can take a look at Perception, it may be a good option.

With it, almost everything is fine except for one issue: it assumes a recursive call to itself, and there is no way to cancel it in the form in which it is proposed to be used.

I tested the following code on the iOS 17 simulator and on current operating systems, including macOS, and it works like a charm (yes, it’s as simple as it looks). If you have any comments, you are always welcome to share them.

@MainActor
final class ObservationTracker {
    
    private let apply: () -> Void
    
    init(_ apply: @escaping () -> Void) {
        self.apply = apply
        observe()
    }
    
    private func observe() {
        withObservationTracking({ [weak self] in
            self?.apply()
        }, onChange: { [weak self] in
            Task { @MainActor in
                self?.observe()
            }
        })
    }
    
}

If you remove the reference to the object, observation will stop immediately (you can verify this in the debugger). Most importantly, do not keep strong references inside the callback. This can result in a memory leak, but that generally applies to withObservationTracker as well.

var observer: ObservationTracker?

...

observer = ObservationTracker { [weak self] in
    self?.time = self?.timeModel.time
}

If you need a more classic syntax with cancel, you can add some kind of flag to the onChange method that will stop the recursive call. In general, it was enough for me to simply set the object to nil.

You should not manually check for internet connectivity in mobile apps

Simply because it makes no sense in 99% of applications that use HTTP, and it leads to a large number of other problems.

I have seen this kind of class in many apps, and almost always the same usage pattern. Here is an example from Stack Overflow.

actor ConnectionService {

    static func monitorNetwork() -> AsyncStream<Bool>{
        AsyncStream { continuation in
            let monitor = NWPathMonitor()

            monitor.pathUpdateHandler = { path in
                switch path.status {
                case .satisfied:
                    continuation.yield(true)
                case .unsatisfied, .requiresConnection:
                    continuation.yield(false)
                @unknown default:
                    continuation.yield(false)
                }
            }

            monitor.start(queue: DispatchQueue(label: "InternetConnectionMonitor"))
            
            continuation.onTermination = { _ in
                monitor.cancel()
            }
        }
    }

    static func isConnected() async -> Bool{
        typealias Continuation = CheckedContinuation<Bool, Never>
        return await withCheckedContinuation({ (continuation: Continuation) in
            let monitor = NWPathMonitor()

            monitor.pathUpdateHandler = { path in
                monitor.cancel()
                switch path.status {
                case .satisfied:
                    continuation.resume(returning: true)
                case .unsatisfied, .requiresConnection:
                    continuation.resume(returning: false)
                @unknown default:
                    continuation.resume(returning: false)
                }
            }
            monitor.start(queue: DispatchQueue(label: "InternetConnectionMonitor"))
        })
    }

}

Then this code is used elsewhere before making some request, supposedly to improve the user experience, for example before requesting some data.

guard await ConnectionService.isConnected() else {
    networkError = true
    return
}
await fetch()

The problem with this code is that it completely ignores how networking and HTTP actually work, makes the surrounding code messy, and in general solves no real problems, except for some imaginary concern for the user with a reminder to “turn on the internet.” But let’s go step by step why this is absolutely pointless and harmful.

First, HTTP has caching, and the response to this request may already be cached. This “helpful” check simply blocks the user from interacting with the app when the data is actually available, and you artificially refuse to show it.

Second, even in this contrived example there is already a problem. You are most likely handling errors incorrectly and making error handling more complex: you end up with pseudo-errors mixed together with real errors. At the very least, throw an exception and store it as your error state to display in the UI.

Third, the HTTP client itself will return a network error if it fails to connect to the server, including an error indicating that there is no internet connection. These are the errors that should be handled and displayed in the interface, if necessary.

Fourth, in practice, a “no network connection” state almost never occurs. Meanwhile, ordinary network errors (such as 404 or response timeouts), which for some reason are often not handled properly, happen all the time.

This code is convenient only for QA engineers, because they turn on airplane mode instead of simulating real network errors, and for developers, because they simply do not implement proper error handling.

Such classes can be used, but in other scenarios, for example, when an error has already occurred and you want to reset an automatic retry timeout so that you can immediately retry once the network is restored (a much rarer case). Another possible use is low-level network work (TCP/UDP sockets), but this kind of interaction is programmed very rarely and is a completely different story.

To summarize: in normal HTTP-based applications, just do not use this, and handle errors properly. And for those who use it this way, you can send them this article so they read it and stop doing this.

What to write about when you've run out of ideas?

I once read some opinion by some person on the internet saying that content has to be produced nonstop and that all trends become outdated within a day. That opinion received a huge amount of positive feedback, as if it were some big, important insight.

If I had the conscience to write such nonsense with such seriousness, I would just as seriously insult him personally for such stupidity or simply block him, but no.

Useful information doesn’t become outdated in a few hours; informational trash does. TikTok content that people forget about in a few minutes is actually negatively useful information.

Yet the first point is considered valid even by serious people, as if content must be produced nonstop. But ideas for content are not infinite.

So I decided to share a secret method for what to do when you’ve run out of ideas.

You don’t need to write anything and you don’t need to do anything if you don’t know what to do.

The need to consistently put something out every day/week/month is not for you or your audience. It’s for platforms that steal people’s attention using your content.

Build healthy relationships with those who read and watch you, instead of working for fucking platforms. And if you need to take a break, then take it: do something useful, go for a walk, watch a movie, and the idea will come on its own.

Just don’t forget to write it down somewhere.

What iOS 10 looked like

If you suddenly feel like experiencing nostalgia or talking about how things used to be better, this post is a perfect opportunity.

iOS 10 was released in 2016 and was the last operating system to support 32-bit processors. If the generational design break happened between iOS 6 and 7, then this was the most important technical transition.

Unfortunately, this operating system is now practically unusable, and there are no functioning third-party applications because of the 32-bit ARM architecture. Some Apple services also behave quite strangely. The browser is outdated as well, and many websites open incorrectly.

The Reminders app still feels somewhat skeuomorphic because of the background texture. Pay attention to Priority, it gives the impression of having been done in a rush. In the latest operating systems this is just a standard text selector (high, medium, low).

You can play an old version of Minecraft, read books, check the weather, and even listen to podcasts. I haven’t tried buying movies, but on such a small display it would be an interesting experience.

A funny moment: how AirPlay shows MacBooks and the HomePod.

An app that lists features from the new operating system looks cute. It seems to me that Apple still haven’t learned how to explain new things without making you open a browser.

The screenshots were taken in December 2023, and I only just got around to publishing them. I’m not sure whether even more services have stopped working since then. In fact, the situation with apps was much better back in 2021-2022, but now most of them have disappeared.

Earlier ↓