BuySellAds.com

Our DNA is written in Objective-C
Jump

Free Range

You will often find yourself working with NSRange parameters and variables, especially when dealing with strings. I stumbled into a problem that I think is an SDK bug, that prompted me to look at the header and find out what kind of functions are provided to us for comfortably dealing with NSRange.

Don’t be fooled by the NS to think that this is an object. Similar to NSInteger this is just a scalar value, basically shorthand for the compiler to know that there are two components – location and length – with 4 bytes each. So if you access range.length you are interested in the second 4 bytes, for range.location you access the first 4.

This also means that you don’t have any instance methods available, all manipulation of NSRange has to be via C-style functions. So let’s have a look at the NSRange.h header contained in the Foundation.framework to see what interesting functions await for us to be unearthed there.

In fact, let me print the entire header and let’s read it together.

typedef struct _NSRange {
    NSUInteger location;
    NSUInteger length;
} NSRange;

This is how the NSRange is defined as a type. Types are what you need to define variables. You can see that NSRange consists of two unsigned integers.

typedef NSRange *NSRangePointer;

In some instances you might not deal with an NSRange directly, but with a pointer to 8 bytes of memory containing an NSRange. Instead of writing NSRange *every time, Apple defines an NSRangePointer for us to use. Since these are a couple more characters to type then the only reason I can think of is safety. Using NSRangePointer avoids the risk of forgetting the asterisk.

NS_INLINE NSRange NSMakeRange(NSUInteger loc, NSUInteger len) {
    NSRange r;
    r.location = loc;
    r.length = len;
    return r;
}

This is the most convenient way to fill an NSRange with values. It is defined as inline which means that this actual code is inserted into yours everywhere you use it as opposed to being execute as a true function call, which would involve copying stuff onto the stack, jumping to code, copying the result back and jumping again. Inlining happens transparently to you, but gives you the better performance of avoiding the function call. This usually makes sense for very small functions like this one.

NS_INLINE NSUInteger NSMaxRange(NSRange range) {
    return (range.location + range.length);
}

This is neat, using this inline function we don’t have to do the addition ourselves to find the maximum index contained in the range.

NS_INLINE BOOL NSLocationInRange(NSUInteger loc, NSRange range) {
    return (loc - range.location < range.length);
}

Another neat inline function lets us check if an index is contained in the range.

NS_INLINE BOOL NSEqualRanges(NSRange range1, NSRange range2) {
    return (range1.location == range2.location && range1.length == range2.length);
}

This saves us even more work if we wanted to see if two ranges are identical.

FOUNDATION_EXPORT NSRange NSUnionRange(NSRange range1, NSRange range2);

This creates a union of two ranges. The code for this is actually contained in one binary of the Foundation framework. The resulting union will go from the smallest location to include the maximum index. So if you create a union of two non-intersecting ranges the result also covers the space between the ranges.

FOUNDATION_EXPORT NSRange NSIntersectionRange(NSRange range1, NSRange range2);

This makes an intersection. This works only if the ranges are overlapping. If they don’t then {0,0} is returned. Knowing this we can easily check if one range is contained within another by creating an intersection and then checking if length is non-zero.

FOUNDATION_EXPORT NSString *NSStringFromRange(NSRange range);

If we needed a string representation of a range, then this function would quickly provide it for you. Use in NSLog for example.

FOUNDATION_EXPORT NSRange NSRangeFromString(NSString *aString);

In some even rarer cases you might want to do the reverse, move from NSString to NSRange. This is the function to achieve that.

@interface NSValue (NSValueRangeExtensions)
 
+ (NSValue *)valueWithRange:(NSRange)range;
- (NSRange)rangeValue;
 
@end

And the final block defines a category for NSValue to package NSRanges into objects for storing for whenever you need to have an Objective-C object, like if you want to store multiple ranges in an array.

Now, about the bug that I suspect to have found. NSAttributedString has a method to enumerate over all the attribute dictionaries and you get an NSRange for where this attributes are valid. enumerateAttributesInRange:options:usingBlock:

This usually works without problems unless you run into a low-memory condition. Apparently there is a situation when this method returns a range outside of the string. So this is how I worked around it to avoid problems.

NSRange validRange = NSMakeRange(0, [attributedString length]);
 
[attributedString enumerateAttributesInRange:range options:0 usingBlock:
         ^(NSDictionary *attrs, NSRange range, BOOL *stop)
{
         if (NSIntersectionRange(range, validRange).length)
         {
                  // work with attributes
         }
         else
         {
             NSLog(@"Invalid Range returned by attribute enumeration: %@", NSStringFromRange(range));
         }
}
];

So if the value returned from the enumeration is outside of the valid range then I ignore it.

Knowing of these functions allows you to more efficiently deal with NSRanges. So you might want to commit these to memory or make a mental or actual bookmark of this article.


Categories: Recipes

%d bloggers like this: