The iOS framework that grows only as fast as its documentation
NIFoundationMethods.m
1 //
2 // Copyright 2011-2014 NimbusKit
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #import "NIFoundationMethods.h"
18 
19 #import "NIDebuggingTools.h"
20 #import <CommonCrypto/CommonDigest.h>
21 #import <objc/runtime.h>
22 
23 #if !defined(__has_feature) || !__has_feature(objc_arc)
24 #error "Nimbus requires ARC support."
25 #endif
26 
27 #pragma mark - NSInvocation
28 
29 NSInvocation* NIInvocationWithInstanceTarget(NSObject *targetObject, SEL selector) {
30  NSMethodSignature* sig = [targetObject methodSignatureForSelector:selector];
31  NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig];
32  [inv setTarget:targetObject];
33  [inv setSelector:selector];
34  return inv;
35 }
36 
37 NSInvocation* NIInvocationWithClassTarget(Class targetClass, SEL selector) {
38  Method method = class_getInstanceMethod(targetClass, selector);
39  struct objc_method_description* desc = method_getDescription(method);
40  if (desc == NULL || desc->name == NULL)
41  return nil;
42 
43  NSMethodSignature* sig = [NSMethodSignature signatureWithObjCTypes:desc->types];
44  NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig];
45  [inv setSelector:selector];
46  return inv;
47 }
48 
49 #pragma mark - CGRect
50 
51 CGRect NIRectContract(CGRect rect, CGFloat dx, CGFloat dy) {
52  return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width - dx, rect.size.height - dy);
53 }
54 
55 CGRect NIRectExpand(CGRect rect, CGFloat dx, CGFloat dy) {
56  return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width + dx, rect.size.height + dy);
57 }
58 
59 CGRect NIRectShift(CGRect rect, CGFloat dx, CGFloat dy) {
60  return CGRectOffset(NIRectContract(rect, dx, dy), dx, dy);
61 }
62 
63 CGRect NIEdgeInsetsOutsetRect(CGRect rect, UIEdgeInsets outsets) {
64  return CGRectMake(rect.origin.x - outsets.left,
65  rect.origin.y - outsets.top,
66  rect.size.width + outsets.left + outsets.right,
67  rect.size.height + outsets.top + outsets.bottom);
68 }
69 
70 CGFloat NICenterX(CGSize containerSize, CGSize size) {
71  return NICGFloatFloor((containerSize.width - size.width) / 2.f);
72 }
73 
74 CGFloat NICenterY(CGSize containerSize, CGSize size) {
75  return NICGFloatFloor((containerSize.height - size.height) / 2.f);
76 }
77 
78 CGRect NIFrameOfCenteredViewWithinView(UIView* viewToCenter, UIView* containerView) {
79  CGPoint origin;
80  CGSize containerViewSize = containerView.bounds.size;
81  CGSize viewSize = viewToCenter.frame.size;
82  origin.x = NICenterX(containerViewSize, viewSize);
83  origin.y = NICenterY(containerViewSize, viewSize);
84  return CGRectMake(origin.x, origin.y, viewSize.width, viewSize.height);
85 }
86 
87 CGSize NISizeOfStringWithLabelProperties(NSString *string, CGSize constrainedToSize, UIFont *font, NSLineBreakMode lineBreakMode, NSInteger numberOfLines) {
88  if (string.length == 0) {
89  return CGSizeZero;
90  }
91 
92  CGFloat lineHeight = font.lineHeight;
93  CGSize size = CGSizeZero;
94 
95  if (numberOfLines == 1) {
96  size = [string sizeWithFont:font forWidth:constrainedToSize.width lineBreakMode:lineBreakMode];
97 
98  } else {
99  size = [string sizeWithFont:font constrainedToSize:constrainedToSize lineBreakMode:lineBreakMode];
100  if (numberOfLines > 0) {
101  size.height = MIN(size.height, numberOfLines * lineHeight);
102  }
103  }
104 
105  return size;
106 }
107 
108 #pragma mark - NSRange
109 
110 NSRange NIMakeNSRangeFromCFRange(CFRange range) {
111  // CFRange stores its values in signed longs, but we're about to copy the values into
112  // unsigned integers, let's check whether we're about to lose any information.
113  NIDASSERT(range.location >= 0 && range.location <= NSIntegerMax);
114  NIDASSERT(range.length >= 0 && range.length <= NSIntegerMax);
115  return NSMakeRange(range.location, range.length);
116 }
117 
118 #pragma mark - NSData
119 
120 NSString* NIMD5HashFromData(NSData* data) {
121  unsigned char result[CC_MD5_DIGEST_LENGTH];
122  bzero(result, sizeof(result));
123  CC_MD5_CTX md5Context;
124  CC_MD5_Init(&md5Context);
125  size_t bytesHashed = 0;
126  while (bytesHashed < [data length]) {
127  CC_LONG updateSize = 1024 * 1024;
128  if (([data length] - bytesHashed) < (size_t)updateSize) {
129  updateSize = (CC_LONG)([data length] - bytesHashed);
130  }
131  CC_MD5_Update(&md5Context, (char *)[data bytes] + bytesHashed, updateSize);
132  bytesHashed += updateSize;
133  }
134  CC_MD5_Final(result, &md5Context);
135 
136  return [NSString stringWithFormat:
137  @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
138  result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],
139  result[8], result[9], result[10], result[11], result[12], result[13], result[14],
140  result[15]
141  ];
142 }
143 
144 NSString* NISHA1HashFromData(NSData* data) {
145  unsigned char result[CC_SHA1_DIGEST_LENGTH];
146  bzero(result, sizeof(result));
147  CC_SHA1_CTX sha1Context;
148  CC_SHA1_Init(&sha1Context);
149  size_t bytesHashed = 0;
150  while (bytesHashed < [data length]) {
151  CC_LONG updateSize = 1024 * 1024;
152  if (([data length] - bytesHashed) < (size_t)updateSize) {
153  updateSize = (CC_LONG)([data length] - bytesHashed);
154  }
155  CC_SHA1_Update(&sha1Context, (char *)[data bytes] + bytesHashed, updateSize);
156  bytesHashed += updateSize;
157  }
158  CC_SHA1_Final(result, &sha1Context);
159 
160  return [NSString stringWithFormat:
161  @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
162  result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],
163  result[8], result[9], result[10], result[11], result[12], result[13], result[14],
164  result[15], result[16], result[17], result[18], result[19]
165  ];
166 }
167 
168 #pragma mark - NSString
169 
170 NSString* NIMD5HashFromString(NSString* string) {
171  return NIMD5HashFromData([string dataUsingEncoding:NSUTF8StringEncoding]);
172 }
173 
174 NSString* NISHA1HashFromString(NSString* string) {
175  return NISHA1HashFromData([string dataUsingEncoding:NSUTF8StringEncoding]);
176 }
177 
178 BOOL NIIsStringWithWhitespaceAndNewlines(NSString* string) {
179  NSCharacterSet* notWhitespaceAndNewlines = [[NSCharacterSet whitespaceAndNewlineCharacterSet] invertedSet];
180  return [string isKindOfClass:[NSString class]] && [string rangeOfCharacterFromSet:notWhitespaceAndNewlines].length == 0;
181 }
182 
183 NSComparisonResult NICompareVersionStrings(NSString* string1, NSString* string2) {
184  NSArray *oneComponents = [string1 componentsSeparatedByString:@"a"];
185  NSArray *twoComponents = [string2 componentsSeparatedByString:@"a"];
186 
187  // The parts before the "a"
188  NSString *oneMain = [oneComponents objectAtIndex:0];
189  NSString *twoMain = [twoComponents objectAtIndex:0];
190 
191  // If main parts are different, return that result, regardless of alpha part
192  NSComparisonResult mainDiff;
193  if ((mainDiff = [oneMain compare:twoMain]) != NSOrderedSame) {
194  return mainDiff;
195  }
196 
197  // At this point the main parts are the same; just deal with alpha stuff
198  // If one has an alpha part and the other doesn't, the one without is newer
199  if ([oneComponents count] < [twoComponents count]) {
200  return NSOrderedDescending;
201 
202  } else if ([oneComponents count] > [twoComponents count]) {
203  return NSOrderedAscending;
204 
205  } else if ([oneComponents count] == 1) {
206  // Neither has an alpha part, and we know the main parts are the same
207  return NSOrderedSame;
208  }
209 
210  // At this point the main parts are the same and both have alpha parts. Compare the alpha parts
211  // numerically. If it's not a valid number (including empty string) it's treated as zero.
212  NSNumber *oneAlpha = [NSNumber numberWithInt:[[oneComponents objectAtIndex:1] intValue]];
213  NSNumber *twoAlpha = [NSNumber numberWithInt:[[twoComponents objectAtIndex:1] intValue]];
214  return [oneAlpha compare:twoAlpha];
215 }
216 
217 NSDictionary* NIQueryDictionaryFromStringUsingEncoding(NSString* string, NSStringEncoding encoding) {
218  NSCharacterSet* delimiterSet = [NSCharacterSet characterSetWithCharactersInString:@"&;"];
219  NSMutableDictionary* pairs = [NSMutableDictionary dictionary];
220  NSScanner* scanner = [[NSScanner alloc] initWithString:string];
221 
222  while (![scanner isAtEnd]) {
223  NSString* pairString = nil;
224  [scanner scanUpToCharactersFromSet:delimiterSet intoString:&pairString];
225  [scanner scanCharactersFromSet:delimiterSet intoString:NULL];
226 
227  NSArray* kvPair = [pairString componentsSeparatedByString:@"="];
228  if (kvPair.count == 1 || kvPair.count == 2) {
229  NSString* key = [kvPair[0] stringByReplacingPercentEscapesUsingEncoding:encoding];
230 
231  NSMutableArray* values = pairs[key];
232  if (nil == values) {
233  values = [NSMutableArray array];
234  pairs[key] = values;
235  }
236 
237  if (kvPair.count == 1) {
238  [values addObject:[NSNull null]];
239 
240  } else if (kvPair.count == 2) {
241  NSString* value = [kvPair[1] stringByReplacingPercentEscapesUsingEncoding:encoding];
242  [values addObject:value];
243  }
244  }
245  }
246  return [pairs copy];
247 }
248 
250  CFStringRef buffer = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
251  (__bridge CFStringRef)parameter,
252  NULL,
253  (__bridge CFStringRef)@"!*'();:@&=+$,/?%#[]",
254  kCFStringEncodingUTF8);
255 
256  NSString* result = [NSString stringWithString:(__bridge NSString *)buffer];
257  CFRelease(buffer);
258  return result;
259 }
260 
261 NSString* NIStringByAddingQueryDictionaryToString(NSString* string, NSDictionary* query) {
262  NSMutableArray* pairs = [NSMutableArray array];
263  for (NSString* key in [query keyEnumerator]) {
264  NSString* value = NIStringByAddingPercentEscapesForURLParameterString([query objectForKey:key]);
265  NSString* pair = [NSString stringWithFormat:@"%@=%@", key, value];
266  [pairs addObject:pair];
267  }
268 
269  NSString* params = [pairs componentsJoinedByString:@"&"];
270  if ([string rangeOfString:@"?"].location == NSNotFound) {
271  return [string stringByAppendingFormat:@"?%@", params];
272 
273  } else {
274  return [string stringByAppendingFormat:@"&%@", params];
275  }
276 }
277 
278 #pragma mark - General Purpose
279 
280 // Deprecated.
281 CGFloat boundf(CGFloat value, CGFloat min, CGFloat max) {
282  return NIBoundf(value, min, max);
283 }
284 
285 // Deprecated.
286 NSInteger boundi(NSInteger value, NSInteger min, NSInteger max) {
287  return NIBoundi(value, min, max);
288 }
289 
290 CGFloat NIBoundf(CGFloat value, CGFloat min, CGFloat max) {
291  if (max < min) {
292  max = min;
293  }
294  CGFloat bounded = value;
295  if (bounded > max) {
296  bounded = max;
297  }
298  if (bounded < min) {
299  bounded = min;
300  }
301  return bounded;
302 }
303 
304 NSInteger NIBoundi(NSInteger value, NSInteger min, NSInteger max) {
305  if (max < min) {
306  max = min;
307  }
308  NSInteger bounded = value;
309  if (bounded > max) {
310  bounded = max;
311  }
312  if (bounded < min) {
313  bounded = min;
314  }
315  return bounded;
316 }
NSRange NIMakeNSRangeFromCFRange(CFRange range)
Create an NSRange object from a CFRange object.
NSDictionary * NIQueryDictionaryFromStringUsingEncoding(NSString *string, NSStringEncoding encoding)
Parses a URL query string into a dictionary where the values are arrays.
NSString * NIMD5HashFromData(NSData *data)
Calculates an md5 hash of the data using CC_MD5.
CGRect NIRectContract(CGRect rect, CGFloat dx, CGFloat dy)
Modifies only the right and bottom edges of a CGRect.
CG_INLINE CGFloat NICGFloatFloor(CGFloat x)
floor()/floorf() sized for CGFloat
NSString * NIMD5HashFromString(NSString *string)
Calculates an md5 hash of the string using CC_MD5.
BOOL NIIsStringWithWhitespaceAndNewlines(NSString *string)
Returns a Boolean value indicating whether the string is a NSString object that contains only whitesp...
CGRect NIRectExpand(CGRect rect, CGFloat dx, CGFloat dy)
Modifies only the right and bottom edges of a CGRect.
CGFloat boundf(CGFloat value, CGFloat min, CGFloat max)
Deprecated method.
NSInteger boundi(NSInteger value, NSInteger min, NSInteger max)
Deprecated method.
NSString * NISHA1HashFromData(NSData *data)
Calculates a sha1 hash of the data using CC_SHA1.
NSString * NISHA1HashFromString(NSString *string)
Calculates a sha1 hash of the string using CC_SHA1.
CGSize NISizeOfStringWithLabelProperties(NSString *string, CGSize constrainedToSize, UIFont *font, NSLineBreakMode lineBreakMode, NSInteger numberOfLines)
Returns the size of the string with given UILabel properties.
CGFloat NIBoundf(CGFloat value, CGFloat min, CGFloat max)
Bounds a given value within the min and max values.
NSInteger NIBoundi(NSInteger value, NSInteger min, NSInteger max)
Bounds a given value within the min and max values.
NSComparisonResult NICompareVersionStrings(NSString *string1, NSString *string2)
Compares two strings expressing software versions.
CGRect NIRectShift(CGRect rect, CGFloat dx, CGFloat dy)
Modifies only the top and left edges of a CGRect.
NSInvocation * NIInvocationWithClassTarget(Class targetClass, SEL selector)
Construct an NSInvocation for a class method given a class object and a selector. ...
CGFloat NICenterX(CGSize containerSize, CGSize size)
Returns the x position that will center size within containerSize.
CGFloat NICenterY(CGSize containerSize, CGSize size)
Returns the y position that will center size within containerSize.
NSString * NIStringByAddingQueryDictionaryToString(NSString *string, NSDictionary *query)
Appends a dictionary of query parameters to a string, adding the ? character if necessary.
NSInvocation * NIInvocationWithInstanceTarget(NSObject *targetObject, SEL selector)
Construct an NSInvocation with an instance of an object and a selector.
CGRect NIFrameOfCenteredViewWithinView(UIView *viewToCenter, UIView *containerView)
Returns a rect that will center viewToCenter within containerView.
NSString * NIStringByAddingPercentEscapesForURLParameterString(NSString *parameter)
Returns a string that has been escaped for use as a URL parameter.
CGRect NIEdgeInsetsOutsetRect(CGRect rect, UIEdgeInsets outsets)
Inverse of UIEdgeInsetsInsetRect.