Quantcast
Channel: NSHipster
Viewing all articles
Browse latest Browse all 382

NSRange

$
0
0

NSRange is one of the essential types of Foundation. Passed around and returned in methods throughout the framework, being well-versed in this struct has a range of benefits, which this week's article will help you locate.


Ranges are data types used to describe a contiguous interval of integers. They are most commonly used with strings, arrays, and similarly-ordered collections.

For Objective-C programs, the Foundation type NSRange is used. In other languages, ranges are often encoded as a two-element array, containing the start and end indexes. In Foundation, NSRange instead encodes a range as struct containing the location and length. By command-clicking (⌘-ʘ) on the NSRange symbol in Xcode, we can jump directly to its declaration in Foundation/NSRange.h:

typedefstruct_NSRange{NSUIntegerlocation;NSUIntegerlength;}NSRange;

In practice, this approach helps mitigate common off-by-one errors when working with ranges. For example, compare the equivalent Javascript and Objective-C code for creating a range of characters for a given string:

range.js

varstring="hello, world";varrange=[0,string.length-1];

Forgetting to subtract 1 for the end index in Javascript would result in an out-of-bounds error later.

range.m

NSString*string=@"hello, world";NSRangerange=NSMakeRange(0,[stringlength]);

NSRange's approach is clearer and less prone to error—especially when it comes to more complex arithmetic operations on ranges.

Usage

Strings

NSString*string=@"lorem ipsum dolor sit amet";NSRangerange=[stringrangeOfString:@"ipsum"];// {.location=6, .length=5}NSString*substring=[stringsubstringWithRange:range];// @"ipsum"

NSString does not have a method like containsString:. Instead, rangeOfString: can be used to check for an NSNotFound location value:

NSString*input=...;if([inputrangeOfString:@"keyword"].location!=NSNotFound){// ...}

Arrays

NSArray*array=@[@"a",@"b",@"c",@"d"];NSArray*subarray=[arraysubarrayWithRange:NSMakeRange(1,2)];// @[@"b", @"c"]

Index Sets

NSIndexSet is a Foundation collection class that is similar to NSRange, with the notable exception of being able to support non-contiguous series. An NSIndexSet can be created from a range using the indexSetWithIndexesInRange: class constructor:

NSRangerange=NSMakeRange(0,10);NSIndexSet*indexSet=[NSIndexSetindexSetWithIndexesInRange:range];

Functions

Because NSRange is not a class, creating and using instances is done through function calls, rather than, say, instance methods.

Many of the NSRange functions are named counter to the modern conventions of Foundation and CoreFoundation wherein the relevant type of the function immediately follows the two-letter namespace. For example, NSMakeRange should instead be named NSRangeMake, following the example of CGRectMake and CGSizeMake, et al. Similarly, a better name for NSEqualRanges would be NSRangeEqualToRange, just like CGPointEqualToPoint.

Although consistency in itself is likely not sufficient reason to go through the trouble of replacing existing usage, this gist shows how one could make their own code base a little more OCD-friendly.

Creating an NSRange

  • NSMakeRange: Creates a new NSRange from the specified values.
NSArray*array=@[@1,@2,@3];NSRangerange=NSMakeRange(0,[arraycount]);// {.location=0, .length=3}

Querying Information

  • NSEqualRanges: Returns a Boolean value that indicates whether two given ranges are equal.
NSRangerange1=NSMakeRange(0,6);NSRangerange2=NSMakeRange(2,7);BOOLequal=NSEqualRanges(range1,range2);// NO
  • NSLocationInRange: Returns a Boolean value that indicates whether a specified position is in a given range.
NSRangerange=NSMakeRange(3,4);BOOLcontained=NSLocationInRange(5,range);// YES
  • NSMaxRange: Returns the sum of the location and length of the range.
NSRangerange=NSMakeRange(3,4);NSUIntegermax=NSMaxRange(range);// 7

Set Operations

  • NSIntersectionRange: Returns the intersection of the specified ranges. If the returned range’s length field is 0, then the two ranges don’t intersect, and the value of the location field is undefined.
NSRangerange1=NSMakeRange(0,6);NSRangerange2=NSMakeRange(2,7);NSRangeintersectionRange=NSIntersectionRange(range1,range2);// {.location=2, .length=4}
  • NSUnionRange: Returns the union of the specified ranges. A range covering all indices in and between range1 and range2. If one range is completely contained in the other, the returned range is equal to the larger range.
NSRangerange1=NSMakeRange(0,6);NSRangerange2=NSMakeRange(2,7);NSRangeunionRange=NSUnionRange(range1,range2);// {.location=0, .length=9}

Converting Between NSString * & NSRange

  • NSStringFromRange: Returns a string representation of a range.
NSRangerange=NSMakeRange(3,4);NSString*string=NSStringFromRange(range);// @"{3,4}"
  • NSRangeFromString: Returns a range from a textual representation.
NSString*string=@"{1,5}";NSRangerange=NSRangeFromString(string);// {.location=1, .length=5}

If the string passed into NSRangeFromString does not represent a valid range, it will return a range with its location and length set to 0.

NSString*string=@"invalid";NSRangerange=NSRangeFromString(string);// {.location=0, .length=0}

While one might be tempted to use NSStringFromRange to box NSRange for inclusion within an NSArray, NSValue +valueWithRange: is the way to go:

NSRangerange=NSMakeRange(0,3);NSValue*value=[NSValuevalueWithRange:range];

NSRange is one of the few cases where some of the underlying implementation of its functions are actually exposed and inlined in the public headers:

Foundation/NSRange.h

NS_INLINENSRangeNSMakeRange(NSUIntegerloc,NSUIntegerlen){NSRanger;r.location=loc;r.length=len;returnr;}NS_INLINENSUIntegerNSMaxRange(NSRangerange){return(range.location+range.length);}NS_INLINEBOOLNSLocationInRange(NSUIntegerloc,NSRangerange){return(!(loc<range.location)&&(loc-range.location)<range.length)?YES:NO;}NS_INLINEBOOLNSEqualRanges(NSRangerange1,NSRangerange2){return(range1.location==range2.location&&range1.length==range2.length);}

NSRangePointer

One oddity worth mentioning with NSRange is the existence of NSRangePointer. "What the what?", you might exclaim in panicked confusion. Jumping to the source confirms our darkest fears:

Foundation/NSRange.h

typedefNSRange*NSRangePointer;

So. Without a definitive origin story, one would have to assume that this type was created by a well-meaning framework engineer who noted the confusion around NSRange being a struct and not a class. NSRange * is equivalent to NSRangePointer, though the latter can be found in out parameters for various methods throughout Foundation. NSAttributedString, for instance, has an NSRangePointer parameter for returning the effective range of an attribute at a particular index (since the attribute likely starts and ends before outside of the specified index):

NSMutableAttributedString*mutableAttributedString=...;NSRangerange;if([mutableAttributedStringattribute:NSUnderlineStyleAttributeNameatIndex:0effectiveRange:&range]){// Make underlined text blue as well[mutableAttributedStringaddAttribute:NSForegroundColorAttributeNamevalue:[UIColorblueColor]range:range];}

CFRange

One final caveat: Core Foundation also defines a CFRange type, which differs from NSRange in using CFIndex types for its members, and having only the function CFRangeMake:

typedefstruct{CFIndexlocation;CFIndexlength;}CFRange;

Anyone working with CoreText or another low-level C API is likely to encounter CFRange in place of NSRange.


Viewing all articles
Browse latest Browse all 382

Trending Articles