Wednesday, February 28, 2018

Google Libraries for Objective-C

Google:

Promises is a modern framework that provides a synchronization construct for Swift and Objective-C.

Google (via Peter Steinberger):

Functional operators for Objective-C: An Objective-C library of functional operators, derived from Swift.Sequence, that help you write more concise and readable code for collection transformations. Foundation collections supported include: NSArray, NSDictionary, NSOrderedSet, and NSSet.

Update (2018-03-01): Gianluca Bertani:

On the same domain, Opinionated-C looks way better[…]

14 Comments RSS · Twitter

On the on hand: Nice to see more Objective-C libraries released!

On the other hand: I still like the shorter syntax used by MACollectionUtlities way better: https://github.com/mikeash/MACollectionUtilities

@ilja Yeah, my own Objective-C code also uses macros to make these more concise.

@ilja I find the code for MACollectionUtilities a bit curious.

For instance, first method:

- (NSArray *)ma_map: (id (^)(id obj))block
{
    NSMutableArray *array = [NSMutableArray arrayWithCapacity: [self count]];
    for(id obj in self)
        [array addObject: block(obj)];
    return array;
}

- This returns a NSMutableArray * instead of the promised NSArray *.
- IIRC, [NS(Mutable)Array arrayWithCapacity:] does not really do anything different from [NS(Mutable)Array array]

`NSMutableArray` is a subclass of `NSArray` and it's perfectly valid to return an instance of a subclass. It's also completely safe, and AFAICT will be treated correctly by the compiler (warning you if you try to mutate the returned value, since it's typed as immutable). There is no way the instance will be mutated behind your back, since it's created in that method and not referenced again with a mutable type anywhere. Adding the extra code to return an immutable instance using return `[array copy]` is thus unnecessary.

> [NS(Mutable)Array arrayWithCapacity:] does not really do anything different from [NS(Mutable)Array array]

It used to do something different, and it does not hurt to pretend it still does. I guess you take a small hit from `[self count]` and maybe a few more instructions to go from `+arrayWithCapacity:` into `+array`.

To my eyes, all the above is not curious at all, in fact I'd call it "best practice" (even if the second point might be considered legacy).

@charles Yes, this looks standard to me as well.

@someone The ReadMe from MACollectionUtilities is a litte bit misleading. You can find the short-hand syntax at the bottom. Typical example from my code looks like this:

NSArray *listingsToRelist = SELECT ([self selectedItemsForSender:sender], [obj hasBeenListed] && [obj hasEnded]);

All the block syntax is hidden through the preprocessor macros.

@charles & @michael: this may be "standard" but this does not prevent the stuff below from happening. And this is not good IMHO:

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSArray * array=@[@"1",@"2",@"3"];
        NSArray * mutable_array=[array ma_map:^id(id object){
            return object;
        }];
        [(NSMutableArray *)mutable_array removeObjectAtIndex:1];
        NSLog(@"%@",mutable_array);
    }
    return 0;
}

2018-03-01 11:46:34.522 testMA[403:303] (
1,
3
)

---

It's worth adding:

if ([self isKindOfClass:[NSMutableArray class]]==YES)
    return array;
else
    return [array copy];

@someone You won’t run into problems unless you specifically cast to another type. You can break all kinds of things in C if you do that, so don’t. I don’t think checking the class at runtime helps anything (and, historically, it didn’t work due to implementation details).

@someone what kind of coding error are you trying to protect against?

The only one I can think of is that you are getting an NSArray back that is actually backed by a mutable array, and you hold onto that array but also hand it off to some other code that casts it and mutates it, breaking your own API contract. That's plausible but unusual; if you are worried about that, just make a copy yourself.

@nolen: I would rather have the original mapping API not change the class of the object.

Because for instance if I add a category to NSMutableArray to replace a method that exists for NSArray, the code that will get executed will not the expected one.

e.g. if I overload hash so that the hash for a mutable array is not the same as the one for a non-mutable array.

Or if a method accepts id as a parameter and you then use isMemberOfClass: on this parameter to decide what to do, you may not understand right away why the code for NSArray instances is not run.

@someone I see. I hadn't thought of the category method case. I'm with charles and mjtsai on this, though: this is pretty standard, and even the Apple APIs don't guarantee the specific class of the object you get back. NSArray is already a class cluster.

@someone If you don’t want it to change the class, you should probably use instancetype instead of casting the returned NSArray to something else.

That said, I think replacing NSMutableArray methods with categories and changing the hash method of a framework class would probably violate an API contract and potentially cause problems. Using isMemberOfClass: for the base Foundation types is a bad idea, anyway, because they use class clusters and the exact types change from release to release.

Ben Kennedy

It is impossible to safely replace a method using a category. That it is even possible is undoubtedly unintended design of Objective C runtime. What if somebody else also adds the same method in their own category? The winner is random and indeterminate; you can't be sure that it will be your implementation.

@Michael Yes, I would (I do actually) use instancetype instead of NSArray *.

I haven't seen any problem with using isMemberOfClass: on NSArray/NSMutableArray from Mac OS X 10.7 to 10.13 at least. I agree that NSString/NSMutableString would be a totally different case.

@Ben Yes, that's one of the reasons why it's recommended to add a prefix to the name of your methods in categories. My point is more that it's better for a method to return the advertised type. Otherwise use id or instancetype. Because having that kind of issue/strategy in the low layers of the code is not helping considering all the other potential strange stuff one can do in upper layers.

Leave a Comment