2011斯坦福 iOS 应用开发第三课

这节课讲的内容非常之多,几乎全都是 Objective-C 的内容,因此也会需要些时间来理解。
内容如下:
1. property
2. strong vs weak
3. nil
4. BOOL
5. Instance vs. Class Methods
6. Instantiation
7. Dynamic Binding
8. Introspection
9. Foundation Framework
10. Enumeration
11. Property List

为什么要用 property

第一个原因也是首要原因是,对于实例变量来说它提供了安全性和子类可访问性。
如果我允许某人能够继承我们,那如果他们想要弄乱我们的实例变量的话,我们希望能够参与进来。因此如果他们把我们的实例变量设成了其他的东西,我们可能想要弹回检查下,确保他不会破坏它们。

第二个原因是,property 同时为延迟实例化,UI 更新,一致性检测(例如 speed < 1)等提供「值」。
这个「值」类似于一个阀门值,通过它我们可以进行延迟实例化,UI 更新。如果你通过 setter 改变了屏幕上某些对象的属性,它会很方便的标记那些需要重画的视图。
一致性检测。在第一节课的时候在 setter 中写过确保速度不会超过光速的代码,property 有许多我们需要用到的有价值的东西,因此我们不要直接访问实例变量,而是尽量使用 property。

实例变量

Q:@property 非要有一个实例变量吗?
A:很显然不是非得要有一个实例变量来辅助 @property(不要使用 @synthesize,并自己创建 setter 和 getter),如果你不想写辅助实例变量,那你就不得不自己动手制造或计算 property,而不是存储。
Q:是否我可以拥有一个不带 property 的实例变量?
A:当然可以。但目前来说还是用 property 好些。

为什么要用点号

因为用起来非常漂亮好看。self.display.text 等同于 [self.display.text …],如果你用方括号的话那你就得在一行代码里用一堆的方括号来请求方法,这非常恐怖,反之 self.display.text 就非常简洁。
同时也使得 property 的读取更明显一点,读代码的时候更容易发现这是在调用 getter。
点号还能协助 C 代码的结构体的读取。

strong vs weak

strong 和 weak 是指针的两个属性,它们都是针对对象指针的,不是任意内存里其他的指针。

strong 指针的意思是你想保留用 property/variable 引用的这个对象。只要你(或其他的对象)用 strong 指针指向它,那么编译器就会留意这个 property 指定的对象,防止它被摧毁。只有当你把 property 设为 nil 的时候对象才会被摧毁(除非一个或多个其他的对象对它仍然有 strong 引用)。

比较之,使用 weak 引用的话意味着你不想自己控制对象的生命周期。你用 weak 引用的对象只有在至少一个其他对象具有 strong 引用时才会保留。一旦没有 strong 引用了,这个对象将会被摧毁并且你的weak property 也会自动设为 nil。

iOS 中经常引用 weak 的情况是:
1. delegate properties,它们经常使用 weak 引用以避免保留循环。
2. subviews/一个试图控制器的主视图控件,因为那些试图早就被主视图强制引用了。

这是不是垃圾回收而是引用计数机制,计算内存中还有多少 strong 指针,当值为0时就表示没有 strong 指针了,那就马上释放掉。不像垃圾回收,你永远不知道什么时候才会释放。垃圾回收是不可控的,而这是完全可控的,当失去最后一个 strong 指针时,它会马上被释放掉,毫不迟疑。

nil

nil 就是0,表示一个对象指针不再指向任何东西的。所有 synthesize 生成的实例变量初始值都是 nil。

如果你希望指针指向某些东西,你可以调用 setter 或在 getter 里使用延迟实例化。

可以在一个 if 语句中隐含地测试是否为 nil。

if (obj) { } // 如果 obj 不为 nil 那么花括号里的代码将会执行

向 nil 发送消息不会使程序崩溃,事实上什么都不会执行。如果有返回值,那就返回0。
除非当你有一个返回 C 结构体的方法时并且你把它发送给一个 nil 对象,那么你会得到一个未定义的 C 结构体。这种情况要单独处理

BOOL

就是 YES 和 NO,不用 True 和 False。

注意:BOOL 必须大写。

Instance vs. Class Methods

《2011斯坦福 iOS 应用开发第三课》

注意 dropBomb:at:from: 这个方法有一个对象参数 Bomb *,还有一个参数 CGPoint 没有 *,所以这个指针要被传入栈中,而 Bomb * 在堆上,几乎所有的 objc 都在堆上。

实例方法以 - 开头,它的对象是普通的实例对象。
类方法以 + 开头,它的对象是类而不是实例,通常用来创建对象或者工具方法。

方法表达式

《2011斯坦福 iOS 应用开发第三课》

二者的调用方法相似但不一样,都是以 [ 开头。但实例方法中后面跟的是一个实例指针,然后再发送消息。但类方法在 [ 后面是一个类。有两种方法表示这里的类,简单点就是直接写类名,另一个就是发送一个叫做 class 的消息给实例,这个特殊方法会返回这个实例的类,然后就可以给这个类发消息了。

在实例方法里,根据继承原理发消息给 self 或者 super 其实都是发给 self。
在类方法里,self 是其他类的类方法,在类方法中给 self 发消息只能发类方法。因为 self 不是实例,只是个类,super 也一样。

Q:什么时候用到实例方法呢?
A:几乎一直在用。

Q;什么时候用到类方法呢?
A:当要创建一个实例时,或者获取一个共享的实例,或者获取关于类的一些公共信息时。

Q:类可以具有 property 或者实例变量吗?
A:不能。类不是实例,所以没法保存任何东西。

Instantiation

获取对象的最好方法是要求其他对象给你一个。

NSString’s - (NSString *)stringByAppendingString:(NSString *)otherString;  
NSString’s & NSArray’s - (id)mutableCopy;  
NSArray’s - (NSString *)componentsJoinedByString:(NSString *)separator;

前几课用过 stringByAppendingStringNSStringNSArray 有个 mutableCopy 方法会返回一个可变的对象。还有更复杂的 componentsJoinedByString 方法,输入一个分隔符比如逗号或空格得到由数组元素连接成的字符串。

不是所有返回的对象都是新建的,如果对象已经存在就返回它的指针

NSArray’s - (id)lastObject;
NSArray’s - (id)objectAtIndex:(int)index;

比如 lastObject 在数组中获取最后一个元素,它不会新建一个元素,只会返回一个指向最后元素的指针。所有数组中的元素都是 strong 指针,所以除非数组释放或者移除该元素,否则它一直都是被 strong 指针所指向。所有类似数组、字典的类都是这样的。

获取对象的主要方法还是使用类方法

NSString’s + (id)stringWithFormat:(NSString *)format, ...  
UIButton’s + (id)buttonWithType:(UIButtonType)buttonType;  
NSMutableArray’s + (id)arrayWithCapacity:(int)count;  
NSArray’s + (id)arrayWithObject:(id)anObject;

若果你要用代码生成一个按键,你可以发消息给 UIButton 类新建一个按键。所以绝大多数对象都是通过其他对象或者类来获取的。

从头分配和初始化一个对象

NSMutableArray *stack = [[NSMutableArray alloc] init];  
CalculatorBrain *brain = [[CalculatorBrain alloc] init];

方法是使用 allocinit 的组合。allocNSObject 的类方法,为对象在堆上分配一个足够大的空间,分配的对象初始值是 nil 或者0。很多对象只分配为 nil 还不够,还需要初始化。不管对象是否需要初始化,我们总是要在 alloc 外面加上 init 或者 initWith。所以永远都要这么写,决不能只是 alloc 甚至也不能下一行再 init,一定要加在后面。

Alloc and init

allocNSObject 完成,而且绝大多数的类有无参的 init 方法。官方文档不允许你的类没有 init 初始方法,所以你必须设计能够被 allocinit 的类。但是你可以有其他任意个 init 方法。

更复杂的 init 方法

- (id)initWithFrame:(CGRect)aRect; // UIView的初始化代码  
UIView *myView = [[UIView alloc] initWithFrame:thePerfectFrame];

UIView 它有一个 init 方法叫做 initWithFrame,这个方法允许初始化为一个矩形,还可以只用 init ,这会初始化大小和坐标都为(0,0),但可以稍后再设置坐标大小。所以它有两种初始化,initWithFrame 和普通的 init

带有不同参数的各种初始化示例

- (id)initWithCharacters:(const unichar *)characters length:(int)length;  
- (id)initWithFormat:(NSString *)format, ...;  
- (id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding;

NSString 拥有超过十几种初始化,stringWithFormat 等同于 [self alloc]initWithFormat,而 initWithData 可以把各种编码的数据转换成字符串。

类必须为子类指定一个初始化

所有的类必须为子类提供一个初始化方法,方便子类初始化的时候先初始化父类。因为你创建子类的时候会调用子类的初始化,当你这么做的时候需要用这个 init 方法先初始化父类。知道这个指定的 init 方法很重要,比如 UIView 的初始化方法是 initWithFrame,所以如果我继承了 UIView,那么我需要在初始化里写 [super initWithFrame],否则父类就没法初始化了。不要调用其他 init 方法,只要指定的那个(可以通过查看文档找到父类指定的 init 方法),然后在子类的初始化里调用这个方法。

静态类型的初始化

由于继承原因,init 方法应该是 id 返回类型(不是静态类型)。
但是,如果你有一个本地变量在通过初始化赋值,那么就应该是固定类型的。
例如,MyObject *obj = [[MyObject alloc] init]; 这里的 obj,我没写 id obj 即使 init 返回的是 id,而是写成写成 MyObject,这样编译器就会确保不会发送错误内容。

Q:为什么要有一个指定的初始化方法?
A:如果仔细想一下,这样可以避免变成一个循环。我有一个自己的 init (A) 调用父类的 init (B),然后父类的 init (B) 又调用自己其他某个 init (C),因为 B 与 C 之间想要共享代码。而那个 init (C) 又去调用自己的 init (D),init (D) 可能已经被你重写,这样就会导致 D 又去调用 B。也就是 A→B→C→D→B→C...,这就无限循环了。所以通过指定 init,所有你自己的 init 就会请求指定的 init,然后一直向上请求父类指定的 init,然后一直这样向上。

新建自定义的初始化方法

@implementation MyObject
- (id)init
{
    self = [super init]; // 使用父类指定的初始化  
    if (self) {
        // 在这里初始化子类
    }
return self;
}
@end

初始化是非常普遍的做法,但在 objc 中不常用。因为有了 property,所以可以延迟实例化,这样一来就不用初始化了。

还记得计算器程序的 Model 吗,我们的 viewController 就没有初始化。如果你确实需要,比如初始化一些状态,上面的代码就是演示。这是 MyObject 指定的初始化 init,这里有行很特别的代码 self = [super init]。某些人可能看到 self = 就崩溃了,觉得 self 不能被赋值。在 objc 中 self 只是本地指针,但这是唯一可以对它赋值的情况。对 self 赋值这是一种协议机制,确保 super 的初始化在我们的之前,如果 super 没有初始化就返回 nil。所以如果因为某些原因没法初始化,比如依赖的数据库挂了,就允许返回 nil。所以在子类里要检查父类是否返回 nil。这里我的 init 调用了 super 指定的初始化,检查它是否返回 nil

示例:CalculatorBrain 子类的快捷初始化

@implementation CalculatorBrain
- (id)initWithValidOperations:(NSArray *)anArray
{
    self = [self init];
    self.validOperations = anArray; // will do nothing if `self == nil`
    return self;
}
@end

这里我让我的 CalculatorBrain 只能被合法的运算符初始化。注意这个 init 里面我没有调用 super 的指定初始化,我在自己的快捷初始化里调用了自己的指定初始化。这就是所有其他的初始化都会调用指定的初始化,然后再向上调用 super 的指定初始化。

Dynamic Binding

指在运行时根据操作对象的数据类型的不同来选择合适的过程(方法)。它可以提高程序的灵活性。它是面向对象的必要条件之一。在Ruby中变量是没有类型的,因此必然可以进行动态绑定。

所有对象都分配在堆中,因此你需要使用一个指针

NSString *s = ...; // 静态类型  
id obj = s; // 不是静态类型,但完全合法 
Never use “id *” (that would mean “a pointer to a pointer to an object”).
  • 动态绑定,这是 objc 特有的,不是所有其他语言都有。
  • 静态类型和 id,它们在 runtime 时没有什么区别。唯一区别是编译器能否辅助发现 bug。
  • 绝不要使用 id *,指针的指针是没有意义的。

执行消息发送的决定代码发生在 runtime

你可以想一下,[obj* method] 在编译器里是个 C 函数,我们叫它 OBJC message sent。OBJC message sent 第一个参数是一个对象指针,第二个参数是一个基本信息描述符,然后剩下的参数就跟你方法中的参数一致。想象一下这个过程,在运行过程中每次发消息都会调用这个函数。这个函数做的第一件事是查看这是个什么类,然后找到类的方法。如果这个类和他的父类都没有这个方法的实现,程序就崩溃了。所以这个过程中,无所谓类型是 id 或者 NSString 什么的,这个函数会自己去代码中查找,所以给出具体类型其实只是为了方便编译器查找 bug。

映射一个指针是合法的,有时甚至鼓励这么做

id obj = ...;
NSString *s = (NSString *)obj; // 这么做很危险,最好你明白自己在做什么

但是通常我们只在使用内省的时候才这么做。

Foundation Framework

NSObject

- (NSString *)description is a useful method to override (it’s %@ in NSLog()).  
- (id)copy; // not all objects implement mechanism (raises exception if not)  
- (id)mutableCopy; // not all objects implement mechanism (raises exception if not)

foundation 是个 framework,含有很多重要对象。

NSObject 几乎是任何 iOS 对象的基础,它没有过多内容。它有 +alloc 类方法,还有那些内省方法,还有 description 来创建一个对象的 description,这样我们可以使用 NSLog 输出到控制台。通常我们会重载 description 来显示自己的内容。
- (id)copy;- (id)mutableCopy;。有些人会很兴奋,以为可以直接复制对象了。但实际上你不可以,只有实现了特殊协议的对象才可以。幸运的是 foundation 中大多数对象都可以,如果你要知道一个对象能否 copy,就去查它的文档。我们还没讲协议,迟些会讲到。但是如果你看到一个对象有实现协议,那就可以 copy。

Q:这是否是深度复制?比如复制一个数组会不会复制数组里的东西。
A:不是。这是浅复制,某些对象是深度的,但这个是浅的。

NSString

  • NSString 是使用 Unicode 编码的国际标准(任何语言)字符串。
  • 在整个 objc 中替代了 C 语言的 char *
  • 一个 NSString 实例是不可改变的,如果需要可变的,待会儿会讲它的可变版本。或者让它做你要求的事,然后它会返回一个新的字符串给你,比如之前那个 append。
  • NSString 通常的用法是,给它发一个消息,然后返回一个新的字符串。例如:
self.display.text = [self.display.text stringByAppendingString:digit];  
self.display.text=[NSStringstringWithFormat:@“%g”,brain.operand]; // 类方法
  • NSString 有大量非常实用的方法,因为它们能做非常多的事情,很多你要写好几行代码的事情它一行就搞定了。数组也是一样,array 和 string 一定要非常熟悉,这能帮你省下不少代码。

NSMutableString

NSMutableString *ms = [[NSMutableString alloc] initWithString:@“0.”];  
NSMutableString *ms = [NSMutableString stringWithString:@“0.”]; // 继承自 NSString  
[ms appendString:digit];

可变版本的字符串叫做 NSMutableString,它的部分功能用 NSString 也能实现,所以我们可以利用 append 来实现可变。

Q:为什么我们不鼓励用 NSMutableString?它这么好用。
A:记住,如果你通过 append 新建一个字符串,过程和用 NSMutableString 是一样的。此外,不可变的 NSString 已经被优化到了极致。所以我们极少用到 NSMutableString。通常我就是想用不可变的 NSString 发消息来获得一个新的字符串。

NSNumber

NSNumber *num = [NSNumber numberWithInt:36];  
float f = [num floatValue]; // 返回 36 作为 float (i.e. will convert types)

NSNumber,原始类型的包装。它还提供了转换功能,比如你包装了 int 但需要一个 float,它会自动转换好给你,反之亦然。

NSValue

CGPoint point = CGPointMake(25.0, 15.0); // CGPoint is a C struct  
NSValue *pointObject = [NSValue valueWithCGPoint:point];

NSValue,为结构体之类的数据提供包装,比如这里的 CGPointNSValue 就是为这些数值提供包装的。比如可以用它把一些坐标点装进数组。

NSData

NSData 是用来装无结构数据的,二进制码之类。这些数据的意义依赖于传递它们的 API,可能是字符串、图片等。

NSDate

NSDate,用来找到当前时间或者存储过去或未来的时间和日期。此外还有各种日期时间的格式化。例如,NSCalendarNSDateFormatterNSDateComponents

NSArray

+ (id)arrayWithObjects:(id)firstObject, ...; //  nil 为结束参数
NSArray *primaryColors = [NSArray arrayWithObjects:@“red”, @“yellow”, @“blue”, nil];
+ (id)arrayWithObject:(id)soleObjectInTheArray; // 比你想的更有用

- (int)count;
- (id)objectAtIndex:(int)index;
- (id)lastObject; // 如果数组没有对象就返回 nil

- (NSArray *)sortedArrayUsingSelector:(SEL)aSelector;
- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)selectorArgument;
- (NSString *)componentsJoinedByString:(NSString *)separator;

- (BOOL)containsObject:(id)anObject; // could be slow, think about NSOrderedSet

NSArray,可能是 NSString 之后第二受欢迎的对象。它是一个有序的对象集合,根据你增加和移除操作会变大和缩小。和其他语言里的数组概念是一样。但是它是不可变的,有人会问你刚刚还说它会缩小,我的意思是创建的时候它会自动根据内容调整大小,之后就不变了。

Q:想想你在不同对象之间通过 API 传递对象,你传了一个可变的过去,比如可变数组。过一会儿你在这个数组里加了些东西,那么之前传过去的会被修改吗?
A:这种情况很难处理,他需要在传进来的时候建一个可变数组的快照。这是个好理由让大家都有可变数组,但只有一个是真的。事实上你会看到大多数的 NSString API 是@property(copy),这个还没讲过,但它就像是一个 strong 的可变副本。所以不可变才是王道,不管是字符串还是数组。

Q:那么当你有了个不可变数组,怎么往里面放东西呢?
A:在创建的时候放。有不少的类方法可以创建一个数组,这个 arrayWithObjects: 传递一串对象进来就生成了一个不可变数组。也有 arrayWithObject: 创建单个对象的数组,你会问为什么?你可能有 API 能够接受多个参数,但通常只接受一个。你只要一个数组,所以这个就用来只传递一个。

NSArray 的主要方法有 countobjectAtIndex:count 表示数组的个数。objectAtIndex: 表示指定位置的内容。

Q:可以往数组里放 nil 吗?
A:不行。有一个对象叫做 NSNull,它可以放在数组里,但是 nil 不行。什么是 NSNull ?它仅仅是个空白的占位符。

对象的容器可能会运行很慢,特别是当数组很大的时候。因为它不知道怎么在里面查找,这时候就会用到 NSOrderedSetNSSet

NSMutableArray

+ (id)arrayWithCapacity:(int)initialSpace; // initialSpace is a performance hint only
+ (id)array;

- (void)addObject:(id)anObject; // at the end of the array
- (void)insertObject:(id)anObject atIndex:(int)index;
- (void)removeObjectAtIndex:(int)index;
- (void)removeLastObject;

- (id)copy; // returns an NSArray (i.e. immutable copy); and NSArray implements mutableCopy

NSMutableArray 是个可变的数组,你可以用 NSArray 的方法创建,也可以用 array 建个空的数组。不要被 arrayWithCapacity: 迷惑了,它还是新建个空的数组。Capacity 只是个提示。

NSMutableArray 的主要方法是 add 或者 insert 还有 remove。别忘了 copy 之于 NSMutableArray 会返回不可变的数组,但是 NSArray 的可变 copy 会返回 NSMutableArray。还有别忘了 NSMutableArray 继承了 NSArray 的全部方法。

NSDictionary

字典是一个不可变的哈希表,通过关键字来来查找对象以获得一个值。两个重要的方法是 count,关键字对象数目,和 objectForKey:,通过 key 查找对象。keys 会发送给哈希表,并用 isEqual: 来寻找配对的内容,如果是指针就匹配指针地址。大多数时候你会用 NSString 来作为 key,然后匹配字符。

NSMutableDictionary 用 set 来添加,remove 来移除。如果你有另一个 NSMutableDictionary,还可以整个合并过去。

NSSet

+ (id)setWithObjects:(id)firstObject, ...;
+ (id)setWithArray:(NSArray *)anArray;
- (int)count;
- (BOOL)containsObject:(id)anObject;
- (id)anyObject;
- (void)makeObjectsPerformSelector:(SEL)aSelector;

NSSet 是一个不可变的,无序的唯一的对象集合。唯一的表示你不能放两个一样的对象,不是说同一个对象指针,而是内容一样的两个对象。这是当你想要检查成员时候用的,比如我是否已经拥有这个对象了。

Q:为什么是不可变的?
A:比如你有个颜色的 set,那就可以查询一个颜色是不是在 set 里了。

countcontainsObject: 是两个关键方法。anyObject 表示随意对象,当任意对象都可以做某个操作时非常有效。

NSMutableSet

- (void)addObject:(id)anObject; // does nothing if object that isEqual:anObject is already in 
- (void)removeObject:(id)anObject;
- (void)unionSet:(NSSet *)otherSet;
- (void)minusSet:(NSSet *)otherSet;
- (void)intersectSet:(NSSet *)otherSet;

可变的 set 可以添加和删除元素,不能在指定位置插入因为它是无序的。

NSOrderedSet

- (int)indexOfObject:(id)anObject;
- (id)objectAtIndex:(int)anIndex;
- (id)firstObject; and - (id)lastObject; - (NSArray *)array;
- (NSSet *)set;

NSOrderedSet 不是 NSSet 的子类,但是是 NSArray 和 NSSet 的混合物。它能够在指定位置插入对象,但仍然不能重复放入对象。NSOrderedSet 执行成员检查时比 NSSet 和 NSArray 要快得多。

NSMutableOrderedSet

- (void)insertObject:(id)anObject atIndex:(int)anIndex;
- (void)removeObject:(id)anObject;
- (void)setObject:(id)anObject atIndex:(int)anIndex;

NSMutableOrderedSet 是可变的,其他都和 NSOrderedSet 一样。

Enumeration

以一个高效的方式遍历集合的成员

例如:NSArray of NSString objects
NSArray *myArray = ...;
for (NSString *string in myArray) { // 编译器不知道 myArray 的内容
    double value = [string doubleValue]; // 如果字符串不是 NSString 程序会崩溃 }

例如:NSSet of id (NSArray of id 也很容易) 
NSSet *mySet = ...;
for (id obj in mySet) {
    // do something with obj, but make sure you don’t send it a message it does not respond to 
    if ([obj isKindOfClass:[NSString class]]) {
        // 发送 NSString 消息给对象 with impunity }
}

和其他语言一样,你可以使用 for-in。比如在 myArray 里枚举出 string。如果 myArray 里没有 string,运行时会崩溃且不会有警告。因为编译器不会知道 myArray 的内容。如果你的数组可能含有其他对象,就可以想这样写 id。注意,我在代码里做了内省,在发送 string 消息前检测其是否是 string。

遍历一个字典的关键字或值

例如:
   NSDictionary *myDictionary = ...;
   for (id key in myDictionary) {
        // do something with key here
        id value = [myDictionary objectForKey:key];
    // do something with value here
    }

字典的枚举是通过关键字。所以在循环里可以对 key 的值做一些操作,但不是修改。说到修改,在 for-in 里面不要修改可变数组,比如添加成员之类的都不要做。

Property List

  • Property List 表示任何以下六个类型的组合,NSArray、NSDictionary、NSNumber、NSString、NSDate、NSData。
  • NSString 的 NSArray 是一个 Property List。
  • 只要 NSArray 的成员是 Property List 那么 NSArray 的 NSArray 也同样是一个 Property List。
  • 只要 NSDictionary 的 NSArray 所有的 key 为 NSString 并且所有的 value 为 NSNumber 时,NSDictionary 是一个 Property List。
  • CalculatorBrain 的数组就不是 Property List,只能是上面六种类型。

Q:为什么要有 Property List?
A:因为 iOS 有的 API 参数 id,它的文档伤规定这个 id 必须是 Property List。这就意味着必须是这六个中的一个。

例如:NSUserDefaults 是个轻量级的 Property List 数据库。轻量级就是小型的,内容不多的数组字典等,不是大型的图片、数据之类的。它基于 NSDictionary,存在于应用程序启动时的空隙。

[[NSUserDefaults standardUserDefaults] setArray:rvArray forKey:@“RecentlyViewed”]; Sample methods:
- (void)setDouble:(double)aDouble forKey:(NSString *)key;
- (NSInteger)integerForKey:(NSString *)key; // NSInteger is a typedef to 32 or 64 bit int
- (void)setObject:(id)obj forKey:(NSString *)key; // obj must be a Property List
- (NSArray *)arrayForKey:(NSString *)key; // will return nil if value for key is not NSArray Always remember to write the defaults out after each batch of changes!
[[NSUserDefaults standardUserDefaults] synchronize];

使用 standardUserDefault 方法进行读写,这和字典很像,但是有更具体的操作。

Q:如果你使用了 setObject:forkey:,那么这里的 obj 是什么呢?
A:是 Property List。

arrayForKey:,如果 key 里面保存的不是 array 就返回 nil。最后一点,无论何时你用了 NSUserDefaults,你必须和磁盘进行同步。这是为了安全起见,只要写 [[NSUserDefaults standardUserDefaults] synchronize]; 就行。任何操作之后都要同步,否则数据不会保存。同步不是没有开销,只是非常少而已。所以每次都要同步,而不是积累一大堆东西之后再做。

    原文作者:jkyin
    原文地址: https://segmentfault.com/a/1190000000360034
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞