有两年没有开发iOS了,最近又开始接触iOS开发,发现好多旧知识忘记了,好多新知识还不知道。
最近正在学习中,一些心得做下memo,也可以和大家分享一下。
初始化方法的返回值类型(类名,id,instancetype)
初始化方法的返回值一般设成id。为什么呢?为什么不设成类名呢?如果你设成类名,子类就不好处理了。比如说子类想覆盖父类的初始化方法,但想返回自己的类型,就不好处理了。所以一般返回id类型。
但是id类型不是type safe的。比如说,NSString *str = [NSArray array]; 编译器是检查不是来的,等到运行的时候就会崩溃的。
后来LLVM编译器出来之后,建议使用instancetype来代替id。凡是返回值是instancetype的方法,编译器都会检查返回值,如果没有返回本类或者子类,都会报编译错误。
NSInteger和int,long
NSInteger类型可以代表一个int类型或者一个long类型,我们推荐使用NSInteger,是因为它会根据手机的处理器来决定到底用int还是long,如果处理器是32位的,那么就用32位的int,如果处理器是64位的,那么就用64位的long。从A7处理器(iPhone 5S)开始,苹果开始采用64位的处理器。
iOS中的单例模式
从iOS4.0开始,GCD横空出世,不仅方便了多线程开发,也引入了一个适合实现单例模式的函数dispatch_once。一下是具体代码:
+ (instancetype)sharedInstance { static MyClass *shared = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shared = [[self alloc] init]; }); return shared; }
onceToken是一个检查代码块是否调用过的一个谓词。dispatch_once不仅能保证代码只调用一次,还是线程安全的,所以就不需要用@synchronized了。确实很方便。
关于self.name和_name的区别
name是属性,通过self.name来调用,调用的是get方法,可以在类外使用。_name是成员变量,只能在.m文件中使用。
一般来说,.m文件中推荐使用_name。因为使用self.name有时候会带来不必要的麻烦。举个例子:代码1self.name = [[NSString alloc] init];
代码2_name = [[NSString alloc] init]; self.name = @"name";
关于_name的引用计数,代码2是正常的,引用计数为1,代码1时不正常的,引用计数是2。我们来分析一下:self.name = 会调用set方法,set方法如下:代码1,alloc了引用计数+1,调用set方法,[_name release]的时候_name还是nil,所以不起作用,最后有retain了,引用计数变为2。- (void)setName:(NSString *)name { if (_name != name) { [_name release]; _name = [name retain]; } }
代码2,alloc了引用计数+1,调用set方法,[_name release]的时候_name还是不为nil,所以起了作用,所以,最后引用计数还是为1。所以,代码1是会造成内存泄漏的。
关于ARC中的strong和copy
@property (copy, nonatomic) NSString *copyName; @property (strong, nonatomic) NSString *strongName;
上面的NSString到底用stong还是copy呢?ARC中的stong其实就是retain,就是引用计数+1。而copy会拷贝一个副本出来。但是,具体的区别是什么呢?NSMutableString *name = [NSMutableString stringWithFormat:@"name"]; self.copyName = name; self.strongName = name;
@"name"存在于堆上面的某个地址中,假设它的地址是0xB1。name存在于栈上面的某个地址中,假设它的地址是0xA1。同样,我们假设strongName的栈地址是0xA2,copyName的栈地址是0xA3。strong只是引用计数+1,不会重新分配内存。copy会拷贝副本,所以会重新分配内存,假设分配到0xB2。所以存在以下指向关系:
由于NSMutableString是NSString的子类,子类可以直接赋给父类。当NSMutableString类型的name在另外一个类里面,通过stringName或copyName所在类的实例来赋值的时候,如果是strongName,会有潜在的风险。因为name和strongName是指向同一块内存的,而name又是可变类型的,所以当外面的name的值变化了,里面的strongName会跟着变化,而我们往往是不希望strongName变化的。要解决此问题,copy就起到了作用,因为copyName又拷贝了一份内存地址,所以和原来的name是相互独立的,不管name改成什么值,copyName永远是@“name”。所以,如果一个类它的子类有可变类型的,那么我们推荐用copy而不是strong。这样的类有NSArray, NSDictionary, NSSet, NSString, NSData等等。有人担心从外部传进来的如果不是可变类型的,那么岂不是每次都copy,会存在性能问题?其实ARC会进行判断,如果赋的值时可变类型的,那么就会执行copy,如果赋的值时不可变类型的,那么就不会执行copy,而是让引用计数+1,就和strong一样了。
Object Literals
Object Literals可以帮助你效率的定义数字,数组和字典对象。类似于Java的Auto Boxing功能。在WWDC2012上提出,Xcode4.4中就可以使用了。那么到底Object Literals怎么使用呢?首先让我们先来看一下以前定义数字,数组和字典对象的方法:再来看一下Object Literals的写法:NSNumber * number = [NSNumber numberWithInt:1]; NSArray * array = [NSArray arrayWithObjects:@"one", @"two", nil]; NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:@"value1", @"key1", @"value2", @"key2", nil];
是不是发现简单了许多?再也不用加nil了,而且字典的key,value也不用倒着写了。NSNumber * number = @1; NSArray * array = @[@"one", @"two"]; NSDictionary * dict = @{@"key1":@"value1", @"key2":@"value2"};
更多事例如下:// 整数 NSNumber *fortyTwo = @42; // 等价于 [NSNumber numberWithInt:42] NSNumber *fortyTwoUnsigned = @42U; // 等价于 [NSNumber numberWithUnsignedInt:42U] NSNumber *fortyTwoLong = @42L; // 等价于 [NSNumber numberWithLong:42L] NSNumber *fortyTwoLongLong = @42LL; // 等价于 [NSNumber numberWithLongLong:42LL] // 浮点数 NSNumber *piFloat = @3.141592654F; // 等价于 [NSNumber numberWithFloat:3.141592654F] NSNumber *piDouble = @3.1415926535; // 等价于 [NSNumber numberWithDouble:3.1415926535] // 布尔值 NSNumber *yesNumber = @YES; // 等价于 [NSNumber numberWithBool:YES] NSNumber *noNumber = @NO; // 等价于 [NSNumber numberWithBool:NO] // 空数组 NSArray * array = @[]; // 等价于 [NSArray array] // 空的字典 NSDictionary * dict = @{}; // 等价于 [NSDictionary dictionary]
Xcode为了方便开发者将旧代码快速的转换为新代码,提供了转换工具(Edit –> Refactor –> Convert to Modern Objective-C Syntax)。
用block遍历元素
遍历数组的两种方式:NSArray *array = ...; for (int i = 0; i < array.count; i++) { ...; } for (NSString *str in array) { ...; }
遍历字典的方式:NSDictionary * dict = …; NSArray * keys = [dict allKeys]; for (NSString * key in keys) { NSString * value = [dict objectForKey:key]; }
在iOS4.0之后,可以用block方便的遍历数组和字典:[array enumerateObjectsUsingBlock:^(NSString * obj, NSUInteger idx, BOOL *stop) { }]; [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { }];