• Week 2023-17

    Personal

    I’m on Bluesky, this new social network created by another billionaire. I don’t have plans to create content there, my focus is, and always will be, Micro.blog. The plan is to cross-post what I write on my blog there, as I already do with Mastodon.

    • Bluesky: @otavio.cc
    • Mastodon: @otaviocc@social.lol

    I canceled Obsidian Sync and Obsidian Publish. The service is really good, but I’m not it’s worth the price for my needs. git works just fine for me.

    Work

    Monday is the day with most of my meetings, retrospective, pre-planning, planning, syncs with other teams, etc… But this time I attended all from home. I’m avoiding working from the office until tinyScientist’s surgery.

    Tuesday I took a day off to talk to doctors about tinyScientist’ surgery. And the rest of the week was just business as usual.

    Hacking

    I wasn’t planning on starting another feature in my omg.lol client before releasing it to the public, but recently I started using Pastebin more and more and thought it would be a good idea to implement it in my client.

    On Monday I worked on the PastebinNetworkService, responsible for the network requests for the feature.

    Tuesday, Wednesday, and Thursday I didn’t have time to work on the project, but on Friday I started the PastebinPersistencekService, responsible for persisting the snippets locally on Core Data.

    Gaming

    The reason I didn’t have much time to work on my app has a name: Cult of the Lamb.

    Photography

    Two weeks ago I traveled to Turkey for the company Workation and didn’t take my camera with me. I knew I would miss the opportunity to take amazing photos, but I wanted to pack light, using a single backpack. So, I challenged myself to take RAW photos using my iPhone instead.

    Everything I know about RAW is theoretical. I don’t shoot RAW and don’t know how to edit or “develop” photos of this format.

    I have Darkroom instead on my devices, because I bought the life-time license a long time ago, and decided to give it a try.

    Darkroom is magical. All the controls and features are so intuitive I was impressed by the results. I even created a Preset for me, so that I can apply the same look and feel to other “similar” photos.

    Will I use RAW on my camera? No. My Fujifilm camera is for SOOC photos only, but I might start shooting RAW more often on the phone.

  • Week 2023-16

    Personal

    Last week I arrived from the Workation on Sunday, and stayed at home most of the week. The first time I left home this week was on Thursday.

    Just to be on the safe side, I took several COVID-19 tests. tinyScientist’s surgery is less than a month from now, and we want to protect him from any sort of cold or flu.

    We were planning to take him to the zoo on Saturday to see the lions, but it was one of those rare sunny days, so the zoo was probably packed with kids, better to avoid it a while longer.

    Decided to walk in the city instead, found a park that’s open enough, and took tons of pictures. Also, coffee…

    Hacking

    I didn’t hack a single line of code on my personal projects. The only thing I did was to post on social network a crash I noticed on my omg.lol client, which was introduced in the latest macOS update. It’s super weird, and no one knows how to solve it. I submitted a bug to Apple.

  • Week 2023-15

    Personal

    Another short week in Germany 🇩🇪. On Monday, we had a holiday, Easter Monday, and, as the day was beautiful, we took tinyScientist to a park. It was his first time on a swing (for babies), and he loved it!

    I like visiting different places, but I hate traveling. A simple 3-hour flight means a whole day of preparations, trains, airport, security checks, etc… but we don’t have teleportation yet, so…

    Working? Vacationing? Hacking? Partying?

    This week we had our Workation and almost all of Nord Security traveled to a resort in Turkey 🇹🇷; more than 1000 people flew from different parts of the World to our 2023 gathering. That’s not a typo, 10^3+ people traveled to our event…

    Wednesday

    For me, it all started with a train ride to the airport. Although the airport is far from the city center, public transit works and the express train runs from the Hauptbahnhof to the airport terminal in about 20–25 minutes.

    Thursday

    We arrived at the resort around 2am after a long flight and bus ride from the Antalya airport to the hotel. The main restaurant and bar are open until 6am, so after checking in, we headed there for dinner.

    I missed the city tour in the morning, I was exhausted and barely remember turning off the alarm. I woke up around 10am for a late breakfast, followed by a visit to the Sea. And then back for lunch.

    There’s an hour time difference between Antalya and Germany, so after lunch I FaceTimed home to watch tinyScientist having his lunch.

    In the afternoon, I worked on my omg.lol for macOS with the most amazing view, and spent the rest of the day chatting with folks from work. Implemented the WebpageNetworkService for the Webpage feature, responsible for all the network calls.

    After dinner, I went to my room to FaceTime scientist and tinyScientist again.

    Friday

    On Thursday, I went to bed around 9 or 10 pm, planning to wake up earlier on Friday to see the sunrise. I got up around 5:30 am, packed my things and walked to the private beach. Since almost everyone was partying on the night before, I was the only one on the beach in the morning.

    Once I was done taking pictures, I sat in a chair, listening to the Mediterranean Sea (and some birds). Priceless. I can’t remember when was the last time I did something like this. Good thing my AirPods Pro had no juice.

    Another good reason to wake up early: breakfast before everyone else. I didn’t make healthier choices and ate scrambled eggs, egg with vegetables and boiled potatoes.

    I spent the rest of the morning socializing with teammates until lunch, which I skipped. The good thing is that all restaurants in the hotel are open 24/7, so having a late lunch is not a problem.

    I found time to do some more work on my omg.lol client. Implemented the WebpagePersistenceService and the WebpageRepository. The first responsible for storing the webpage (and history of changes) on Core Data, and the latter the abstraction on top of the network and persistence services. The glue.

    Found a Starbucks inside the resort, next to one of the pools. Since this is an all-inclusive resort, Starbucks is also included. Perfect for cold drinks next to the beach or a pool.

    At 4pm, we had our Q1 OKRs meeting with the entire company, followed by dinner and the OKR party. The OKR meeting was the only work part of the workation.

    But before the party, I finished the Webpage Feature implementation and can now update otavio.lol using it.

    Saturday

    I intended to take more pictures of the sun rising, but I overslept and woke up at 6:30. At that time, the sun was already out there, so I went straight to breakfast. Omelet, bread, and cheese.

    And then headed out to my favorite spot in the whole resort.

    Implemented the SwiftUI previews for the Webpage UI and then joined some folks from my team by one of the pools, drinking an Iced Coffee Americano from the hotel’s Starbucks.

    I felt guilt for my food choices and decided to eat better during lunch.

    After lunch, I must have coffee, so I did. After grabbing another Starbucks, I took the opportunity to walk the resort a bit and found this cat – one of many cats that live in the hotel – staring at me.

    I spent the rest of the day with my team, chatting and having dinner and some baklavas.

    When the party started, I went to my room to talk to scientist and tinyScientist, pack my bag, and sleep.

    Sunday

    Homecoming day. I woke up early again, and had a nice walk on the beach, followed by a long, unhealthy breakfast. Our bus was scheduled to leave for the airport at lunchtime, so I might have eaten more than needed to make up for the lunch I was about to miss.

    Spent the morning with the folks from the team and left the resort to visit some shops and buy souvenirs and Turkish tea to take back home. My bag was already packed from the day before, but I had to take everything out and repack it to make room for the tea set I bought.

    I traveled with a single backpack (a big one, the limit for taking on the cabin), but after putting the tea set in it, it weighed about 10 kg. The limit is 8 kg, and I was afraid someone would weight it at the airport, but no one did, so it was fine.

    I was so exhausted I didn’t feel the trip home. It was almost like teleportation finally existed. The workation was spectacular, something I’ve never done before, but it’s good to be back home. I missed scientist and tinyScientist, and seeing them again was a relief. The day ended with us giving tinyScientist a bath.

  • Week 2023-14

    Personal

    tinyScientist got a new bed. They grow so fast!

    After using so many Read Later services in the last decade, I believe I can finally stop looking for the right one for me. Readwise’s Read works the way I need a Read Later app to work. Both their apps, Readwise and Read, have performance and UX issues, but they work, deliver what they promise, and are always improving. I was skeptical with all those YouTube videos praising the service, but I decided to give it a try and am impressed.

    Last week I migrated all my personal notes from Obsidian to Bear, but maybe I was too eager to try Bear’s update that I rushed things. Thinking again, Obsidian (and some other apps) offer something Bear doesn’t: access to the notes without having to use the their app. Obsidian uses simple Markdown files in the file system, while Bear stores all notes and assets in a sqlite database, hidden away from the user.

    Portability is an important thing nowadays, especially with so many apps hiding their features behind subscriptions. With simple Markdown files I can edit my notes with anything, including my beloved Vim.

    Work

    Another short week at work, 7th of April was the first day of Easter (Good Friday). But I went to the office twice, on Monday and Wednesday, for 1-on-1s with the folks from my team, to support them with their Development Plans.

    Hacking

    I had plans to work on the Webpage Feature of my native omg.lol client for macOS, but decided to work a little bit on Micro.publish, my Obsidian plugin to post to Micro.blog.

    Readwise’s plugin for Obsidian automatically fetches highlights and notes when Obsidian launches, so I decided to do the same for Micro.blog categories. Now, when the user opens Obsidian, it fetches the categories one has on their blogs.

    The command to force fetch categories is still present and can be used at any time to retrieve categories (e.g., in case new categories were added directly to Micro.blog while Obsidian was open).

    Another feature I’ve added is the ability to schedule a post for a future date. I usually don’t scheduled my posts, but it’s a nice feature to have.

    I’ve also updated my blog’s colors to match the Obsidian dark theme I use, and used Xcode dark colors for the code snippets. Feels much more natural to me now.

    Photography

    During the week I published another page to my Digital Garden, Obscura, where I detail the setup I have to take Monochrome photos using the app.

    Later during the week I updated the page to include the missing piece in my setup, which was causing Glass not to show the EXIF of my photos.

    After talking to Ben, Software Engineer behind Obscura, we found what was missing, and now my setup is exactly the way I wanted it to be: Monochrome photos by default, saving JPEG SOOC, and with EXIF preserved, ready to share online (or not).

  • Week 2023-13

    Personal

    tinyScientist is officially six months old. I was browsing some of his early pictures and it’s crazy how much things have changed in such a short time. This week he tried Parsnip and Potato. He loved the first, but didn’t enjoy the latter that much. Pumpkin is still this favorite.

    Browsing Disney+ I found Devs, a Hulu TV Show I had no idea existed. It’s an 8-episode miniseries about free will and determinism where Nick Offerman runs a tech company with quantum technology to… It doesn’t matter, it’s Nick Offerman. Interesting show, watched the entire thing.

    Last week Bear released Bear 2 Beta on TestFlight and, as a big Bear fan, I downloaded it to try. The editor was rebuilt from scratch, adding features users were asking for a long time. One of my favorite features is backlinks, something all Obsidian users know well. Currently Bear supports [[WikiLinks]], allowing one to link another note (as Obsidian does), but the new version implements backlinks, showing which pages link the one selected. It also shows pages that mention the page but don’t link them (Unlinked Mention). This is big! I exported all my Zettelkasten notes from Obsidian and imported on Bear and will use it for now on. And it’s a native application, and I’m a sucker for good native applications.

    I’m using Arc from The Browser Company as default browser in my work computer for a few months, and I’m loving it. The way the tabs are managed, the different spaces, and profiles, the pinned tabs, etc… I’m the kind of person who can’t handle multiple tabs open at the same time, quickly getting lost in a see of tiny favicons. Arc changed that completely, I can easily keep several sites open simultaneously and, more importantly, find my way in it.

    Thursday Arc released Arc for iPhone. It’s not a browser but a bookmark manager. It gives access to Spaces, Easel, and Notes one has on their computer. And offers the ability to create new tabs by importing URLs from other apps via the iOS’ share sheet. Still not sure how useful it is; time will tell.

    Work

    Most of the time I work from home, but if there’s a day I like to go to the office, this day is Monday. It’s the day I have most of my meetings (Retrospective, Planning, 1-on-1s), and it’s the day the kitchen is restocked. Big day.

    On Wednesday we had a public Meshnet Meetup, open to the community where the team talked about the feature and technical details and concepts on networking and how the whole architecture was designed and built. My macOS team works on Meshnet, and I’m happy to see the company promoting and talking about what we’re building openly. We even released some of the libraries as open source software. And, as in most meetups, tons of pizza and beer.

    During March I experienced 4-day workweeks, but this week I had two days off, Thursday and Friday. Finally used all my vacation days from 2022.

    Hacking

    I finished the Now Pages support on my omg.lol app for macOS. The implementation allows the user to update their Now Pages and keeps a history of previous edits, in case the user wants to roll back a version.

    Additionally, I’ve added a way to share the Now Page as a Status in the Statuslog.

    Next week I plan to add the Webpage feature, so that users can edit their pages. Once that’s done, I will decide on how to release the application. I don’t know if Open Source or not, if AppStore or direct download (as Micro.blog and NetNewsWire). But I want to get it out before implementing the blog support, which will be a bit more complex.

    Photography

    First year I managed to complete Micro.blog’s March Photo Challenge. I created a category and added all my photos there,. These are my favorites:

    • Solitude - JPEG, SOOC with a Fujifilm X100V
    • Engineering - JPEG, SOOC with a Fujifilm X100V
    • Road - JPEG, SOOC with a Fujifilm X100V
    • Portico - JPEG, SOOC with a Fujifilm X100V
    • Court - JPEG, SOOC with Obscura 3
    • Support - JPEG, SOOC with Obscura 3

    I’m planning to write a blog post (or Digital Garden entry) about Obscura 3, the setup I have, and why it’s my favorite iPhone camera app.

  • Week 2023-12

    Earlier this week I deleted another internet account: OpenAI.

    We - Nord Security - hosted the CocoaHead Berlin on Wednesday. It was great, full house, and we had too speakers from Nord Security. Bob from NordLocker talked about SwiftUI and Ieva from NordVPN about fastlane, screenshot automation and the iOS strategy to inject mock data for App Store screenshots.

    Speaking of work, on Thursday I had a long training on Leadership (and Management) at the company. Earlier this year I got a new title/role, going from Senior Software Engineer to Engineering Team Lead, which means I’m half of the time Software Engineer and the other half Engineering Manager. It’s a brand new world to me, so I went back home exhausted. There’s a lot to process.

    On Friday I had another day off, still taking 2022 vacation days before the end of March (German regulations, vacation days from a given year have to be spent before end of March of the following year). I took tinyScientist to a coffee shop around 9am, where we had coffee with a friend and talked about iOS and macOS development, leadership and management, and layoffs. tinyScientist got bored with all the talk and decided to take a nap in his kinderwagen.

    In the previous weeks I finished two features of my omg.lol native client: Statuslog and PURLs, and during this week I started working on the Now Page. I still don’t know how I’m gonna distribute the app, if via AppStore, or directly for download as NetNewWire and Micro.blog do. Same goes for pricing, I still haven’t decided if it’ll be a paid app or not.

    Finally, the most important thing which happened during the week: tinyScientist had his first solid meal. We were not expecting him to eat (we thought he would only play with the food), but he loved pumpkin. As result, he had pumpkin four days in a row for lunch.

  • Week 2023-11

    On Tuesday I returned from a 10-day vacation, and on Friday I had another day off. In Germany there’s no carry over from one year to another, and the deadline to spend the vacation days from the previous year is end of March. As I still have vacation days from 2022 that I’m forced to take, I’m taking all the Fridays off until the end of the Month.

    During the week we took tinyScientist to the doctor to do some extra tests he’ll for his surgery. We’re excited for him, but anxious as hell. I have faith in Science and in the doctors here. The support he’s getting from them is spectacular.

    After months waiting for tinyScientist’s Kindergeld, it has finally arrived. Bureaucracy in Germany is a big thing, but at the end everything works as expected.

    Last weekend I finished the modular architecture for my omg.lol client for macOS. The first feature I built while working in the architecture was the Statuslog. During the week, with just a few hours here and there I added PURLs. It’s a good indicator the modular architecture is a success. My goal was to achieve full decoupling in a way it should be easy to extend the application and add new features.

    I started playing Octopath Traveler II. The first game was amazing, and I ordered the second game as soon as they started selling it.

  • Week 2023-07

    It wasn’t a good week. ADHD decided to show up. My head wouldn’t stop and I got nothing (good) done. But at least I published a digital garden page about Fakes and Nórdico, a theme for the Mastodon app Mona.

  • Week 2023-06

    Braille Institute released a font for low vision readers, Atkinson Hyperlegible. The font is great and I asked Matter to add it to their list of supported fonts. Matter accepted my requested and within a couple of hours they deployed the changes. I’ve update my blog to use Atkinson Hyperlegible as well.

    I keep going back-and-forth between Reminders and Things. Every time there’s a new macOS version I go back to Reminders to see what changed. But the truth is, Things is an amazing app and I decided to go back to it. The best of all, I can synchronize Things on all the computers I use, no matter if they’re using the same Apple ID or not. Reminders use iCloud, so I’m limited to the Apple ID I’m using. The downside is that I had to pay for Things twice.

    Same goes for Safari and Firefox. I’m a big Firefox fan (since it was called Phoenix) and love the Multi-Account Containers feature, but Safari is so much faster and has better support for physical keys and Apple Pay so I keep going back to it.

    I read a review about ReadKit and decided to give it a try. I deleted Reeder from my devices to force myself to use it. The application is actually nice and I’m enjoying it a lot. I like the way it integrates with Pinboard as well, which is a service I use for about 15 years. It supports Smart Folders, a great feature when subscribing to tons of feeds.

    Decided to create a mini Digital Garden on Micro.blog. It’s not perfect because I have to create all the links between pages manually. But at least all the content is hosted in my blog. I have to check if there’s an API to post Pages on Micro.blog directly, and add an option to my Micro.publish plugin so that people can publish posts and pages from Obsidian. For now, the digital garden has notes about Unit Testing, Open Source, and Child Benefits in Germany.

    I released a minor change to Micro.publish plugin, including a link to my Buy My a Coffee page. I added a link to Buy My a Coffee in all my mini Digital Garden pages as well.

    We’re cat sitting for friends and I usually go to their house at night, after tinyScientist goes to bed. I’m using my time there to work on my omg.lol native client for macOS and iOS. My client is fully modularized and each feature is a micro app on its own. Once all the setup is done, the app should scale pretty well, and adding new omg.lol features should be a breeze. I plan to write about Modularization once the setup is finished.

    I finally started playing Fire Emblem Encore, but already have my eyes on Metroid Prime Remastered. I must resist, in two weeks there’s Octopath Traveler II and in May, The Legend of Zelda: Tears of the Kingdom.

    LEGO Series 24 is complete. We went to a LEGO store and they had hundreds of minifigure bags. Spent a good amount of time there, feeling the bags and found the last two.

    And finally, I had to use Twitter to talk to my ISP’s support. Their service is too unstable and every now and then I have to contact them to complain (and ask for my money back). Maybe it’s time to switch providers so that I don’t have to use Twitter anymore.

  • Week 2023-05

    Earlier this week I took my parents to the airport and had the opportunity to work on my side project on the train on my way back home. For some reason I enjoy coding on trains, buses, and airplanes. Especially when it’s raining outside. A wonderful combination of white noise sounds, I suppose.

    tinyScientist went to the doctor for his U-Untersuchungen and took some other vaccines. My wife has a Phd in vaccine development working with viruses, so of course tinyScientist won’t miss a single shot.

    On Tuesday I returned to work after two weeks off and had my contract updated to match my new title: Engineering Team Lead. Never have I imagined I would take such kind of role, but the setup we have in the company works so well that I decided to accept the challenge. I’m glad I have the time to write code and lead a team.

    Internet in Germany has its ups and downs; probably more downs than ups. This week my Internet Provider gave me money back because the service is too unstable in my area, I had to use my phone as personal hotspot in 30% of the meetings I attended.

    Earlier this year LEGO released the Series 24 of its minifigures. We went to a store to feel the packages, as we always do, and found 10 minifigures. Still missing 2.

    And finally, I canceled Audible. The local public library is great and offers thousands of audiobooks via Libby for 10 EUR/year.

  • Week 2023-04

    • Had my parents visiting us during the week and visited several museums with them
    • Finished removing all my followers on Twitter
      • It took a while since I had thousands of followers (from a remote time when I had a popular podcast back in Brazil)
    • Deleted more accounts:
      • Pinterest
      • Tumblr
      • Stack Overflow
      • Git Tower
      • mastodon.social
    • Added support to a new statuslog.lol feature to my native macOS and iOS client, which allows users to reply to statuses via Mastodon
    • Used my page and /now pages as base to create the theme for my weblog.lol
  • SwiftUI Navigation

    With a couple of lines of code I migrated my app from NavigationView to NavigationSplitView, which offers much better navigation in SwiftUI.

    Pinboarding sidebar visible

    And I got for free the ability to show/hide the sidebar.

    Pinboarding sidebar hidden

    Apple has a pretty good session about NavigationSplitView and NavigationStack on their developer portal. The jokes and puns are awful, but the content is really good.

  • Micro.publish

    Obsidian is the source of truth for most of my writings1 and I though it would be interesting to post to Micro.blog directly from it. In the past I explored several applications such as Ulysses, iA Writer, and many others which allow publishing to Micro.blog, but they don’t work for me, they don’t reflect the way I think and take notes. Obsidian does.

    So I searched on Micro.blog and found this post from @philbowell:

    I wonder if there is a way to post to Micro.blog from Obsidian. 🤔

    And nope, there isn’t. So I took the challenge.

    Obsidian isn’t a native application and I can’t remember when it was the last time I wrote something which isn’t compiled to run on an specific architecture 👨‍💻, so I had to learn TypeScript and npm, and spent a good amount of time making Visual Studio Code look pretty (stealing ideas from my friend Atila).

    Luckily for me, the TypeScript syntax isn’t that different from Swift, so in a matter of hours I had something working. Since the Obsidian Plugin architecture is OOP, I decided to go with OOP using MVVM, without third-party dependencies to avoid taking unnecessary complexity into Obsidian.

    The result of all this is Micro.publish, my Obsidian plugin to publish to Micro.blog. The plugin isn’t available to install from Obsidian’s Community Plugins yet since they review all the plugins there, but it’s possible to install it directly from GitHub by following some manual steps.

    Micro.publish has the features I need. It allows

    • setting default tags
    • setting the default post visibility
    • setting a default blog (for those with more than one M.b blog)

    And before publishing, it’s possible to override these default settings for the post being published.

    More features are planned, and will be coming soon.


    1. DayOne, don’t worry I won’t dump you. ↩︎

  • Micro.blog Publish for Obsidian

    First version of my Micro.blog plugin for Obsidian is ready 👨‍💻 Some feature are missing - as expected for a first release -, but I plan to add them in the future. E.g.,

    • Blog selection
    • Set categories/tags

    Currently it allows users to

    • Set the default post visibility for new posts
    • Set the post title
    • Open post, preview, and edit URLs after publishing the post

    The plugin can be found on GitHub. The next step is to submit it to Obsidian’s Community Plugins.

    Important: The name isn’t final since this is not an official Micro.blog product. I have to find a better name for it.

    Some screenshots:

    Preferences

    Command

    Review

    Confirmation

  • Leather work for my EDC

    Two weeks ago I decided to get my leather tools in the cellar and make something. Since I have more than one pouch - a small one, and a bigger when I’m carrying my Fuji - I decided to make something to put my pocket notebooks - which I also make -, cards, pocket knife, and flashlight. This way it’s easier to move all my EDC from one pouch to another.

    Leather work is fun, and for me it works for medidation or to listen to podcasts I need to focus on.

    I’m pretty happy with the final result. Below the pocket knife and flashlight.

    And the one for pocket notebook, cards, and small pen.

    I’ve also made a strap for FFP2 masks, because I hate those elastics around my ears.

  • Pair Programming, TDD, and Rotation?

    I moved the content to my Digital Garden and can be found here.

  • Reachability using NWPathMonitor and Combine

    About a month ago I decided to build a new app to attend a personal need, a desktop client for Pinboard, a service I’ve been using for over a decade to store and manage my bookmarks.

    As any application which requires internet to fetch and update data, my client needed a way to detect network accessibility. For a long time the strategy adopted by engineers in this situation was to rely on Apple’s Reachability class, a sample code the company used to provide for download on their developer portal. Well, either that or one of the many CocoaPods frameworks the community built to replace Apple’s drag-and-drop solution. But since iOS 12.0 and macOS 10.14 there’s a powerfull first-party alternative.

    Introduced a couple of years ago, NWPathMonitor is the easiest way to detect network changes (and retrieve connection properties). Its interface is extremely simple to use, providing a callback to get notified of updates.

    let monitor = NWPathMonitor()
    let queue = DispatchQueue.global(qos: .background)
    
    monitor.pathUpdateHandler = { path in
        print(path.status) // .unsatisfied, .satisfied, .requiresConnection
    }
    
    monitor.start(
        queue: queue
    )
    
    // ...
    
    monitor.stop()
    

    My application in built in SwiftUI (2.0) and Combine and the solution above wouldn’t feel right in my View Model. So I started with a simple wrapper, hiding the monitor and exposing a publisher which notifies path changes.

    let wrapper = NWPathMonitorWrapper()
    
    let cancellable = wrapper
        .pathUpdatePublisher()
        .receive(on: RunLoop.main)
        .sink { path in
            print(path.status)
        }
    
    wrapper.start()
    

    But the implementation (below) still requires NWPathMonitorWrapper.start() to be called, hurting the beauty of the publisher-subscriber relationship.

    public final class NWPathMonitorWrapper {
    
        // MARK: - Properties
    
        private let monitor: NWPathMonitor
        private let queue: DispatchQueue = .global(qos: .background)
        private let pathUpdateSubject = PassthroughSubject<NWPath, Never>()
    
        // MARK: - Life cycle
    
        public init(
            monitor: NWPathMonitor = NWPathMonitor()
        ) {
            self.monitor = monitor
            self.monitor.pathUpdateHandler = {
                self.pathUpdateSubject.send($0)
            }
        }
    
        // MARK: - Public
    
        public func start() {
            monitor.start(
                queue: queue
            )
        }
    
        public func stop() {
            monitor.cancel()
        }
    
        public func pathUpdatePublisher() -> AnyPublisher<NWPath, Never> {
            pathUpdateSubject
                .eraseToAnyPublisher()
        }
    }
    

    So I looked for a similar example in Apple’s frameworks, one which requires a method to be called to trigger the action. Turns out URLSession, familiar to every iOS/macOS engineer, is the perfect example since it requires URLSession.resume() to be called to fire the network requrest. Recently URLSession got a new method, a publisher which starts the request at the moment there’s demand (a subscriber):

    let session = URLSession.shared
    
    let cancellable = session
        .dataTaskPublisher(for: request)
        .receive(on: RunLoop.main)
        .tryMap { result in
            // ...
        }
        .sink(
            receiveCompletion: { _ in },
            receiveValue: { _ in }    
        )
    

    And that’s the interface I wanted for my monitor. A simple publisher which starts emitting values as soon as there’s demand.

    let monitor = NWPathMonitor()
    
    let cancellable = monitor
        .pathUpdatePublisher()
        .receive(on: RunLoop.main)
        .sink { path in
            print(path.status)
        } 
    

    SwiftUI and Combine are new to me, I spiked and re-implemented some UI components I have up in my sleeve, but nothing too complex. Building this client is helping me to explore and learn new APIs.

    The protocols Publisher, Subscriber, and Subscription are examples of these APIs. To implement a custom publisher it’s necessary to implement types conforming to two of these protocols.

    First, a quick recap:

    • Publisher is the type which emits events over time,
    • Subscriber is the type which receives events published by the publisher, and
    • Subscription implements the link between publishers and subscribers.

    The first step was to define my publisher interface. Since the monitor requires a queue to run, I decided to pass the queue as argument. In order to simplify its interface, a background queue is passed by default to the implementation.

    extension NWPathMonitor {
      
        public func pathUpdatePublisher(
            on queue: DispatchQueue = .global(qos: .background)
        ) -> NWPathMonitor.PathMonitorPublisher {
          // ...
        }
    }
    

    The interface provides a hint of the first type to be implemented, NWPathMonitor.PathMonitorPublisher. Most of the code is boilerplate, and for this publisher, the Output expected is NWPath, without a Failure.

    When the publisher receives a subscriber, the link between them is established.

    extension NWPathMonitor {
       
      public struct PathMonitorPublisher: Publisher {
    
            // MARK: - Nested types
        
            public typealias Output = NWPath
            public typealias Failure = Never
        
            // MARK: - Properties
        
            private let monitor: NWPathMonitor
            private let queue: DispatchQueue
        
            // MARK: - Life cycle
        
            fileprivate init(
                monitor: NWPathMonitor,
                queue: DispatchQueue
            ) {
                self.monitor = monitor
                self.queue = queue
            }
        
            // MARK: - Public
        
            public func receive<S>(
                subscriber: S
            ) where S: Subscriber, S.Failure == Failure, S.Input == Output {
                let subscription = PathMonitorSubscription(
                    subscriber: subscriber,
                    monitor: monitor,
                    queue: queue
                )
        
                subscriber.receive(
                    subscription: subscription
                )
            }
        }
    }
    

    The Subscription fulfills the demand from the subscriber. As soon as there’s demand, it starts monitoring changes using the monitor’s callback, passing changes to the subscriber. And since Subscription conforms to Cancellable, cancel() can be used to stop the monitor.

    extension NWPathMonitor {
    
        private final class PathMonitorSubscription<
            S: Subscriber
        >: Subscription where S.Input == NWPath {
    
            // MARK: - Nested types
      
            private let subscriber: S
            private let monitor: NWPathMonitor
            private let queue: DispatchQueue
      
            // MARK: - Life cycle
      
            init(
                subscriber: S,
                monitor: NWPathMonitor,
                queue: DispatchQueue
            ) {
                self.subscriber = subscriber
                self.monitor = monitor
                self.queue = queue
            }
      
            // MARK: - Public
      
            func request(
                _ demand: Subscribers.Demand
            ) {
                guard
                    demand == .unlimited,
                    monitor.pathUpdateHandler == nil
                else {
                    return
                }
      
                monitor.pathUpdateHandler = { path in
                    _ = self.subscriber.receive(path)
                }
      
                monitor.start(
                    queue: queue
                )
            }
      
            func cancel() {
                monitor.cancel()
            }
        }
    }
    

    With all in place, the View Model becomes extremely simple and elegant. Below a View Model which publishes a boolean indicating the connectivity status to the View.

    final class ViewModel: ObservableObject {
    
        // MARK: - Properties
    
        @Published var isConnected: Bool = false
        private var monitorCancellable: Cancellable?
    
        // MARK: - Life cycle
    
        init(
            pathMonitorPublisher: NWPathMonitor.PathMonitorPublisher
        ) {
            monitorCancellable = pathMonitorPublisher
                .receive(on: RunLoop.main)
                .map { $0.status == .satisfied }
                .assign(to: \.isConnected, on: self)
        }
    }
    

    Overall, I’m happy with the result, and am looking forward to using Combine (and SwiftUI) in production.

    Resources:

  • How to replace type methods in Swift to improve testability

    I moved the content to my Digital Garden and can be found here.

  • Map, Filter, Sort, and Reduce in Objective-C

    I strongly believe that the best way to learn something new is by experimenting and practicing it.

    A few months ago I was studying Functional Programming in Swift. Swift is a modern, powerful, and safe language. Its syntax is so simple and elegante that it works as an invitation to dive into the Functional Programming world.

    After practicing Functional Programming in Swift for a few weeks, I decided to try something a little bit different. I decided it was time to experiment with curried functions, map, filter, sort, and reduce in Objective-C.

    Swift collections implement map, sort, filter, and reduce, so the first step on my experiment was to reimplement these in a NSArray category, trying to match the method signature that Swift implements.

    • Map: given a transform operation, it returns a new NSArray where all its elements are transformed according to the operation. The transform operation takes an object and returns another object (that might be of a different type of the original object).
    • Filter: given a condition operation, it returns a new NSArray where all its elements satisfy the condition. The condition operation takes an object and returns a boolean.
    • Sort: given a sort condition, it returns a new NSArray where all its elements are sorted according to the condition. The sort condition takes two objects and returns a boolean.
    • Reduce: given a combine operation, it returns a recombined object by recursively processing its constituent parts. The combine operation takes the initial state and an object and returns a recombined object which is of the same type of the initial state.

    The NSArray category ended up with the following interface:

    typedef id(^Transform)(id);
    typedef BOOL(^Condition)(id);
    typedef BOOL(^SortCondition)(id, id);
    typedef id(^Combine)(id, id);
    
    [@interface](https://micro.blog/interface) NSArray (MFSR)
    
    - (NSArray *)map:(Transform)transform;
    - (NSArray *)filter:(Condition)condition;
    - (NSArray *)sort:(SortCondition)isOrderedBefore;
    - (id)reduce:(id)initial andCombine:(Combine)combine;
    
    @end
    

    To test the interface above and its implementation, I started with a immutable array of movies — James Bond movies — loaded from a plist file.

    NSArray<Movie *> *movies = [[NSBundle mainBundle] moviesFromPlist];
    

    Where each movie contains a title, the actor’s name who played Bond, and some additional information about about the flick. The movie interface is defined as:

    @interface Movie : NSObject
    
    @property (nonatomic, readonly) NSString *title;
    @property (nonatomic, readonly) NSString *actor;
    @property (nonatomic, readonly) NSInteger year;
    @property (nonatomic, readonly) CGFloat boxOffice;
    @property (nonatomic, readonly) CGFloat budget;
    @property (nonatomic, readonly) CGFloat tomatometer;
    
    @end
    

    Sorting the array of movies requires a sort condition that takes two movies and returns a boolean that represents the relationship between them. So, for sorting all the British Secret Service agent movies by budget:

    BOOL(^byBudget)(Movie *, Movie *) = ^BOOL(Movie *a, Movie *b) {
        return a.budget > b.budget;
    };
    
    NSArray<Movie *> *moviesByBudget = [movies sort:byBudget];
    

    Simple.

    For filtering, the method requires a movie and returns true when the movie matches the condition and false otherwise. Below, a simple way to create an immutable array containing the Sean Connery movies.

    BOOL(^isConnery)(Movie *) = ^BOOL(Movie *a) {
        return [a.actor isEqual:@"Sean Connery"];
    };
    
    NSArray<Movie *> *conneryMovies = [movies filter:isConnery];
    

    But here is the tricky part. To filter movies played by other actors, more blocks like the one above would be required. The functional way to address this is using curried functions — popular technique in Swift (and in other Functional Programming languages).

    Since filter expects a block that takes a movie and returns a boolean, another function is required, where its output is a function matching this signature.

    The new function takes a string (actor’s name) and returns a function that takes a movie and returns a boolean.

    typedef BOOL(^FuncMovieToBool)(Movie *);
    
    FuncMovieToBool(^isActor)(NSString *) = ^FuncMovieToBool(NSString *actor) {
        return ^BOOL(Movie *movie) {
            return [movie.actor isEqual:actor];
        };
    };
    
    NSArray<Movie *> *actorMovies = [movies filter:isActor(@"Daniel Craig")];
    

    Without any changes to the filter method signature or implementation, it’s possible to filter the array of movies to get a list of movies played by any actor on the big screen.

    Finally, it’s possible to combine all these functions to achieve the desired result. Let’s say I want the movies were Pierce Brosnan played James Bond, sorted by ratings and reduced to a list.

    NSArray<Movie *> *moviesByRatings
        = [[movies filter:isActor(@"Pierce Brosnan")]sort:byRatings];
    
    NSString *description
        = [moviesByRatings reduce:@"Brosnan movies sorted by ratings:"
                       andCombine:^NSString *(NSString *initial, Movie *movie) {
            return [NSString stringWithFormat:@"%@\n* %@ (Tomatometer: %@)",
                                              initial,
                                              movie.title,
                                              @(movie.tomatometer)];
        }];
    

    Voilà:

    Brosnan movies sorted by ratings:
    * GoldenEye (Tomatometer: 82)
    * Die Another Day (Tomatometer: 57)
    * Tomorrow Never Dies (Tomatometer: 57)
    * The World Is Not Enough (Tomatometer: 51)
    

    But putting all the fun and excitement aside, would I use these methods on an Objective-C project? Probably not — NSArray already implements methods for sorting and filtering arrays using NSPredicate.

    My point here was to experiment and practice map, filter, sort, reduce, and curried functions. Programming requires practice. And practicing these techniques and methods (and reimplementing them) in Objective-C improved the way I use them in Swift.

  • Curried Function in Objective-C

    Function Currying and uncurrying have been extensively discussed since Apple introduced Swift in 2014. I won’t extend myself on this topic because there are some great resources about it on the Internet.

    But, shortly, what is currying? It’s a technique that transforms a function that takes several arguments into a sequence of functions, each with a single argument. The result is a chain of functions returning functions. It’s beautiful, it’s mathematics!

    Below, a simple example in Swift:

    func add(_ a: Int) -> (Int) -> (Int) -> Int {
        { b in { c in a + b + c } }
    }
    
    let addTwo = add(2)               // Int -> Int -> Int
    let addFive = addTwo(3)           // Int -> Int
    let result = addFive(4)           // 9
    
    print("result = \(result)")       // result = 9
    

    and the same example in Objective-C:

    typedef NSInteger(^FuncInt2Int)(NSInteger);
    typedef FuncInt2Int(^FuncInt2Int2Int)(NSInteger);
    
    FuncInt2Int2Int(^add)(NSInteger) = ^FuncInt2Int2Int(NSInteger a) {
        return ^FuncInt2Int(NSInteger b) {
            return ^NSInteger(NSInteger c) {
                return a + b + c;
            };
        };
    };
    
    FuncInt2Int2Int addTwo = add(2);        // Int -> Int -> Int
    FuncInt2Int addFive = addTwo(3);        // Int -> Int
    NSInteger result = addFive(4);          // 9
    
    NSLog(@"result = %ld", result);         // result = 9
    

    The Swift version is minimal and elegant. But the Objective-C one has its charm, doesn’t it?