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?

jscs
  • 63,694
  • 13
  • 151
  • 195
David H
  • 40,852
  • 12
  • 92
  • 138

1 Answers1

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.

Community
  • 1
  • 1
David H
  • 40,852
  • 12
  • 92
  • 138
  • 2
    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: https://gist.github.com/3314686 – calimarkus Aug 10 '12 at 15:44
  • 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. – calimarkus Aug 10 '12 at 20:52
  • 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. – big_m Mar 18 '14 at 15:59
  • 1
    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: https://gist.github.com/9623156 – big_m Mar 18 '14 at 16:07
  • This post is now almost two years old, id offer its of limited use right now. – David H Mar 18 '14 at 20:41
  • 1
    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! – big_m Mar 21 '14 at 16:15