Object-C基础(10)——异常处理、反射、动态调用方法与内存回收

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,

        但实际上已经没有指针变量指向它们,系统又不回收它,这就形成了内存泄露。

        只要注意:不要形成强引用循环。

 

 

           

你可能感兴趣的:(Object-C)