Object-C的异常处理
Object-C的异常处理,通常不会作为常规的编程手段,很多时候只是作为程序调试的手段。
语法
@try
{
// 执行代码
}
@catch(异常类1 形参名1) ----@catch块可以有很0~N个
{
// 异常处理块
}
@catch(异常类2 形参名2)
{
// 异常处理块
}
...
@finally ----@finally块可以有很0~1个
{
// 总要执行的代码
}
注意:@try不能独立存在。
执行流程为:如果在执行try块的某行代码时,出现了一个异常,那么try块中后面的代码将不会得到执行。
接着程序就会根据该异常去寻找对应的catch块: 异常对象 isKindOfClass: 异常类,如果找到就执行该异常处理块。
如果找不到,程序就直接终止。
如果在执行try块的某行代码时,没有出现了一个异常,整个try块都会执行完成,但catch块就不会执行了。
无论程序是否出现异常,程序总会去执行finally块。
总结:如果不出现异常:try块所有代码都会执行,catch块不会执行。finally会执行。
如果出现异常:try块异常代码之后的代码都不会执行,只有一个catch块会执行。finally会执行。
NSException是Object-C提供的所有异常的父类。
访问异常信息
当程序捕捉到异常时,异常对象会传给catch块的异常形参,因此程序即可根据异常形参来获取异常信息:
- name:获取异常的名称。
- reason:获取引发异常的原因。
- userInfo:异常所携带一些额外信息。这是一个NSDictionary类型的属性。通常不存在,表现为nil。
#import "FKApple.h" int main(int argc , char* argv[]) { @autoreleasepool { @try { FKApple* app = [[FKApple alloc] init]; NSLog(@"~~^^^~~"); [app taste]; NSLog(@"====~~~===="); } @catch(NSException* ex) { NSLog(@"程序捕捉到异常"); NSLog(@"异常名:%@" , ex.name); NSLog(@"异常原因:%@" , ex.reason); NSLog(@"异常userInfo:%@" , ex.userInfo); } @finally { NSLog(@"一定执行的代码块"); } } }
自定义异常与抛出异常
自定义异常:定义一个类,该类继承NSException———— NSException的子类,就是一个异常类。
实际上,很少自定义异常类。
抛出异常:当程序出现某种与业务规则不符合的情况时,即可抛出异常来阻止程序继续向下运行。
@throw 异常对象;
使用@throw时,必须创建一个异常对象。
反射
KVC(setValue: forKey:valueForKey:)(setValue: forKeyPath:, valueForKeyPath:)
动态的编程方式。
反射,是一种更通用的动态编程方式, KVC只能动态调用setter、getter方法。反射可以动态调用任意方法。
获取类
1. NSClassFromString(字符串)函数:根据字符串来获取对应的类。
2. 调用指定类的class方法来获取类。
3. 调用对象从class方法来获取类。
@class()不是用于获取类的,而是用于声明指定的类是存在的。
@class(类名),用法是存在的。它的作用是声明该类是存在的,在引用循环时候就需要使用它。
——原因是,当两个类相互引用时,程序不能在两个类的头文件部分互相import,这样会出错。此时就需要@class
一旦获取到Class之后,程序就可通过Class的alloc、init来创建对象了。
#import "FKUser.h" int main(int argc , char* argv[]) { @autoreleasepool { // 假如程序需要同时创建NSNumber、NSDate、FKUser这些类的实例 NSArray* arr = @[@"NSNumber" , @"NSDate" , @"FKUser" , @"FKApple"]; for(int i = 0 ; i < arr.count ; i++) { // 使用NSClassFromString()函数来获取类。 Class clazz = NSClassFromString( arr[i] ); NSLog(@"%@" , [[clazz alloc] init] );} <span> </span> } }
检查继承关系
判断一个对象是否为某个类的实例,是否为某个类或其子类的实例,或者是否遵守了某个协议。
[对象 isMemberOfClass:类] 检查该对象是否为指定类的实例。
[对象 isKindOfClass:类] 检查该对象是否为指定类、或其子类的实例。
[对象conformsToProtocol:协议] 检查该对象是否遵守了指定协议。
这3个方法的返回值都是BOOL值。
获取协议
1. NSProtocolFromString(字符串)函数:根据字符串来获取对应的协议。
2. @protocol(协议名)
相比之下,第二种方式来获取协议更加安全一些,如果你把协议名写错,编译器会立即报错。
动态调用方法
如果要动态地调用getter、setter方法,可通过KVC来实现。
如果要动态地调用普通方法,则可通过此处的反射来实现。
检查某个对象是否具有指定方法:
[对象respondsToSelector: (SEL)selector]:检查该对象是否具有指定的方法。
Object-C中,很喜欢把调用方法,称为“发送消息”。
比如 abc对象调用xyz方法; 也可称为:向abc对象发生xyz消息。
如何获取SEL(方法)
1. NSSelectorFromString(字符串)函数:根据字符串来获取对应的协议。
2. @selector(方法名) —— 方法名中,如有形参,不要忘了英文冒号。
相比之下,第二种方式来获取方法更加安全一些,如果你把方法名写错,编译器会立即报错。
动态调用方法
-使用NSObject本身提供的: -(void)performSelector:(SEL)aSelector withObject:(id)anArgument
- 使用objc_msgSend(调用者, selector, 参数...)函数来动态调用方法。
确定Object-C的方法,需要方法名、冒号、形参标签。
对于第一种机制,通常只能传入一个参数;如果需要动态调用的方法要传入多个参数,就需要使用第二种机制。
——通过动态调用方法可以看出,Object-C其实并不能真正隐藏某个方法,即使是隐藏方法,同样可通过动态机制来进行调用。
将方法分离成函数,并赋值给函数指针变量
方法的本质,其实依然是函数,因此通过函数指针变量即可把方法分离出来。
(1)定义一个函数指针类型的变量。
(2)将指定方法实现赋值给函数指针类型
Object-C对象的内存回收
理解对象的内存回收
当Object-C对象被创建时, alloc(负责分配内存)--> init (负责对内存进行初始化)。
接下来该对象就存在系统内存中。
Object-C何时回收该对象所占的内存?
当该对象的RC(引用计数)为0时,系统会调用该对象dealloc方法,然后销毁该对象,并回收该对象的内存。
所谓引用计数:相当于OS系统会自动为每个对象都添加int型的值,该值可以为0~任意整数。
如何改变对象的RC呢?
- MRC:手动引用计数。由程序员自己来控制各个对象的RC的值。
- ARC: 自动引用计数。由系统来控制各个对象的RC的值,在这种方式下,程序员不允许该变对象的RC。
推荐使用ARC,ARC会保证每个对象有几个引用变量引用它,该对象的RC就为几。
在MRC模式下,有如下几个方法来控制对象的RC
- retain :将RC加1。
- release :将RC减1。一个对象release之后,并不一定会被回收(只有该对象的RC原来为1,被release之后才会被回收)
- autorelease: 不改变RC值。将对象添加到自动释放池中。
自动释放池相当于一个数组,当自动释放池自己被销毁之前,它会把池中所有对象都release一次。
对于处于自动释放池中对象,当池被销毁时,该对象也不一定被回收;(只有该对象的RC原来为1,池回收时它才会被回收)
- retainCount:只读属性,返回该对象的RC。
自动释放池:NSAutoreleasePool,该对象同样有RC,也是RC为0时,该对象被系统回收。
ARC:
不管ARC,还是MRC,系统回收对象的标准是完全相同的:只要对象的RC为0,系统就会回收它。
MRC和ARC区别何在:MRC是由于程序员通过代码来管理对象的RC;ARC是系统负责管理对象的RC。
ARC唯一的问题就是要避免强引用循环,强引用循环会导致两个对象的RC都为1,
但实际上已经没有指针变量指向它们,系统又不回收它,这就形成了内存泄露。
只要注意:不要形成强引用循环。