iOS开发之OC高级笔记

OC-33章:init

alloc负责分配对象空间,init负责初始化对象。

1
NSMutableArray *things = [[NSMutableArray alloc] init]

init方法可以被重写覆盖,像description方法一样。

1
2
3
4
5
6
7
- (instancetype) init {
self = [super init]; //初始化父类
if (self) {
//初始化自己的代码
}
return self;
}

instancetype 关键字会告诉编译器返回什么类型的对象。自己编写的或是覆盖的任何初始化方法都应该返回 instancetype 类型的值。如果返回具体的类类型,会在继承的时候导致强制转换,所以使用 instancetype 确保可以安全的继承初始化方法。当然也可以使用 id 类型,但是更推荐 instancetype。

对于实现自己的初始化方法的注意事项:

  • 出于优化考虑,init 方法会释放已经分配了内存的对象,然后创建另一个对象返回之。苹果要求:将父类的 init 方法所返回的对象赋给 self。
  • init 方法在执行过程中发生了错误,所以会释放对象并返回 nil。苹果建议:检查父类的 init 方法的返回值,确定不是 nil 并且有效。否则,就没有必要执行自定义的初始化代码。

因为 init 没有实参,如果希望向 init 方法传入实参,可以定义其他初始化方法,并在 init 方法中调用该方法并传入默认参数。多个初始化方法也可以用这种方法。

在 init 方法中既可以通过直接赋值来设置实例变量,也可以使用存取方法来完成这项任务。

编写初始化方法应该遵循以下规则:

  • 其他的初始化方法都应该(直接地或间接地)调用指定初始化方法。
  • 指定初始化方法应该先调用父类的指定初始化方法,然后再编写自己的初始化代码。
  • 如果某个类的指定初始化方法和父类不同,就必须覆盖父类的指定初始化方法,并调用新的指定初始化方法。
  • 如果某个类有多个初始化方法,就应该在相应头文件中明确地注明哪个方法是指定初始化方法。

禁用初始化方法

1
2
3
4
- (instancetype)init {
[NSException raise:@"BNRWallSafeInitialization"
format:@"Use initWithSecretCode: not init"]; //抛出异常
}

OC-34章:再谈属性

属性的特性:

  • 存取类型
    • 属性声明为 readwrite 代表程序应该自动创建存方法和取方法
    • 属性声明为 readonly 代表程序无须创建存方法
  • 生命周期类型

    • assign: 是默认也是最简单的,存方法会将传入的值直接赋给实例变量。
    • strong:凡是指向对象的实例变量,通常应该使用 strong 特性。
    • weak:要求不保留传入的对象,如果该对象被释放,相应的实例变量会被自动赋为 nil,这么做可以避免产生悬空指针
    • unsafe_unretained:和weak特性有点类似,要求不保留传入的对象,然而,如果该对象被释放,相应的实例变量不会被自动赋为 nil。
    • copy:要求拷贝传入的对象,并将新的对象赋给实例变量。大多数类都有两个版本,一个可修改的,另一个不可修改的。copy 方法返回不可修改对象,mutableCopy 返回可修改对象。

      OC 没有为属性提供 mutableCopy 这样的特性,如果某个对象需要复制传入的对象,并且要求新对象是可修改的,就必须自己编写代码实现(向传入的对象发送mutableCopy消息),而不能依赖属性机制。

  • atomic 和 nonatomic:默认atomic,但建议使用 nonatomic

如果声明一个属性,手动实现存取方法,编译器就不会合成实例变量。但如果你需要实例变量,就必须自己创建,创建的方法是在类的实现文件中的implementation后添加 @synthesize 指令。

1
2
@synthesize mushroom = _mushroom; 
//然后就可以实现存取方法并使用实例变量了。

声明一个只读属性时,编译器会自动合成一个取方法和一个实例变量,此时如果手动实现取方法,编译器也不会合成实例变量,而需要手动合成。


2016-03-28 12:00 更新


OC-35章:KVC

KVC(key-value coding)能够让程序通过名称直接存取属性。也可以用KVC读取实例变量。setValue:forKey方法会查找相应的存方法,如果没有,就会直接为实例变量赋值,valueForKey:方法会查找相应取方法,如果没有,就会直接返回相应的实例变量。

  • KVC的方法在NSObject类中定义。
  • KVC是一个违背了对象封装的例外。
  • KVC只对对象有效。所以应该尽量用相应对象来声明属性。

Key Path:大多数程序到最后都有一个相对复杂的对象表。如下图,这种通过层层指针指向的对象虽然可以使用KVC来遍历,但是使用 key path 要方便很多。有点类似与C++的.号访问对象属性。和KVC的方法名稍有区别,setValue:forKeyPath:valueForKeyPath:
Complex object

以下是用KVC遍历的方法:

1
2
3
4
BNRDepartment *sales = [[BNRDepartment alloc] init];
BNREmployee *sickEmployee = [sales valueForKey:@"manager"];
BNRPerson *personToCall = [sickEmployee valueForKey:@"emergencyContact"];
NSString *numberToDial = [personToCall valueForKey:@"phoneNumber"];

以下是用 key path 的方法:

1
2
BNRDepartment *sales = [[BNRDepartment alloc] init];
NSString *numberToDial = [sales valueForKeyPath:@"manager.emergencyContact.phoneNumber"];

通过 key path 设置属性的值:

1
2
BNRDepartment *sales = [[BNRDepartment alloc] init];
[sales setValue:@"manager.emergencyContact.phoneNumber"];

代码效果同KVC:

1
2
3
4
BNRDepartment *sales = [[BNRDepartment alloc] init];
BNREmploy *sickEmployee = [sales vlaueForKey:@"manager"];
BNRPerson *personToCall = [sickEmployee valueForKey:@"emergencyContact"];
[personToCall setValue:@"555-555-5555" forKey:@"phoneNumber"];

OC-36章:KVO

KVO(key-value observing)是指当指定的对象的属性被修改时,允许对象接收通知的机制。

  • 观察对象的属性时,要指定观察属性的名称。
  • 在KVO中使用context指针可以确认通知的对象是否是本对象,context会随通知一起发送。
  • 如果使用存取方法来设置属性,那么系统会自动通知观察者。如果没有使用存取方法,这时可以通过willChangeValueForKey:didChangeValueKey:方法通知系统某个属性的值即将/已经发生变化。
  • 如果希望某个变量接收到另一个变量改变时的通知,就需要用 keyPathsForValuesAffecting+某个变量名 来设置,示例如下:

    1
    2
    3
    + (NSSet *)keyPathsForValuesAffectingLastTimeString {
    return [NSSet setWithObject:@"lastTime"];
    }

OC-37章:范畴

通过使用范畴(Category),程序员可以为任何已有的类添加方法。
应该使用范畴来给已存在类增加新方法,而不要在范畴中替换已存在的方法;这种情况下应该创建该类的子类。

  1. 创建Category时候要选择类型为 Objective-C Category,选择基于的类(即你想添加方法的系统类)
  2. 系统会自动创建 你选择的基类名+继承的子类类名+后缀的方式创建.h和.m的文件,比如:NSString+YourClass.h,和普通类文件稍有不同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //NSString+YourClass.h
    @interface NSString (YourClass)
    @end

    //NSString+YourClass.m
    @implementation NSString (YourClass)
    //此处实现增加的新方法
    -(void)test {}
    @end
  3. 调用自己增加的新方法

    1
    2
    3
    4
    5
    //首先需要导入头文件
    @import "NSString+YourClass.h"

    //然后在代码中直接基于基类调用
    [NSString test]; //注意此处原来NSString是没有test方法的

–END–

avatar

神无

舍悟离迷,六尘不改。