iOS开发中的单元测试(三)——URLManager中的测试用例解析

本文转载至 http://www.cocoachina.com/cms/plus/view.php?aid=8088

此前,我们在《iOS开发中的单元测试(一)&(二)》中介绍了从使用者的角度对比当下比较流行的两款单元测试框架OCUnit和GHUnit,这篇文章中我们将介绍一款导航控件URLManager

URLManager是一个基于UINavigationController和UIViewController,以URL Scheme为设计基础的导航控件,目的是实现ViewController的松耦合,不依赖。

准备框架,定义基类

首先按照之前的两篇文章介绍的方法导入单元测试框架和匹配引擎框架,建立好测试Target,并配置编译选项。

定义测试用例基类:UMTestCase(代码1),其他用例全部继承自UMTestCase。

  1. #import
  2. @interface UMTestCase : GHTestCase
  3. @end

代码1,UMTestCase,用例基类

构建用例

URLManager工具类(UMTools)测试用例(UMToolsTestCase)。UMTools中扩展了NSURL,NSString和UIView,方法涉及到给URL添加QueryString和从QueryString中读取参数,对字符串做子串判断,进行URL的编码和解码,对UIView的x,y,width和height的直接读写等。需要在用例中定义测试过程中会使用到属性(代码2), 并在setUpClass中初始化他们(代码3)。

  1. 代码2,定义属性// 普通字符串,带有字母和数字
  2. @property   (strong, nonatomic)     NSString    *string;
  3. // 普通字符串,仅带有字母
  4. @property   (strong, nonatomic)     NSString    *stringWithoutNumber;
  5. // 将被做URLEncode的字符串,含有特殊字符和汉字
  6. @property   (strong, nonatomic)     NSString    *toBeEncode;
  7. // 把 toBeEncode 编码后的串
  8. @property   (strong, nonatomic)     NSString    *encoded;
  9. // 普通的URL,带有QueryString
  10. @property   (strong, nonatomic)     NSURL       *url;
  11. // 去掉上边一个URL的QueryString
  12. @property   (strong, nonatomic)     NSURL       *noQueryUrl;
  13. // 一个普通的UIView
  14. @property   (strong, nonatomic)     UIView      *view;
  1. (void)setUpClass
  2. {
  3. self.string                 = @"NSString For Test with a number 8848.";
  4. self.stringWithoutNumber    = @"NSString For Test.";
  5. self.toBeEncode             = @"[email protected]#$%^&*()_+=-[]{}:;\"‘<>.,/?123qwe汉字";
  6. self.encoded                = @"%7E%21%40%23%24%25%5E%26%2A%28%29_%2B%3D-%5B%5D%
  7. 7B%7D%3A%3B%22%27%3C%3E.%2C%2F%3F123qwe%E6%B1%89%E5%AD%97";
  8. self.url                    = [NSURL URLWithString:@"http://example.com
  9. /patha/pathb/?p2=v2&p1=v1"];
  10. self.noQueryUrl             = [NSURL URLWithString:@"http://example.com
  11. /patha/pathb/"];
  12. self.view                   = [[UIView alloc] initWithFrame:CGRectMake(10.0f,
  13. 10.0f, 100.0f, 100.f)];
  14. }

代码3,初始化属性

使用单元测试框架中的断言处理简单用例

单元测试是白盒测试,要做到路径覆盖(代码4)。 对“ContainsString”的测试进行正向和反向两种情况(即YES和NO两种返回结果)。

  1. #pragma mark - UMString
  2. - (void)testUMStringContainsString
  3. {
  4. NSString *p = @"For";
  5. NSString *np = @"BAD";
  6. GHAssertTrue([self.string containsString:p],
  7. @"\"%@\" should contains \"%@\".",
  8. self.string, p);
  9. GHAssertFalse([self.string containsString:np],
  10. @"\"%@\" should not contain \"%@\".",
  11. self.string, p);

代码4,字符串测试用例

同时单元测试又要对功能负责,因此在路径覆盖之外还要尽量照顾到完整的功能。例如,对URLEncode的测试(代码5),要对尽量全面的特殊字符进行测试,而不是从源码实现中取出枚举的字符。

  1. (void)testUrlencode
  2. {
  3. GHAssertEqualStrings([self.toBeEncode urlencode], self.encoded,
  4. @"URLEncode Error.",
  5. self.toBeEncode, self.encoded);
  6. GHAssertEqualStrings([self.encoded urldecode], self.toBeEncode,
  7. @"URLDecode Error.",
  8. self.encoded, self.toBeEncode);
  9. }

代码5,URLEncode测试用例

在进行这个测试之前,urlencode的实现忽视了对“~”的编码,正是由于单元测试用例所取的特殊字符是单独列举,并非从实现枚举中获取,检查出了这个错误。

引入匹配引擎,使用匹配引擎默认规则

前文提到过匹配引擎可以使测试用例中的断言更加丰富,URLManager的用例中也使用了匹配引擎:OCHamcrest

在此前的介绍中提到,引入OCHamcrest可以通过定义“HC_SHORTHAND”来开启匹配引擎的简写模式。因为开启简写模式后匹配规则中的“containsString”规则和上述例子(代码5)中的“containsString:”方法命名冲突,导致测试程序无法正常运行,所以这个工程直接使用了类似“HC_asserTaht”这样带有HC前缀的完整命名。

我建议使用匹配引擎的开发者谨慎开启简写功能,OCHamcrest的匹配规则简写通常是很常见的单词,非常容易与工程中的类定义或方法定义重名。即使当下没有规则和方法名发生冲突,随着工程代码量的增加,一旦出现命名冲突的情况,重构的成本将非常高。

匹配引擎可以提供更丰富的断言,最简单的例如,URLManager的UMURL扩展支持向一个URL上添加参数,对这个方法测试断言就用到了匹配某个字符串是否包含某子串的规则(代码6)。

  1. #pragma mark - UMURL
  2. - (void)testAddParams
  3. {
  4. NSURL *queryUrl = [self.noQueryUrl addParams:@{@"p1":@"v1",@"p2":@"v2"}];
  5. HC_assertThat(queryUrl.absoluteString, HC_containsString(@"p1=v1"));
  6. HC_assertThat(queryUrl.absoluteString, HC_containsString(@"p2=v2"));
  7. }

代码6,URL参数测试用例

匹配规则中的陷阱

由于匹配规则的粒度较细,所以对于某些运行结果需要考虑到多种情况,否则正常的结果也可能会断言失败。

例如测试用例期望得到一个空容器(例如:NSArray),而SDK则认为这个容器已经没有存在的必要而释放了他,返回的是一个nil。对removeAllSubviews的测试中,对一个view调用removeAllSubviews方法,期望view.subviews为空。在SDK 6.x甚至SDK 7 DP1之前,都是没问题的,但在SDK 7 DP3中,SDK会把所有清空的容器和对象释放,以回收系统资源。在这种条件下view.subviews返回的就是nil,如果只是做类似HC_empty()这样的匹配,断言会失败,所以在断言之前做一个subviews属性的空判断(代码7)。

  1. (void)testRemoveAllSubviews
  2. {
  3. UIView *subViewA = [[UIView alloc] init];
  4. UIView *subViewB = [[UIView alloc] init];
  5. [self.view addSubview:subViewA];
  6. [self.view addSubview:subViewB];
  7. HC_assertThat(self.view.subviews, HC_containsInAnyOrder(subViewA, subViewB, nil));
  8. [self.view removeAllSubviews];
  9. if (nil != self.view.subviews) {
  10. HC_assertThat(self.view.subviews, HC_empty());
  11. }
  12. }

代码7,removeAllSubviews用例

另外,在默认匹配规则中会有一些容易产生歧义的命名,以collection的containsInAnyOrder为例:匹配对象是一个collection对象(也就是遵循NSFastEnumeration协议的对象,NSArray等),给出若干个匹配规则或元素。期待这个规则匹配该对象是否包含给出的若干元素,且不关心顺序。但在实际测试过程中会发现,这个规则要求给出的元素必须是该collection对象的完备集,也就是说要求给出的元素列表和要匹配的容器对象中的元素必须是相等的结合,但允许不关注顺序。

对UMNavigationController的测试中,需要判断增加一项URL Mapping是否生效,如果使用该匹配规则,就不能单纯判断config是否包含增量的URL,要断言成功必须连同此前config属性初始化写入的值一起考虑,使用一个完整的元素集合进行匹配(代码8)。

  1. (void)testAddConfig
  2. {
  3. [UMNavigationController setViewControllerName:@"ViewControllerA" forURL:@"
  4. um://viewa2"];
  5. NSMutableDictionary *config = [UMNavigationController config];
  6. NSLog(@"%@", [config allKeys]);
  7. HC_assertThat([config allKeys],
  8. HC_containsInAnyOrder(HC_equalTo(@"um://viewa2"), HC_equalTo(@"
  9. um://viewa"),
  10. HC_equalTo(@"um://viewb"), nil));
  11. GHAssertEqualStrings(config[@"um://viewa2"], @"ViewControllerA",
  12. @"config set error.");
  13. }

代码8,AddConfig用例

自建匹配规则

上述例子表明匹配规则往往无法恰好满足测试需求,需要对默认规则进行升级。

升级一个匹配规则,首先阅读OCHamcrest默认规则源码,找到无法满足需求的代码。上述HC_containsInAnyOrder的例子中,个性需求是某个collection是否包含某几个元素(而非完整集合),而默认规则只能匹配完整集合。阅读源码(代码9)可以发现,在maches:describingMismatchTo:函数中,对规则对象的collection属性(要进行匹配的容器对象)进行遍历,并逐个调用matches:方法。matches:方法中针对每个collection属性中的元素遍历匹配规则集合(matchers),并从规则集合(matchers)中移除匹配成功的规则。当给出的规则集合(matchers)全部成功匹配过之后,matchers属性已经为空。若此时对collection属性的遍历继续进行,matches:方法就不会进入匹配逻辑,直接跳出循环返回NO,导致匹配失败。

  1. (BOOL)matches:(id)item
  2. {
  3. NSUInteger index = 0;
  4. for (id matcher in matchers)
  5. {
  6. if ([matcher matches:item])
  7. {
  8. [matchers removeObjectAtIndex:index];
  9. return YES;
  10. }
  11. ++index;
  12. }
  13. [[mismatchDescription appendText:@"not matched: "] appendDescriptionOf:item];
  14. return NO;
  15. }
  16. - (BOOL)matches:(id)collection describingMismatchTo:(id)
  17. mismatchDescription
  18. {
  19. if (![collection conformsToProtocol:@protocol(NSFastEnumeration)])
  20. {
  21. [super describeMismatchOf:collection to:mismatchDescription];
  22. return NO;
  23. }
  24. HCMatchingInAnyOrder *matchSequence =
  25. [[HCMatchingInAnyOrder alloc] initWithMatchers:matchers
  26. mismatchDescription:mismatchDescription];
  27. for (id item in collection)
  28. if (![matchSequence matches:item])
  29. return NO;
  30. return [matchSequence isFinishedWith:collection];
  31. }

代码9,HC_containsInAnyOrder规则中的两个核心方法

我们的需求是,当匹配规则列表全部成功匹配之后就是此次匹配成功的标志。所以需要修改matches:方法中的匹配逻辑,当匹配列表为空则返回YES。

升级方案是继承HCIsCollectionContainingInAnyOrder创建一个新的匹配规则类HCIsCollectionHavingInAnyOrder;重新定义匹配规则HC_hasInAnyOrder;重写调用matches:方法的matches:describingMismatchTo:方法(代码10);更新的核心是定义一个HCMatchingInAnyOrderEx类,按照个性需求定义matches:方法(代码11)。使用这个修改过的匹配规则就可以判断一个Collection是否包含某个几个元素了。

  1. @implementation HCIsCollectionHavingInAnyOrder
  2. - (BOOL)matches:(id)collection describingMismatchTo:(id)
  3. mismatchDescription
  4. {
  5. if (![collection conformsToProtocol:@protocol(NSFastEnumeration)])
  6. {
  7. [super describeMismatchOf:collection to:mismatchDescription];
  8. return NO;
  9. }
  10. HCMatchingInAnyOrderEx *matchSequence =
  11. [[HCMatchingInAnyOrderEx alloc] initWithMatchers:matchers
  12. mismatchDescription:mismatchDescription];
  13. for (id item in collection)
  14. if (![matchSequence matches:item])
  15. return NO;
  16. return [matchSequence isFinishedWith:collection];
  17. }
  18. @end
  19. id HC_hasInAnyOrder(id itemMatch, ...)
  20. {
  21. NSMutableArray *matchers = [NSMutableArray arrayWithObject:HCWrapInMatcher
  22. (itemMatch)];
  23. va_list args;
  24. va_start(args, itemMatch);
  25. itemMatch = va_arg(args, id);
  26. while (itemMatch != nil)
  27. {
  28. [matchers addObject:HCWrapInMatcher(itemMatch)];
  29. itemMatch = va_arg(args, id);
  30. }
  31. va_end(args);
  32. return [HCIsCollectionHavingInAnyOrder isCollectionContainingInAnyOrder:matchers];
  33. }

代码10,HCIsCollectionHavingInAnyOrder实现

  1. (BOOL)matches:(id)item
  2. {
  3. NSUInteger index = 0;
  4. BOOL matched = (0 >= [self.matchers count]);
  5. for (id matcher in self.matchers)
  6. {
  7. if ([matcher matches:item]) {
  8. [self.matchers removeObjectAtIndex:index];
  9. matched = YES;
  10. return YES;
  11. }
  12. ++index;
  13. }
  14. return matched;
  15. }

代码11,更新过的matches:方法

  1. (void)testAddConfig
  2. {
  3. [UMNavigationController setViewControllerName:@"ViewControllerA" forURL:@"um://
  4. viewa2"];
  5. NSMutableDictionary *config = [UMNavigationController config];
  6. HC_assertThat([config allKeys],
  7. HC_hasInAnyOrder(HC_equalTo(@"um://viewa2"), nil));
  8. GHAssertEqualStrings(config[@"um://viewa2"], @"ViewControllerA",
  9. @"config set error.");
  10. }

代码12,使用新规则的测试用例

另一个方面,在测试过程中会出现各种逻辑,有时默认规则根本无法覆盖,需要完全自建规则。例如对CGPoint和CGSize的相等匹配,如代码13中对UMView的size和origin方法测试。OCHamcrest的默认规则中根本没有提供任何针对CGPoint和CGSize两个结构体的匹配规则,所以要完成这个测试就需要自己定义针对这两种数据结构的匹配规则。

  1. #pragma mark - UMView
  2. HC_assertThat(NSStringFromCGSize(self.view.size),
  3. HC_equalToSize(self.view.frame.size));
  4. HC_assertThat(NSStringFromCGPoint(self.view.origin),
  5. HC_equalToPoint(CGPointMake(self.view.frame.origin.x, self.
  6. view.frame.origin.y)));

代码13,UMView测试用例片段

自定义匹配规则的详细说明可以参见上一篇《iOS开发中的单元测试(一)&(二)》,本文只对开发自定义规则中遇到的问题和需要特殊处理的方面进行解释。

OCHamcrest的匹配规则要求被匹配的必须是一个有强引用的对象,所以当被匹配的是一个struct结构(如CGPoint)需要进行一次转换,如代码14中定义的这个规则扩展——OBJC_EXPORT id HC_equalToPoint(CGPoint point)。 在CGPoint相等匹配的规则中,需要先把CGPoint转为字符串后传入断言方法,规则会把这个字符串储存起来,并与后续给出的CGPoint进行比较。匹配引擎对传入的需要进行匹配的参数类型没做任何限制,所以规则可以直接传入CGPoint。

开发自定义规则一般建议同时定义SHORTHAND,即使当前单元测试中不会用到(例如本文中的测试),但这个规则被其他复用的时候,可能会用到SHORTHAND命名。

  1. #import
  2. OBJC_EXPORT id HC_equalToPoint(CGPoint point);
  3. #ifdef HC_SHORTHAND
  4. #define equalToPoint HC_equalToPoint
  5. #endif
  6. @interface HCIsEqualToPoint : HCBaseMatcher
  7. + (id)equalToPoint:(CGPoint)point;
  8. - (id)initWithPoint:(CGPoint)point;
  9. @property (nonatomic, assign)       CGFloat     x;
  10. @property (nonatomic, assign)       CGFloat     y;
  11. @end

代码14,扩展匹配规则HC_equalToPoint定义

在匹配规则的过程中,有一个点需要特别注意,即对匹配对象类型和完整性的判断。往往开发者把注意力都放在对对象值的匹配上,而忽略了类型和完整性这类判断,最终导致整个用例运行失败,但无法准确定位出错的位置。上面提到的对subviews是否为空的判断也是这样的一个例子。所以在自定义的匹配规则中就需要考虑到这方面的问题,如代码15的matches:方法中,先要对传入的泛型对象item校验是否为字符串,后再转化为CGPoint对象,并进行相应比对。示例中给出的是一种较简单的情况,在更复杂的情况下,除了对泛型对象的类进行校验,还要校验其是否响应某方法,属性类型,空判断,等。

  1. #import "HCIsEqualToPoint.h"
  2. #import
  3. id  HC_equalToPoint(CGPoint point)
  4. {
  5. return [HCIsEqualToPoint equalToPoint:point];
  6. }
  7. @implementation HCIsEqualToPoint
  8. + (id)equalToPoint:(CGPoint)point
  9. {
  10. return [[self alloc] initWithPoint:point];
  11. }
  12. - (id)initWithPoint:(CGPoint)point
  13. {
  14. self = [super init];
  15. if (self) {
  16. self.x = point.x;
  17. self.y = point.y;
  18. }
  19. return self;
  20. }
  21. - (BOOL)matches:(id)item
  22. {
  23. if (! [item isKindOfClass:[NSString class]]) {
  24. return NO;
  25. }
  26. CGPoint point = CGPointFromString((NSString *)item);
  27. return (point.x == self.x && point.y == self.y);
  28. }
  29. - (void)describeTo:(id)description
  30. {
  31. [description appendText:@"Point not equaled."];
  32. }
  33. @end

代码15,扩展匹配规则HC_equalToPoint实现

一个操作多个测试方法

以上提到的几个例子中所测试的都是非常简单的操作,所以一个测试方法覆盖了一个或多个操作,但对于较复杂的操作,往往需要多个测试方法,循序渐进的断言。例如测试通过URL生成UMViewController的用例,生成一个UMViewController实例由简单到复杂可以有三种简单方式:简单的URL生成,带参数的URL生成和带Query字典的URL生成,此外还有URL参数和Query字典共用的方式。所以对于这个操作至少需要使用4个测试方法(代码16)分别进行测试。

  1. (void)testViewControllerForSimpleURL
  2. {
  3. self.viewControllerA = (ViewControllerA *)[self.navigator
  4. viewControllerForURL:
  5. [NSURL URLWithString:@"um://viewa"]
  6. withQuery:nil];
  7. HC_assertThat(self.viewControllerA, HC_instanceOf([UMViewController class]));
  8. HC_assertThat(self.viewControllerA, HC_isA([ViewControllerA class]));
  9. }
  10. - (void)testViewControllerForURLWithArgs
  11. {
  12. self.viewControllerA = (ViewControllerA *)[self.navigator
  13. viewControllerForURL:[NSURL URLWithString:@"um://viewa?
  14. p1=v1&p2=v2"]
  15. withQuery:nil];
  16. HC_assertThat(self.viewControllerA, HC_instanceOf([UMViewController class]));
  17. HC_assertThat(self.viewControllerA, HC_isA([ViewControllerA class]));
  18. HC_assertThat([self.viewControllerA.params allKeys], HC_containsInAnyOrder
  19. (@"p1", @"p2", nil));
  20. GHAssertEqualStrings(self.viewControllerA.params[@"p1"], @"v1", @"param error.");
  21. GHAssertEqualStrings(self.viewControllerA.params[@"p2"], @"v2", @"param error.");
  22. }
  23. - (void)testViewControllerWithQuery
  24. {
  25. self.viewControllerA = (ViewControllerA *)[self.navigator
  26. viewControllerForURL:
  27. [NSURL URLWithString:@"um://viewa"]
  28. withQuery:@{@"k1":@"v1", @"k2":@"v2"}];
  29. HC_assertThat([self.viewControllerA.query allKeys], HC_containsInAnyOrder
  30. (@"k1", @"k2", nil));
  31. GHAssertEqualStrings(self.viewControllerA.query[@"k1"], @"v1", @"param error.");
  32. GHAssertEqualStrings(self.viewControllerA.query[@"k2"], @"v2", @"param error.");
  33. }
  34. - (void)testViewControllerForURLAndQuery
  35. {
  36. self.viewControllerA = (ViewControllerA *)[self.navigator
  37. viewControllerForURL:
  38. [NSURL URLWithString:@"um://viewa?p1=v1&p2=v2"]
  39. withQuery:@{@"k1":@"v1", @"k2":@"v2"}];
  40. HC_assertThat([self.viewControllerA.params allKeys], HC_containsInAnyOrder
  41. (@"p1", @"p2", nil));
  42. GHAssertEqualStrings(self.viewControllerA.params[@"p1"], @"v1", @"param error.");
  43. GHAssertEqualStrings(self.viewControllerA.params[@"p2"], @"v2", @"param error.");
  44. HC_assertThat([self.viewControllerA.query allKeys], HC_containsInAnyOrder
  45. (@"k1", @"k2", nil));
  46. GHAssertEqualStrings(self.viewControllerA.query[@"k1"], @"v1", @"param error.");
  47. GHAssertEqualStrings(self.viewControllerA.query[@"k2"], @"v2", @"param error.");
  48. }

代码16,测试通过URL生成UMViewController的用例

一个测试方法多次断言

除了一个操作需要多个测试方法的情况,在同一个测试方法中也会有对一个结果进行多次断言的情况(上述用例代码16中已经是这种情况,一下用例更具代表性)。这种情况发生在操作结果较为复杂的情况下,例如生成一个UMNavigationController(代码17)就是这种情况:UMNavigationController的初始化方法是带RootViewController参数的,所以初始化的实例除了判断其本身是否为UINavigationController的子类和UMNavigationController实例外,还要判断rootViewController的合法性,以及viewControllers数组的正确性。

  1. (void)testInitWihtRootViewControllerURL
  2. {
  3. UMNavigationController *navigator = [[UMNavigationController alloc]
  4. initWithRootViewControllerURL:[NSURL URLWithString:@"um://viewb"]];
  5. HC_assertThat(navigator, HC_instanceOf([UINavigationController class]));
  6. HC_assertThat(navigator, HC_isA([UMNavigationController class]));
  7. HC_assertThat(navigator.rootViewController,
  8. HC_instanceOf([UMViewController class]));
  9. HC_assertThat(navigator.rootViewController, HC_isA([ViewControllerB class]));
  10. HC_assertThatInteger(navigator.viewControllers.count, HC_equalToInteger(1));
  11. HC_assertThat(navigator.viewControllers,
  12. HC_hasInAnyOrder(HC_instanceOf([UMViewController class]), nil));
  13. HC_assertThat(navigator.viewControllers,
  14. HC_hasInAnyOrder(HC_isA([ViewControllerB class]), nil));
  15. HC_assertThat(navigator.viewControllers,
  16. HC_hasInAnyOrder(HC_is(navigator.rootViewController), nil));
  17. }

代码17,测试生成UMNavigationController的用例

总结

本文一共取了URLManager中的17段代码片段作为例子,介绍了从利用测试框架提供的断言方法进行简单的测试,一直到使用自定义匹配引擎规则创建较复杂测试用例,并且提到了部分测试引擎和匹配引擎使用过程中会遇到的陷阱。旨在推动开发者能够在开发过程中更简单高效的使用单元测试,为提升代码质量增加一份保障。读者可以在URLManager的工程中阅读更多的测试用例代码。

时间: 2024-10-11 01:37:22

iOS开发中的单元测试(三)——URLManager中的测试用例解析的相关文章

iOS开发读取plist文件、iphone中plist文件的

在Xcode中建立一个iOS项目后,会自己产生一个.plist文件,点击时会看见它显示的是类似于excel表格: 但是,如果打开方式选择Source Code,你会看见它其实是一个xml文件. 我们会做一个小例子,在这个例子中我们自己建立一个plist文件并填入数据,然后运行时读取这个plist文件,并将数据填写在界面上. 首先要知道读取plist文件的方法,一般来说,使用代码 NSString *plistPath = [[NSBundle mainBundle] pathForResourc

iOS开发- 自定义遮罩视图(引导, 功能说明)源码+解析

iOS开发- 自定义遮罩视图(引导, 功能说明)源码+解析 我们平时使用App的时候, 经常在第一次使用的时候, 会有类似"新手教程"之类的东西, 来引导我们应该如何使用这个App. 但是这个"新手教程"不同于常规的引导页(引导页指第一次打开App时候, 弹出的那种介绍视图. 他是静态的, 不需要与用户交互, 可以直接一页页翻, 或者直接跳过.)所谓的"新手教程", 就是按照App的提示, 一步步跟着完成. 那这个"新手教程"

iOS开发 多线程(一)GCD中dispatch队列知识

GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用.所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得淋漓尽致. dispatch队列的生成可以有这几种方式: 1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_

iOS开发系列文章(持续转载中……) 感谢作者,直接连接到作者文章的

C语言 iOS开发系列--C语言之基础知识 iOS开发系列--C语言之数组和字符串 iOS开发系列--C语言之指针 iOS开发系列--C语言之预处理 iOS开发系列--C语言之存储方式和作用域 iOS开发系列--C语言之构造类型 Objective-C iOS开发系列-Objective-C之基础概览 iOS开发系列--Objective-C之类和对象 iOS开发系列--Objective-C之协议.代码块.分类 iOS开发系列-Objective-C之内存管理 iOS开发系列--Objecti

iOS开发:XCTest单元测试(附上一个单例的测试代码)

测试驱动开发并不是一个很新鲜的概念了.在我最开始学习程序编写时,最喜欢干的事情就是编写一段代码,然后运行观察结果是否正确.我所学习第一门语言是c语言,用的最多的是在算法设计上,那时候最常做的事情就是编写了一段代码,如何编译运行,查看结果是否正确,很多时候,还得自己想很多特殊的(比如说零值,边界值)测试数据来检测所写代码.算法是否正确.那个时候,感觉还好,比较输出只是只是控制台的一个简单的数字或者字符.在学习iOS开发中,很多时候也是要测试的,这种输出是必须在点击一系列按钮之后才能在屏幕上显示出来

iOS开发之窥探UICollectionViewController(三) --使用UICollectionView自定义瀑布流

上篇博客的实例是自带的UICollectionViewDelegateFlowLayout布局基础上来做的Demo, 详情请看<iOS开发之窥探UICollectionViewController(二) --详解CollectionView各种回调>.UICollectionView之所以强大,是因为其具有自定义功能,这一自定义就不得了啦,自由度非常大,定制的高,所以功能也是灰常强大的.本篇博客就不使用自带的流式布局了,我们要自定义一个瀑布流.自定义的瀑布流可以配置其参数: 每个Cell的边距

一个java程序员自学IOS开发之路(三)

  2015/10/10 Day 14 装箱和拆箱 数组和字典中只能存储对象类型,其他基本类型和结构体是没有办法放到数组和字典中的,当然你也是无法给它们发送消息的(也就是说有些NSObject的方法是无法调用的),这个时候通常会用到装箱(boxing)和拆箱(unboxing).其实各种高级语言基本上都有装箱和拆箱的过程,就像Java中有基本数据类型包装类 在ObjC中我们一般将基本数据类型装箱成NSNumber类型(当然它也是NSObject的子类,但是NSNumber不能对结构体装箱),调用

iOS开发——网络编程OC篇&amp;(九)数据解析

数据解析 关于iOS开发的中数据解析的方法有两种JSON和XML,这里只做简单的介绍,会使用就可以了. JSON—— 关于JSON的解析经过很多爱好者的分析使用相同自带的还是最好的,不管是从使用的容易度还是性能方面 NSJSONSerialization 1 -(void)start 2 { 3 4 NSString* path = [[NSBundle mainBundle] pathForResource:@"Notes" ofType:@"json"]; 5

iOS开发-从16进制颜色中获取UIColor

目前iOS中设置UIColor只能使用其枚举值.RGB等方法,不能直接将常用的16进制颜色值直接转为UIColor对象,所以写了点代码,将16进制颜色值转为UIColor. 代码如下, //头文件#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface TextServcie : NSObject +(UIColor *) getColorFromHEX:(NSString *)hex; @end .m

iOS开发之获取一段字符串中的中文字和中文字符

#pragma mark -获取一段字符串中的中文字 + (NSArray *)getAStringOfChineseWord:(NSString *)string { if (string == nil || [string isEqual:@""]) { return nil; } NSMutableArray *arr = [[NSMutableArray alloc]init]; for (int i=0; i<[string length]; i++) { int a