Archive for October 29, 2018

Monday, October 29, 2018

Business Licensing for Omni’s iOS Apps

Ken Case:

In 2016, we started switching our apps over to free downloads with in-app purchases. This solved a lot of problems for consumers who purchase our apps through the App Store, by enabling free trials, upgrade discounts, and free upgrades for recent purchases. Unfortunately, switching to in-app purchases made it much more difficult for businesses and schools to purchase our apps through the App Store, since Apple’s Volume Purchase Program (which lets organizations purchase apps) doesn’t support in-app purchases.


Looking around at how other people have solved this problem, one of the better approaches is to offer a single sign-on based licensing solution. (This is an approach Microsoft supports for licensing Office 365, for example.) The idea is that an organization will purchase licenses for use by a team, and the app will offer to let team members sign in with a set of credentials which will be verified by that organization using their single sign-on server. This lets the organization be responsible for purchasing and distributing their team’s licenses—including redistributing licenses when appropriate.

So, in other words, licensing has to completely bypass the App Store. I guess Apple allows this because you can still purchase in-app. But there must be restrictions or else we would already see a parallel app economy with discounts and upgrades purchased directly from the developer.

Update (2018-11-13): Ken Case:

To be clear, we do offer IAP. Unfortunately, IAP doesn’t support business purchases (which I filed a few years ago as radar 29148022). Before implementing this option, we had no mechanism for businesses to purchases licenses at all (they could only reimburse personal purchases).

NSMutableDictionary’s Practical Limit

Vincent Bénony:

Pro tip: if your Objective-C application deals with a huge collection of objects, avoid relying on NSMutableDictionary as the “count” method returns the number of objects… modulo 0x2000000 “allKeys” and “allValues” are also affected…

Greg Titus:

Fun fact! If you make your dictionary with 0x2000001 objects in Swift and then cast it to an NSDictionary, then it works just fine from Objective-C. Can’t be a mutable dictionary that way, though.

Update (2018-10-31): Vincent Bénony:

It depends on the private class used by Foundation. For instance, if I build the dictionary with « dictionaryWithObjects:forKeys: » it works perfectly, but as soon as I create a mutable copy, it fails.

NSKeyValueObservingCustomization Is Fundamentally Broken

Lily Ballard (tweet):

The breakage is the fact that implementing it relies on being able to do equality testing on key paths, but equality testing on key paths doesn’t work in the face of subclassing. By that I mean given the following code

class Foo: NSObject {
    @objc dynamic var name: String?
class Bar: Foo {}

The expression \ == \ returns false even though Bar is just inheriting its property from Foo. This means that an implementation of NSKeyValueObservingCustomization cannot possibly work properly on anything besides a non-final class. Even if keypath construction in this instance were changed such that \ returns the same thing as \, the same cannot be done for the more complicated case[…]

I guess the workaround is to use the string-based Objective-C methods instead of the AnyKeyPath Swift ones.

See also: SR-9077.

NSKeyValueObservingCustomization relies on a global table to map String key paths back into AnyKeyPath values. However, as the string keypath does not include the root type, this means that observing properties with the same name on separate objects will overwrite each other in the global table. In a single-threaded scenario this is acceptable as the NSKeyValueObservingCustomization methods are invoked synchronously when the observation is created, and the global table is populated immediately prior to creating the observation. However, in a multithreaded scenario, the global keypath table could be overwritten with a different keypath prior to invoking the NSKeyValueObservingCustomization method.

App Stores No Longer Listing All In-App Purchases

Jim Tanous (via Bryan Chaffin):

A concerning change was quietly pushed to the iOS App Store recently. Users this week noticed that Apple is no longer listing all in-app purchases and their prices for apps and games that offer them.


The problem with this new policy is that the nature and prices of in-app purchases vary wildly depending on the developer and type of app or game. Users could previously check out the description and prices of the in-app purchases to determine if they were reasonable before downloading or buying an app. Now, it seems, users must download and launch the app to see the same information.

This results in not only an inconvenience for the user, but it also inflates download statistics for app developers and potentially exposes user information as well.

The Early Days of GitHub

EnterpriseReady Podcast (via Hacker News):

In episode 2 of EnterpriseReady, Grant chats with Tom Preston-Werner about how the open source company he co-founded, GitHub, rose up to become an essential coding resource for developers everywhere.


As we approached all of the enterprise problems, we take it from that approach, we were learning what enterprises needed at the time. Like I said before, I hadn’t ever made software for enterprises. I had no idea what features they wanted.


That choice is one that everyone’s faced with which is, “Do we create a fork of our repository and build enterprise features there? Because we don’t need them in the SaaS products, so let’s not complicate the SaaS product with them. We’ll build those features in the enterprise model only, and then we’ll port all of the new stuff from the SaaS model into the enterprise codebase. Because Git can do that. It’s good at merging stuff.” So that’s what we did, we had a separate repository.

We forked off SaaS model repository, and new enterprise features went in there. We hired a person specifically to do this merge process, which was a thankless horrible task, and we did it for a while and it was just extremely slow. Merging is not amazing, ever. If you have conflicts it’s a nightmare. It’s a never-ending nightmare.