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

Objective C Builder Pattern play

So I read two posts on the Builder Pattern from Java today that got linked to off of Twitter.

This by Klaas Pieter which referenced this one by Uli Kusterer.  Both good articles.

I haven’t done Java much at all for the last 10 years and so am not used to this pattern, but I thought about how I might do something similar and while I’m not sure my first thought is any better, it seems to meet the requirements and/or benefits noted by the two blog posts in question and the tweet discussion referencing them.

Instead of:

 Pizza pizza = new Pizza.Builder()
     .size(12)
     .pepperoni(true)
     .mushrooms(true)
     .build();

(from Uli’s post), or

  Image* theImage =
    (new Image.Builder)->SetWidth(100)
    ->SetHeight(80)->SetDepth(8)->Build();

(from Klaas’ post)

I tried something like:

Foo * aFoo = [Foo fooWithData: @{
     @"width" : @21, @"height" : @22 }];

Here’s one way to implement that – there are multiple, clearly
(pardon the formatting, trying to fit into our narrow blog them is annoying):

// .h file
#import <Foundation/Foundation.h>

@interface Foo : NSObject

+ (instancetype) fooWithData: (NSDictionary*) initParams;

@end

// .m file
@interface Foo ()
@property (nonatomic, assign) long width;
@property (nonatomic, assign) long height;
@property (nonatomic, assign) long depth;
@end

@implementation Foo

+ (instancetype) fooWithData: (NSDictionary*) initParams
{
  Foo *result = [[Foo alloc] init];

  // If one uses setValue:forKey: in a loop as
  // as Uli notes then we can't support integral
  // properties like ints.
  // Also doing it explicitly as below
  // means we don't have to have the same name
  // for our private internal properties as we
  // document for our public parameters because
  // we can map them here.
  // e.g.,
  //  result.imageWidth = [initParams[@"width"] longValue];

  if ( initParams[@"width"] != nil )
    result.width = [initParams[@"width"] longValue];

  if ( initParams[@"height"] != nil )
    result.height = [initParams[@"height"] longValue];

  if ( initParams[@"depth"] != nil )
    result.depth = [initParams[@"depth"] longValue];

  return result;
}

- (instancetype) init
{
  self = [super init];
  if ( self )
  {
    // init with defaults
    _width = 10;
    _height = 10;
    _depth = 1;
  }
  return self;
}

- (NSString*) description
{
  return [NSString stringWithFormat:
           @"Foo: (%p), width: %ld, height: %ld, depth: %ld",
           self, self.width, self.height, self.depth];
}
@end

// main.m
#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {
  @autoreleasepool {
      // note no depth specified, taking default of 1
    Foo * aFoo =
      [Foo fooWithData: @{ @"width" : @21, @"height" : @22 }];
    NSLog(@"aFoo: %@", aFoo );
  }
}

Paste the above into CodeRunner and run it and you get:

2014-04-12 16:22:38.918 Untitled 7[71144:707]
aFoo: Foo: (0x7f8842c0af00), width: 21, height: 22, depth: 1

In the above I only typed in the class method as taking the full parameter list but normally I would have made the init method take the same parameter and do the initialization & mapping there. Something I’d likely add if I was going to actually use this, which I’m not likely to. Why not? Because to use the construct above you’d have to document the parameter keys available to use in the dictionary and what type of value each takes. This is where ObjectiveC’s named parameters comes in handy: they are self documenting.

Anyway, an interesting procrastination from what I was supposed to be doing this Saturday afternoon… 🙂

7DRL 2014 – Day 7: In which everything is complete

Trinkets – the roguelike game I’ve spent the past week creating for the 7DRL competition – is finished, about 20 hours before the deadline! It’s a game about wandering an extra dimensional vault, acquiring trinkets and trying to get back home.

A build for mac is available here. Unfortunately, there’s no windows/linux build, but if you’re on one of those operating systems and want to play, the source is available here.

Screen Shot 2014-03-15 at 2.38.31 PM

… which turns out to be entirely justified.

My high score is 59 – can you beat it? If so, let me know in the comments below!

7DRL 2014 – Day 6: In which victory approaches at high velocity

One day remains, and I’m actually almost done. It would be nice to redo the map generator, and game balance can always be tweaked some more, but the plot and endgame are implemented, the boss-fights are done with basic AI, and there’s even a score feature. Shown below is the second of four boss fights:

You have a bad feeling about this level...
You have a bad feeling about this level…

… which turns out to be entirely justified.
… which turns out to be entirely justified.

7DRL 2014 – Day 5: In which parkour is performed in fancy shoes

Today was very productive; I finished the procedural generation of trinkets (for now, at least), improved the magic system, procedurally generated strangely worded descriptions of said trinkets, added new effects including knock-back and circular slide, and added all of the non-boss enemies, including the Abberant visible below:

Image
Parkour shoes acquired!

Shown above is the non-procedurally-generated description of one of the coolest shoe types in the game (thus far, at least). These shoes allow you to run along walls, moving faster than enemies or even jumping over them.

I still have two more days left to go, and quite a bit still to do, including:

  • Plot
  • Victory conditions
  • Boss fights + Boss AI
  • Game balance
  • Prettier maps