Is there any way to get the neat Objective-C literal indexing feature in Xcode 4.4?
Asked Answered
I

1

7

I read all about the new Objective-C literals, and used Xcode to convert my old code, but the indexing code didn't change. I changed it by hand but then it wouldn't compile. I saw a post that said we have to wait until iOS 6, but I want the indexing NOW!

Is there any solution?

Interglacial answered 27/7, 2012 at 19:59 Comment(0)
I
21

Well, there is a way to do it! Add the indexing methods as a category to NSArray and NSDictionary, and you can get the feature for most of the classes you'd want it for. You can read up on ObjectiveC literals here. And thanks to James Webster's solution for @YES and @NO you can use them properly in your projects now too! (the technique)

1) Create the Interface files

// NSArray+Indexing.h
#if !defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
@interface NSArray (Indexing)
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
@end
@interface NSMutableArray (Indexing)
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
@end
// NSDictionary+Indexing.h
@interface  NSDictionary (Indexing)
- (id)objectForKeyedSubscript:(id)key;
@end
@interface  NSMutableDictionary (Indexing)
- (void)setObject:(id)obj forKeyedSubscript:(id)key;
@end
#endif

2) Create the Implementation files // see edit below before doing this - you can skip this

// NSArray+Indexing.m
#if !defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
#import "NSArray+Indexing.h"
@implementation NSArray (Indexing)
- (id)objectAtIndexedSubscript:(NSUInteger)idx
{
    return [self objectAtIndex:idx];
}
@end
@implementation NSMutableArray (Indexing)
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
{
    [self replaceObjectAtIndex:idx withObject:obj];
}
@end

// NSMutableDictionary+Indexing.m
@implementation  NSDictionary (Indexing)

- (id)objectForKeyedSubscript:(id)key
{
    return [self objectForKey:key];
}
@end
@implementation  NSMutableDictionary (Indexing)
- (void)setObject:(id)obj forKeyedSubscript:(id)key
{
    [self setObject:obj forKey:key];
}
@end
#endif

3) Add the Interface files to your pch file for global use, or add them as needed to .m files

// Add to PCH file
#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
...
#if !defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0

// New Indexing
#import "NSDictionary+Indexing.h"
#import "NSArray+Indexing.h"

// Provided by James Webster on StackOverFlow
#if __has_feature(objc_bool) 
#undef YES 
#undef NO 
#define YES __objc_yes 
#define NO __objc_no 
#endif 

#endif
#endif

#endif

4) Rebuild, then add the below files to verify that it all works

// Test Example
{

    NSMutableArray *a = [NSMutableArray arrayWithArray:@[ @"a", @"b", @"c" ]];
    NSLog(@"%@", a[1]);
    a[1] = @"foo";
    NSLog(@"a: %@", a);

    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{ @"key" : @"object" }];
    NSLog(@"%@", dict[@"key"]);

    dict[@"key"] = @"New Object";
    dict[@"newKey"] = @"WOW a new object";

    NSLog(@"dict: %@", dict);

    NSLog(@" %@ %@", @YES, @NO );
}

EDIT: Well, according to a key llvm/clang Apple engineer, there is a library that already gets linked in with the implementations, so you just need the interface file:

Date: Mon, 20 Aug 2012 15:16:43 -0700 From: Greg Parker To: ... Subject: Re: How to make Obj-C collection subscripting work on iOS 5? ...

As an experiment I added the category @interface for these methods, but not the @implementation — the app still ran fine (at least in the 5.1 simulator)

The compiler emits the same calls. The magic is in the increasingly inaccurately named libarclite ("It's Not Just For ARC Anymore™"), which adds implementations of the subscripting methods at runtime if they don't already exist.

IIRC there are some subscript-able classes that libarclite does not upgrade (NSOrderedSet, maybe?) so you still need to test thoroughly on older deployment targets.

Interglacial answered 27/7, 2012 at 19:59 Comment(6)
you could enter a system fallback, if you are compiling for ios6 only. (#if !defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0), see: gist.github.com/3314686Clemenciaclemency
Also the imports are not needed, because the messages are created on runtime. The compiler won't show warnings. And the classes will be loaded anyway. But perhaps it's more readable to have the imports.Clemenciaclemency
I think the insight that only the interface is required is worthy of being a separate answer (and being the correct one). The comment added above the implementation block doesn't jump out while scanning, and I didn't scroll all the way down until I had already started to implement the code and came back to check something.Furness
Also, the point might be moot, but the code given for setObject: atIndexedSubscript: isn't quite an exact match for the description of the NSMutableDictionary method. The latter allows an index one greater than the current highest index to grow the array by one, while replaceObjectAtIndex:withObject: would throw an exception. Here's my (untested) attempt to fix it: gist.github.com/9623156Furness
This post is now almost two years old, id offer its of limited use right now.Interglacial
I suppose so. Two years is not that old, but in this case I guess there won't be a lot of others still using the iOS 5 SDK (which I still do because Apple's compatibility macros no longer generate warnings when you use something that's not available in your target OS). In any case, it was still helpful to me! Thanks!Furness

© 2022 - 2024 — McMap. All rights reserved.