Improving on the common AnyCancellable .store(…) pattern.

If you’ve been reading any Combine documentation or code, you’ve probably seen code like this before:

let newPhotos = photos.selectedPhotos newPhotos
  .map { [unowned self] newImage in
    return self.images.value + [newImage]
  }
  .assign(to: \.value, on: images)
  .store(in: &subscriptions)

Pretty standard: call something that returns a publisher, map the results to whatever type you need, assign that value to an instance variable, and finally, store the resulting AnyCancellable into a Set named subscriptions which is an instance variable or property in the enclosing scope. In this case we’re in a UIViewController subclass where it’s defined as:

private var subscriptions = Set<AnyCancellable>()

This code is from the book Combine: Asynchronous Programing with Swift by the raywenderlich.com tutorial team (writers Scott Gardner, Shai Mishali, Florent Pillet, & Marin Todorov on this book, and designer Vicki Wenderlich). It’s a good book on Combine and this code is from the project in Chapter 8.

The problem with this pattern in this particular case, and other similar situations, is that the AnyCancellable is never removed from the subscriptions Set and so it’s a memory leak. Sometimes this doesn’t matter – when the enclosing scope is going to go away and the code is only called once relatively soon before exiting scope cleans up memory. But sometimes, as in this sample project, the user can do the action invoking this code (adding photos in this case) repeatedly and each time the .store(...) call adds another AnyCancellable to the Set.


An easy solution is to add an instance variable to your class (a UIViewController in this case):

private var newPhotosSubscription: AnyCancellable?

and then change the code above to something like this so the AnyCancellable is assigned to the instance variable and the .store(..) part is removed:

self.newPhotosSubscription = newPhotos
  .map { [unowned self] newImage in
    self.images.value + [newImage]
  }
  .assign(to: \.value, on: images)

When this assignment happens it frees the previous value, if any, and thus there will be at most a single AnyCancellable kept around.

Ok, that works. But I’m a perfectionist and that single AnyCancellable hanging around bugged me. That’s fixable, but as I went and fixed this issue in more places in the project there was a proliferation of instance variables as I had to add one for each place this came up.

So here’s a solution I came up with to avoid that.

First, add an extension on AnyCancellable:

extension AnyCancellable {
  func store(in dictionary: inout [UInt64: AnyCancellable], 
             for key: UInt64) {
    dictionary[key] = self
  }
}

Then change the subscriptions instance variable to be a matching Dictionary (and remove the individual instance variables you added for each case previously, if you did that already):

private var subscriptions = Dictionary<UInt64, AnyCancellable>()

In our original function which created the newPhotosSubscription and let the user choose photos, change the code to:

let key = DispatchTime.now().uptimeNanoseconds
 
newPhotos
  .handleEvents(receiveCompletion: { [unowned self] _ in
    self.subscriptions.removeValue(forKey: key)
  })
  .map { [unowned self] newImage in
    self.images.value + [newImage]
  }
  .assign(to: \.value, on: images)
  .store(in: &subscriptions, for: key)

So above we created a Dictionary instead of a Set for our subscriptions instance variable that holds on to the AnyCancellable so it remains allocated while needed. The dictionary allows us to store the AnyCancellable under a unique key, which in this case is a UInt64. On the first line above we create that key and assign it the number of nanoseconds since the device rebooted.

Then we add a .handleEvents operator to the subscription pipeline. Once the publisher has sent the .finished or .failure completion event we no longer need to keep the AnyCancellable around. Our receiveCompletion closure code removes the AnyCancellable stored under the captured key from our subscriptions instance variable.

(Note: if you preferred, you could replace UInt64 with UUID in the dictionary declaration and the AnyCancellable extension. Then instead of setting the key value to DispatchTime.now().uptimeNanoseconds you could generate a new UUID with UUID(). All that matters is that the is a unique value conforming to Hashable and doesn’t cost too much to generate).


I’m relatively new to Combine, so if you know a better way to do this, I’d love to hear about it!

Swift 5 Encodable nested and hoisted JSON

Ran into an Encodable situation that puzzled me for a bit today.

For those who don’t want to read much, the key observation is that try foo.encode(to: container.superEncoder(forKey: key)) will DELETE any existing key node in the container and the encode foo into a new node with that key. However, nestedContainer(keyedBy: CodingKeys.self, forKey: key) will add encoded items to an existing key if it already exists.

Here’s the blow-by-blow of me figuring that out 🙂

(I have a lot of Swift yet to learn, so it’s likely there is a better way to do all this, but my searching wasn’t finding anything and this works and isn’t entirely horrible, so I thought I’d post it in case it helps someone else).

I have the following model which is already Codable (only Encodable shown for brevity):

public enum UserIdentifier: Encodable {
  case email(String)
  case phone(String)
  case guestID(String)
}

extension UserIdentifier {
  enum CodingKeys: String, CodingKey {
    case address, phone, gid
  }
    
  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    switch self {
    case let .email(value):
      try container.encode(value, forKey: CodingKeys.address)
    case let .phone(value): 
      try container.encode(value, forKey: CodingKeys.phone)
    case let .guestID(value): 
      try container.encode(value, forKey: CodingKeys.gid)
    }
  }
}

This then needed to be a field in a struct representing the body of a POST request to an http server:

struct PostBody: Encodable {
  let userID: User.UserIdentifier
  let name: String
  let password: String
}

and what the server expected in the body is JSON of the form:

{
  "address" : "bob@email.com",
  "username" : "Robert Tree",
  "password" : "MyBadPassword!"
}

To see what the output of the default Encodable support will output I created a little test case (I often do this kind of thing in a playground, but this was already embedded in an iOS app so it was easiest to just make a test case):

class Tests: XCTestCase {
  func testCreateUserPostBodyToJSON() throws {
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted

    let body = UserService.CreateUserPostBody(userID: User.UserIdentifier.email("bob@email.com"), 
        name: "Robert Tree", 
        password: "MyBadPassword!")

    let jsonData = try encoder.encode(body)
    print(String(data: jsonData, encoding: .utf8)!)
  }
}

and, of course we expect this won’t be right, but it’s our starting point.  Here’s what it outputs:

{
  "userID" : {
     "address" : "bob@email.com"
  },
  "name" : "Robert Tree",
  "password" : "MyBadPassword!"
}

So clearly we need a custom encode function to pull the userID enum contents up to the top level. Let’s try this:

func encode(to encoder: Encoder) throws {
  enum CodingKeys: String, CodingKey {
    case username, password
  }
  var container = encoder.container(keyedBy: CodingKeys.self)
  try container.encode(name, forKey: .username)
  try container.encode(password, forKey: .password)
  try userID.encode(to: encoder)
}

The key is that last line: try userID.encode(to: encoder). Here’s the new output:

{
  "address" : "bob@email.com",
  "username" : "Robert Tree",
  "password" : "MyBadPassword!"
}

Bingo! Just what the doctor ordered!

Except then I try the request to the server and it turns out the api documents for the server are out of date (🤦🏼‍♂️) and what the server really wants is:

{
  "user" : {
    "address" : "bob@email.com",
    "username" : "Robert Tree",
    "password" : "MyBadPassword!"
  }
}

Hmmm. Ok, so this is a little more challenging, but it’s what nestedContainer is for, so we’ll try using that:

func encode(to encoder: Encoder) throws {
  enum ParentCodingKeys: String, CodingKey {
    case user
  }
  enum CodingKeys: String, CodingKey {
    case username, password
  }

  var container = encoder.container(keyedBy: ParentCodingKeys.self)
  var nestedContainer = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
  try nestedContainer.encode(name, forKey: .username)
  try nestedContainer.encode(password, forKey: .password)
  try userID.encode(to: container.superEncoder(forKey: .user))
}

Looks fancy. Let’s try it:

{
  "user" : {
    "address" : "bob@email.com"
  }
}

OH NO!!! what happened?!!

Hmmm… That looks like just the output of the last line, so comment out
try userID.encode(to: container.superEncoder(forKey: .user))
and see if the first part is working at least:

{
  "user" : {
    "username" : "Robert Tree",
    "password" : "MyBadPassword!"
  }
}

Ok. So the first part is creating things exactly how we want it to, it’s just that last line isn’t adding itself to the already existing "user" : { } node. Rats! Documentation isn’t shedding any light (it *does* in fact shed light, but it’s too subtle for me to catch on a too-quick read; as we’ll see below). Tried this on a whim:

func encode(to encoder: Encoder) throws {
  enum ParentCodingKeys: String, CodingKey {
    case user
  }
  enum CodingKeys: String, CodingKey {
    case username, password
  }

  var container = encoder.container(keyedBy: ParentCodingKeys.self)
  try userID.encode(to: container.superEncoder(forKey: .user))
  var nestedContainer = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
  try nestedContainer.encode(name, forKey: .username)
  try nestedContainer.encode(password, forKey: .password)
}

And the output is:

{
  "user" : {
    "address" : "bob@email.com",
    "username" : "Robert Tree",
    "password" : "MyBadPassword!"
  }
}

Success!

So, apparently, superEncoder(forKey: key) (Docs, emphasis mine: “Stores a new nested container for the given key and returns A new encoder instance for encoding super into that container.”) always makes the node for key and overwrites whatever happened to be there before (how rude!). Luckily, nestedContainer(keyedBy: Keys, forKey: key) (Docs: “Stores a keyed encoding container for the given key and returns it.”) doesn’t do that and will just add items to the node if it already exists. Sweet!

Follow up:

It occurs to me that the solution above only works because there’s only a single enum like UserIdentifier to include in the nested structure. They can’t both be first and so the second one would overwrite the first one.

Hopefully there’s another better way to do this that someone will point out in a comment which can handle multiple objects.

Making iOS 12 projects in Xcode 11

Some Xcode 11 iOS project templates aren’t iOS 12 compatible out of the box, but it’s relatively easy to make them work, here’s how:

  1. Select File > New… > Project…
  2. Select iOS and “Single View App” template, for example.
  3. Give the app a name, Swift, and be sure to select “Storyboard”, not “SwiftUI” (which needs some View and some requires iOS 13 according to the error message from Xcode), then select the “Next” to create the project.
  4. Select “AppDelegate.swift” and add @available(iOS 13, *) on the line above each of the func definitions in the “UISceneSession Lifecycle”
  5. Add an implementation of the window property inside the AppDelegate class like so: var window: UIWindow?
  6. Open “SceneDelegate.swift” and add @available(iOS 13, *) on the line above class SceneDelegate
  7. Select the project file in the Project Navigator to edit project settings and select the Project in the left pane of the editor
  8. In the “Info” tab, change the “iOS Deployment Target” at the top of the right pane to the appropriate 12.x deployment target for your project.
  9. Build and Run.  Good to go!

I’ve posted the two edited source files in a gist here

And thus he was gone…

With a harsh gesture at the ground ahead and loud but guttural roar of uttered spell, there was a bright flash, a sharp and loud “CRACK!”, and a deep groaning and tearing sound as the earth before them ripped open to reveal a vast chasm into which tumbled quantities of rock and debris as the edges collapsed inward. With a long and wrenching curse audible even over the furor of a great host of men trying to calm their startled beasts, the speaker dove forward into the depths of blackness and the earth closed up over him like the jaws of some huge monster closing over it’s prey. As the beasts were brought back under control by their riders, a silence spread over the assembled host with only the lesser grumbles of the earth settling back into place and the cries of the startled birds soaring overhead to be heard.

“I want my CFT”

I want my CFT

(sung to the tune of “Money for Nothing” by Dire Straits (1))

Now look at them LIGOs that’s the way you do it
Correct the errors on the CFT
That ain’t workin’ that’s the way you do it
Photons for nothin’ and your phase for free

Now that ain’t workin’ that’s the way you do it
Lemme tell ya them bits won’t flip
Maybe trap an ion in a planar crystal
Maybe trap an ion on a chip

We gotta install microwave lasers
Custom qubit deliveries
We gotta move these refrigerators
We gotta move these Ising machines

We gotta install microwave lasers
Custom qubit deliveries
We gotta move these refrigerators
We gotta move these Ising machines

I shoulda learned semiconductors
I shoulda learned them fermions
Look at those ions, they can’t find them on the camera
Man, we could have some fun

And he’s up there, what’s that? Rad-pressure noises?
Squeezin’ on the vacuum like a chimpanzee
Oh, that ain’t workin’ that’s the way you do it
Get your photons for nothin’ get your phase for free

We gotta install microwave lasers
Custom qubit deliveries
We gotta move these refrigerators
We gotta move these Ising machines

Listen here, now that ain’t workin’ that’s the way to do it
Correct the errors on the CFT
That ain’t workin’ that’s the way you do it
Photons for nothin’ and your phase for free
Photons for nothin’ and the phase for free
Get your photons for nothin’ and phase for free
Photons for nothin’ and the phase for free (I want my, AdS/CFT)
Photons for nothin’ and the phase for free (I want my, AdS/CFT)


 

Geek is in a Quantum Physics PhD program at University of New Mexico, Albuquerque and his department organized this 21st annual workshop during February 2019 in Albuquerque: http://physics.unm.edu/SQuInT/2019/index.php

Geek attended parts of the event in-between his classes and job teaching undergraduate physics labs.  He sent dad this humorous song summary of the workshop along the lines of others we’ve done on the blog. 🙂

1. Money for Nothing (wikipedia)

 

Memorabilia box reveals another funny item and story…

I lived in rural Maui back when there was zero bus service and you had to have a car to get anywhere (though we did moderately well for local area access on bicycles via back roads and pineapple fields).  The fact that it is an island made out of the slopes of two mountains (one of which is 10,000′ tall!) meant a lot of steep rides though, so like any teenager in a rural location in those days, a car was a significant desire. 🙂

My first automobile was a Chevy Love truckthat I purchased when I was 14-1/2 – six months before I could get a permit and license to drive.  $250 and we had to tow it from a neighbor’s yard because it didn’t run.  It was in terrible shape. He’d stopped driving it several years prior because the back of the truck was so rusted out that his dogs’ legs were falling through and the water in the puddles in the unpaved road to our group of houses was splashing up into the cab through the giant rust holes.  Truly an amazing vehicle! 😛  The back tires were extra wide for some reason, but that just made it seem cooler 🙂  It looked something like this, but with a ton of rust holes:

CHEVY-LUV_Truck.jpg

Of course I was stoked to get it and spent the next 6 months getting it running, putting plywood down in the floor of the truck bed, and so on.  Had to get the rust ground off the brakes by a machine shop (raw metal sitting == rust build up fast in Hawaii), clean out the mildew, etc and so on.

The deal with my parents was that if I drove myself and my sister to school (no school buses to the schools we were in) then they’d pay the car insurance and just enough gas money to go those miles.  The truck maintenance and gas for any other trips was on me.

I got my permit at 15, and two weeks later my license (minimum required waiting time).  The muffler was pretty rusted out and so it sounded a bit like a crop duster when I pressed the gas pedal (i.e., loud and frappy).  It also wouldn’t idle so I had to keep tapping the gas pedal at stop lights and stop signs to keep the motor from dying; other teens thought I wanted to race (ha!).

During some periods it wouldn’t start reliably.  Had to push start it pretty regularly so I parked it up an embankment that let me roll it down towards the house and hopefully get it to start (yikes!).  Some periods I’d start it first and then run inside to take my shower before driving to school so that I wouldn’t have to take a second shower after getting all greasy getting it running.

Anyway, at one point I decided to make a stencil out of legal size file folders (only stiff card stock I had access to for free :)) and spray painta logo on the side of the doors to embrace the crappiness of the truck and so I made one that evoked the circular logo of the state that was on the side of government trucks.

I found the stencil in my mementoes box and without further ado or story telling, here it is:

IMG_8357 small for web.JPG

🙂 Apropos, for truly it was a rusted hulk that should have been scrapped, but got me around the island for maybe a year before I upgraded to a $750 car that burned almost a quart of oil a week and a went through about a quart of transmission fluid every two days until I took the transmission out and got it rebuilt, but that ran much more reliably and had much less rust 🙂  (So bad for the environment – we were so clueless back then!  sigh).

15th Birthday Poem from Dad’s grandmother

Looking through my memento boxes for things for my dad (Geek’s grandfather)’s memorial service and found this poem my grandmother wrote me for my 15th birthday.  She was a great grandmother.

15th birthday poem from Granny Bee.png

 

It references our relation to Daniel Boone, and the large ranch on the coast of California that our family owned for many generations and that my dad (Boone) grew up on.