Sunday, February 1, 2015

Categorical

Soroush Khanlou:

-[NSString containsString:] was added in iOS 8. If you’re writing a standard library, this seems like one of the most crucial components for string handling. And yet, it’s been conspicuously absent since the early ’90s, when NSString was created.

[…]

So given these myriad reasons why categories aren’t good enough, why will Apple not implement these methods?

[…]

Other vendors have proven that it’s possible to do this well. Ruby, for example, takes it to the other extreme: their Integer objects respond to methods like #odd?, #even? and even #gcd. Providing those methods in the standard library yields more readable code and it gives the vendor the opportunity to tweak and optimize them at later date, knowing that anyone who needs them will benefit from their optimizations.

[…]

In the same way that base types like Swift’s Optional or JavaScript ES6’s Promises must be built-in for proper adoption, these simple methods need to be available to all developers. Apple’s proclivity towards leaving holes in their API forces developers into a bind. We must either write inexpressive, hard-to-read code, do needless work opening these classes back up, or leave a trail of dependencies in our wake.

14 Comments RSS · Twitter

Ruby's random grab bag of a standard library being held up as an exemplar? Really? This person has clearly never maintained a library or framework over a long period of time.

I also don't see what's so unexpressive about -[rangeOfString:].location == NSNotFound. Dovetails nicely into some useful functionality in the framework.

And, off the top of my head, I can think of at least two different implementations, with different API contracts and thus different consequences for your code, for firstObject. You must prefix your category names. Best practices are best practices for a reason.

The author seems to be missing another possibility. If Apple holds back on these convenience functions until iOS 8/Yosemite, then developers who use them are automatically locked into the latest OS version. That's good for Apple who wants to sell new devices but not so much for developers who want to support as many potential customers as possible.

Apple sells iPhones and a few iPads and Mac. The more developers rely on Apple's frameworks like PubSub, the more they are under Apple's control. One of the hardest lessons I have had to learn is that while Apple makes great hardware, their software just isn't all that great.

-[NSString rangeOfString:] exists since forever, and is technically superior. Yet it autocompletes so badly ... sigh.

Ruby is probably too extreme, but I think Cocoa is way too conservative about providing convenience methods. I do agree that category methods should always be prefixed.

I’m surprised that people are defending -[NSString rangeOfString:] as being expressive enough or even superior. Do you consider -hasPrefix: and -hasSuffix: to also be unnecessary? What do you think about string replacement being added in 10.5?

Do you also think that -[NSArray containsObject:] should be replaced by -[NSArray indexOfObject:] != NSNotFound? If you don’t like -firstObject, do you think that -lastObject should never have been added?

The guy wrote "... one of the most crucial components for string handling. And yet, it’s been conspicuously absent since the early ’90s ..." and I pointed out, that this crucial component was there all along, just not by the name he seemed to be looking for.

"I do agree that category methods should always be prefixed."

IMO much too broad. Prefixing your category methods on third party classes makes sense. Apple for example sure doesn't do it in their Foundation categories.

[s hasSuffix:other] would be [s rangeOfString:other].location == [s length] - [other length]

but that is in my eyes just not convenient anymore to type and three method calls, where -hasSuffix: can probably do better. If you have -hasSuffix: it's kind of logical to have a -hasPrefix: method as well. I can vaguely guess, why the didn't put in -firstObject originally, but it was a mistake in my opinion.

-[NSArray containsObject:] is one of the most superflous methods ever. If I use it, I know I am doing it wrong, I should really be using some kind of NSSet, NSHashTable or NSOrderedSet.

@Nat In the very next sentence (which I didn’t quote) he indicated that he knows about -rangeOfString:.

Yes, of course Apple should not prefix in their Foundation categories.

I think that -hasSuffix: implementation would fail if there were multiple matches. You could probably do it without any extra method calls using -rangeOfString:options: and NSBackwardsSearch | NSAnchoredSearch.

Author here.

> Do you consider -hasPrefix: and -hasSuffix: to also be unnecessary?

Just a cool sidenote: `-hasPrefix:` and `-hasSuffix:` are way more performant than `-rangeOfString:, since `-rangeOfString:` probably uses a string-searching algorithm like [Boyer-Moore](http://en.wikipedia.org/wiki/Boyer–Moore_string_search_algorithm), which does a bunch of up-front work preprocessing the haystack string. Checking prefixes and suffixes is a lot simpler, since you can return `NO` if the first character you compare doesn't match.

That's true, without the options my `-hasSuffix:` code is buggy.

At the end of the day, I don't even have a particular beef with `containsString:`, though it is extremly close to being superflous. Would you also like a method `hasInfix:` (which would be identical to `containsString:`) ?

Initially, I agreed with the article, but come to think of it, I find that `-containsString:` is the only convenience method I really wish Apple had included. I agree the use of `rangeOfString` is not the most readable thing in the world. I keep using it, because I still like that better than having a category and a dependency.

Even `lastObject` I have not missed much. Thus, in the end, it's really just a handful of convenience methods. And sure, Apple could have provided the few I miss, considering they are otherwise well covered.

Things like JSON or other basics like Base64, gzip, etc. are a different matter, where it's quite a commitment for Apple, but I still wish they did not have such a long wait to include them in the APIs.

@Nat! It’s shorter, but I think -containsString: reads better.

@charles Why are you (barely) missing -lastObject when Apple has included it since 10.0?

I long missed stuff like string replacement, regex, and -localizedStandardCompare:, but luckily we have them now.

> @charles Why are you (barely) missing -lastObject when Apple has included it since 10.0?

I meant `firstObject`, sorry for the confusion. We have it now, of course. But apart from `containsString` that may be another good example of a convenience method that I always wondered was missing. Then my point was that beyond `containsString` and`firstObject`, I can't really think of other convenience methods that I find missing on a frequent basis. But maybe I just got used to how things are :-)

@mjtsai

But would there be any disadvantage to an additional method ``hasInfix:`` in Foundation (existing in harmony with ``containsString:``) ? It surely would complement the hasSuffix:hasPostfix: methods logically...

@Nat! Yes, I don’t think there should be two ways to do the exact same thing. There is a difference between a convenience method (that reveals intent and reduces the number of tokens) and an alternate spelling (though Cocoa has a few of those where the original method was misspelled). Although I confess that I do make some categories like that when I really don’t like the original spelling, e.g. -[NSString mjtParent] instead of -[NSString stringByDeletingLastPathComponent].

@mjtsai

Yes I agree with that.

Unfortunately I also think calling another Obj-C method just for the minor convenience of picking out something from a C struct is kinda sinful. I am so torn :)

Leave a Comment