第五章:面向对象(下)

一、Objective-C的包装类

c语言中包括的基本数据类型(int,short,long,float,double等)都不是对象,OC提供了NSValue,NSNumber来封装C语言的基本类型。

1、他们不是包装类

  • (1)、NSInteger:大致等于long型整数;
  • (2)、NSUInteger:大致等于unsigned long型整型;
  • (3)、CGFloat:在64为平台上大致相当于double;在32为平台上大致相当于float。

定义NSInteger和NSUInteger

#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

2、NSValue和NSNumber(包装类)

NSValue是NSNumber的父类,NSValue是一个通用的包装类,它可用于包装单个short、int、long、float、char、指针、对象id等数据项,然后可以把其加到NSArray、NSSet等集合(这些集合要求它们的元素必须是对象)中。
NSNumber的三类方法:

  • (1)、+numberWithXxx::该类方法直接将特定的值包装成NSNumber;
  • (2)、-initWithXxx::该实例方法需要先创建一个NSNumber对象,再用一个基本类型的值来初始化NSNumber;
  • (3)、-xxxValue:该实例方法返回该NSNumber对象包装的基本类型的值。

例如:

//调用类方法将int类型的值包装成NSNumber对象
NSNumber* num = [NSNumber numberWithInt:20];
//调用类方法将float类型的值包装成NSNumber对象
NSNumber *dou = [NSNumber numberWithDouble:3.4];
//调用类方法将char类型的值包装成NSNumber对象
NSNumber *c = [NSNumber numberWithChar:'J'];
NSLog(@"%d",[num intValue]);
NSLog(@"%g",[dou doubleValue]);
NSLog(@"%c",[c charValue]);
//先创建NSNumber对象,在调用initWithXxx方法执行初始化
NSNumber* ch = [[NSNumber alloc]initWithChar:'j'];
NSLog(@"%@",ch);

二、处理对象

1、打印对象和description方法

description方法是NSObject类的一个实例方法,所有的Objective-C类都是NSObject类的子类,它是一个“自我描述”方法,该方法通常用于事先这样一个功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象具有的状态信息。

例如:

打印会出现
<FKPerson:16进制的首地址>
//创建一个FKPerson的对象,将之赋给p变量
FKPerson* p = [[FKPerson alloc]initWithName:@"孙悟空"];
//打印p指向的FKPerson对象
NSLog(@"%@",[p description]);//<FKPerson: 0x100202170>

2、==和isEqual方法

  • (1)、==:如果两个变量是基本类型的变量,且都是数值型(不一定要求数据严格相同),则只要两个项目相等,使用==判断将返回真。
    对于两个指针类型的变量,他们必须指向同一个对象(也就是两个指针变量保存的内存地址相同)时,判断才会返回真。
int it = 65;
float f1 = 65.0f;
NSLog(@"65和65.0f是否相等?%d",it == f1);//1
char ch = 'A';
NSLog(@"65和'A'是否相等?%d",it == ch);//1
NSString* str1 = [NSString stringWithFormat:@"hello"];
NSString* str2 = [NSString stringWithFormat:@"hello"];
NSLog(@"str1和str2是否相等?%d",str1 == str2);//0
NSLog(@"str1是否isEqual str2?%d",[str1 isEqual:str2]);//1
NSString* s1 = @"hello";
NSString* s2 = @"hello";
NSLog(@"%p--%p",s1,s2);
NSLog(@"s1和s2是否相等?%d",s1 == s2);
//让s3指向新生的对象
NSString* s3 = [NSString stringWithFormat:@"hello"];
NSLog(@"s3的地址:%p",s3);
NSLog(@"s1和s3是否相等?%d",s1 == s3);
注意:上述中的s1和s2是在常量池中取得字符串,故它们指向同一个元素,地址相同,内容也相同,所以相等。运用stringWithFormat:类方法创建的字符串对象是运行时创建出来的,它被保存在运行时内存区域(堆内存),所以他的指针变量保存的地址和s1指针变量保存的地址不同。
  • (2)、isEqual::是NSObject类提供的一个实例方法,要求两个指针变量指向同一个对象才会返回真,和==没有区别。但是需要注意的是:NSString重写了NSObject的isEqual:方法,NSString判断两个字符串的标准是:只要要个字符串所包含的字符序列仙童,通过isEqual:比较返回真;否则返回假。
    正确重写isEqual:方法应该卯足下列条件:

    • ①、自反性:对任意x,[x isEqual:x]一定返回真;
    • ②、对称性:对任意x和y,如果[x isEqual:y]返回真,则[y isEqual:x]也返回真;
    • ③、传递性:对任意x和y,如果[x isEqual:y]返回真,[y isEqual:z]返回真,则[x isEqual:z]一定返回真;
    • ④、一致性:对任意x、y、z,如果对象中用于等价比较的关键信息没有改变,那么无论调用[x isEqual:y]多少次,返回的结果保持一致,要么一定是真,要么一直是假。
    • ⑤、对任何不适nil的x,[x isEqual:nil]一定返回假。
      另外需要指出的是:NSObject默认提供的isEqual:只是比较对象的地址。

      提示:NSString定义了一个isEqualToString:方法,专门用于判断当前字符串与另一个字符串的字符序列是否相等。
      

三、类别与扩展

类簇:定义一个父类,并以该父类派生多个子类,其他程序使用这些类时,总是面向父类编程,当程序调用父类的初始化方法、静态方法来返回对象时,实际上返回的是子类的实例。

1、类别(category)只能为类添加方法,不能添加成员变量

Objective-C的动态特征允许使用类别为现有的类添加新方法,并且不需要创建子类,不需要访问原有类的源代码。
类别同样接口和实现部分组成,接口部分的语法如下:

@interface已有类 (类别名)
//方法定义
。。。
@end

定义类别和定义类的区别:

  • ①、定义类时使用的类名必须是该项目中没有的类,而定义类别时使- 用的类名必须是已有的类;
  • ②、定义类别时必须使用圆括号来包含别名;
  • ③、类别中通常只定义方法。

例如:

为NSNumber增加一个类别,类别接口部分如下

#import <Foundation/Foundation.h>
@interface NSNumber (FK)
//在类别中定义四个方法
- (NSNumber *)add:(double)num2;
- (NSNumber *)substract:(double)num2;
- (NSNumber *)multiply:(double)num2;
- (NSNumber *)divide:(double)num2;
@end

类别的实现部分

#import "NSNumber+FK.h"
//类别提供实现部分
@implementation NSNumber (FK)
- (NSNumber *)add:(double)num2{
    return [NSNumber numberWithDouble:([self doubleValue] + num2)];
}
- (NSNumber *)substract:(double)num2{
    return [NSNumber numberWithDouble:([self doubleValue] - num2)];
}
- (NSNumber *)multiply:(double)num2{
    return [NSNumber numberWithDouble:([self doubleValue]*num2)];
}
- (NSNumber *)divide:(double)num2{
    return [NSNumber numberWithDouble:([self doubleValue]/num2)];
}
@end

main函数的调用部分

#import "NSNumber+FK.h"
NSNumber* myNum = [NSNumber numberWithInt:3];
//测试add:方法
NSNumber* add = [myNum add:2.4];
NSLog(@"add = %@",add);

注意:

  • ①、通过类别为指定类添加新方法之后,这个新方法不仅会影响NSNumber类,还会影响NSNumber类的所有子类,每个子类都会获取类别扩展的方法;
  • ②、可根据需要一个类定义多个类别,不同的类别都可对原有的类增加方法定义;
用法
①、利用类别对类进行模块化设计;
②、利用类别来调用私有方法;
③、使用类别来实现非正式协议。

2、利用类别对类进行模块化设计

通过类别将类方法按照模块分到不同的.m文件中,从而提高项目后期的可维护性。

3、使用类别来调用私有方法

没有在接口部分定义而是在类实现部分定义的方法相当于是私有方法。
怎么调用那些类似的私有方法:

  • (1)、但是OC没有私有方法,可以使用NSObject的performSelector:方法来执行动态调用,完全可以调用那些私有方法。
  • (2)、通过类别向前引向,从而实现私有方法的调用。

例如:

FKItem的接口口部分

#import <Foundation/Foundation.h>
@interface FKItem : NSObject
@property (nonatomic ,assign)double price;
- (void)info;
@end

FKItem的实现部分

#import "FKItem.h"
//FKItem提供实现部分
@implementation FKItem
@synthesize price;
//实现接口部分定义的方法
- (void)info{
    NSLog(@"这是一个普通的方法");
}
//额外新增的方法
- (double)calDiscount:(double)discount{
    NSLog(@"我运行了");
    return self.price*discount;
}
@end

FKItem添加类别

//为FKItem增加一个类别
@interface FKItem (FK)
//在类别的前面声明calDiscount:方法,告诉编译器,FKItem类中可以包含calDiscount方法
- (double)calDiscount:(double)discount;
@end

main函数的调用部分

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKItem* item = [[FKItem alloc]init];
        item.price = 90;
        [item info];
        NSLog(@"物品嘉泽的价格为:%g",[item calDiscount:0.75]);
    }
    return 0;
}

4、扩展(extension):即可以添加方法 也可以添加成员变量

扩展与类别类似,扩展相当于匿名类别,用于临时对某个类的接口进行扩展,类实现部分同时实现类接口部分定义的方法方法和扩展部分定义的方法,定义扩展的语法格式如下:

@interface 已有类()
{
     实例变量
}
//定义方法。。。
@end

例如:

在FKCar中的扩展中接口部分

#import "FKCar.h"
@interface FKCar ()
{
    NSString* name;
}
@property(nonatomic, copy) NSString* color;
- (void)drive:(NSString *)owner;
@end

在FKCar的接口部分

#import <Foundation/Foundation.h>
@interface FKCar : NSObject
@property (nonatomic ,copy)NSString* brand;
@property (nonatomic ,copy)NSString* model;
- (void)drive;
@end

在FKCar的实现部分

#import "FKCar.h"
#import "FKCar_drive.h"
@implementation FKCar
@synthesize brand;
@synthesize model;
@synthesize color;
- (void)drive{
    NSLog(@"%@汽车正在路上奔跑",self);
}
- (void)drive:(NSString *)owner{
    NSLog(@"%@正驾驶%@汽车在路上奔跑",owner,self);
}
- (NSString *)description{
    return [NSString stringWithFormat:@"%@---%@---%@",self.brand,self.model,name];
}
@end

main函数的调用实现部分

#import "FKCar_drive.h"
#import "FKCar.h"
FKCar* car = [[FKCar alloc]init];
car.brand = @"宝马";
car.model = @"X5";
car.color = @"黑色";
[car drive];
[car drive:@"孙悟空"];
NSLog(@"%@",[car description]);

四、协议(protocol)与委托

1、规范、协议和接口

协议定义了一种规范,协议定义某一批类多需要遵守的规范,它不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规范这批类中必须提供某些犯法,协议不提供任何实现,协议体现的是规范和实现分离的设计哲学。

2、使用类别实现非正式协议

例如:

为NSObject的Eatable类别中定义一个taste:方法

#import <Foundation/Foundation.h>
//以NSObject为基础定义Eatable类别
@interface NSObject (Eatable)
- (void)taste;
@end

提示:所有继承NSObject类的子类都会自动带有该方法,而且NSObject的子类可以根据需要,自行决定是否要实现该方法。

FKApple的接口部分

#import <Foundation/Foundation.h>
#import "NSObject+Eatable.h"
//定义类的接口部分
@interface FKApple : NSObject
@end

FKApple的实现部分

#import "FKApple.h"
//为FKApple提供实现部分
@implementation FKApple
- (void)taste{
    NSLog(@"苹果营养丰富,口味很好!");
}
@end

main函数的调用部分实现

FKApple* apple = [[FKApple alloc]init]; [apple taste];//苹果营养丰富,口味很好!

注意:对于实现非正式协议的类而言,OC并不强制实现该协议中的所有方法,也就是说,上面的FKApple类也可以不实现taste方法,但是FKApple如果不实现该协议中的方法,当调用的时候会出现unrecognized selector的错误。

3、正式协议的定义

定义正式协议的基本语法如下:

@protocol 协议名<父协议1,父协议2>
{
     零个到多个方法定义
}

语法的详细说明:

  • (1)、协议名应于类名采用相同的命名规则,有多个单词连接而成,每个单词首字母大写,单词与单词之间无须任何分隔符。
  • (2)、一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类。
  • (3)、协议中定义的方法只有方法签名,没有方法实现,协议中包含的方法既可是类方法,也可是实例方法。

例如:

定义一个FKOutput的协议

#import <Foundation/Foundation.h>
@protocol FKOutput <NSObject>
//定义协议中的方法
- (void)output;
- (void)addData(String msg);
@end

定义一个FKProductable 的协议

#import <Foundation/Foundation.h>
@protocol FKProductable <NSObject>
//定义协议的方法
- (NSDate *)getProduceTime;
@end

定义一个FKPrintable 的协议,父协议为FKOutput和FKProductable

#import <Foundation/Foundation.h>
#import "FKOutput"
#import "FKProductable"
@protocol FKPrintable <NSObject,FKOutputFKProductable>
//定义协议中的方法
- (NSString *)printColor;
@end

4、遵循(实现)协议

在类定义的接口部分可指定该类继承的父类,以及遵守的协议,语法如下:

@interface 类名 : 父类 <协议1,协议2...>

例如:

定义类FKPrinter遵守FKPrintable协议

#import <Foundation/Foundation.h>
#import "FKPrintable.h"
//定义类的接口部分,继承NSObject,遵守FKPrintable协议
@interface FKPrinter : NSObject<FKPrintable>
@end

FKPrinter实现协议的部分

#import "FKPrinter.h"
#define MAX_CACHE_LINE 10
@implementation FKPrinter
{
    NSString* printData[MAX_CACHE_LINE];//使用数组记录所有需要缓存的打印数据
    int dataNum;//打印当前需打印的作业数
}
- (void)output{
    //只要还有作业,继续打印
    while (dataNum > 0) {
        NSLog(@"%@%@",self.printColor,printData[0]);
        //将剩下的作业数减1
        dataNum--;
        //把作业对列整数前移一位
        for (int i = 0; i < dataNum; i ++) {
            printData[i] = printData[i + 1];
        }
    }
}
- (void)addData:(NSString *)msg{
    if (dataNum >= MAX_CACHE_LINE) {
        NSLog(@"输出对列已满,添加失败");
    }else{
        //把打印数据添加到对列中,已保存数据的数量加1
        printData[dataNum++] = msg;
    }
}
- (NSDate *)getProduceTime{
    return [[NSDate alloc]init];
}
- (NSString *)printColor{
    return @"红色";
}
@end

main函数的调用实现部分

FKPrinter* printer = [[FKPrinter alloc]init];
[printer addData:@"疯狂iOS讲义"];
[printer addData:@"疯狂XML讲义"];
[printer output];
//创建一个FKPrinter对象,当成FKProductable使用
NSObject<FKProductable>* p = [[FKPrinter alloc]init];
//调用FKProductable协议中定义的方法
NSLog(@"%@",[p getProduceTime]);
//创建一个FKPrinter对象,当成FKOutput使用
id<FKOutput> myOut = [[FKPrinter alloc]init];
//调用FKOutput协议中定义的方法
[myOut addData:@"孙悟空"];
[myOut addData:@"猪八戒"];
[myOut output];

如果程序需要使用协议来定义变量,有如下两种语法:

①、NSObject<协议1,协议2...>* 变量;
②、id<协议1,协议2...> 变量;

正式协议和非正式协议之间的区别:

  • ①、非正式协议通过为NSObjecr创建类别来实现,而正式协议则直接使用@protocol创建;
  • ②、遵守非正式协议通过继承带特定类别的NSObject来实现;而遵守正式协议则有专门的OC语法;
  • ③、遵守非正式协议不要求实现协议中电议的所有方法;而遵守正式协议则必须实现协议中定义的所有方法。

协议的关键字:@optional、@required两个关键字

  • ①、@optional:位于该关键字之后、@required或@end之前声明的方法是可选的-—实现类既可选择实现这些方法,也可不识闲这些方法。
  • ②、@required:位于该关键字自后、@optional或@end之前声明的方法是必须的—-实现类必须实现这些方法。如果没有实现这些方法,编译器就会提示警告.@required是默认行为。

5、协议与委托(delegate)

协议体现的是一种规范,定义协议的类可以吧协议定义的方法委托给实现协议的类,这样可以让类定义具有更好地通用性质,因为具体的动作交给协议的实现类去完成。

定义协议:

@protocol SecondViewControllerDelegate <NSObject]] >
- (void)addArray:(NSArray *)array;
@end

设置代理:

@property (nonatomic ,strong)id<SecondViewControllerDelegate>delegate;

遵守协议

navc.controller = self;

实现代理协议

[self.delegate addArray:self.selectArray];

五、使用@try处理异常

指定协议

#import <Foundation/Foundation.h>
@protocol FKEatable <NSObject>
@optional
- (void)taste;
@end

遵守协议

#import <Foundation/Foundation.h>
#import "FKEatable.h"
//定义类的接口部分
@interface FKApple : NSObject<FKEatable]] >
@end

实现协议

FKApple* apple = [[FKApple alloc]init]; [apple taste];

当设置协议的时候,制定了方法,但是遵守协议的类没有实现该方法,当要求自己的代理去完成方法时就会出现系统崩溃,为了避免程序引发的异常,程序可使用OC提供的异常机制进行处理。

1、使用@try…@catch…@finally捕捉异常

开发者可以将可能引发异常的代码放在@try后的代码内,当程序引发异常时,该异常可以使用catch来捕捉,进行处理,最后使用@finally块回收资源。下面是OC异常处理机制的语法:

@try{
     //业务实现代码...
}
@catch(异常1 ex){
    //异常处理代码...
}
@catch(异常2 ex){
    //异常处理代码...
}
//可能很多@catch块
@finally{
}
  • 抛出(throw)对象:如果实行@try块里的业务逻辑代码出现异常,系统将自动生成一个异常对象,该异常对象被提交给系统。
  • 捕获(catch)异常:当运行环境接收到异常对象时,会寻找能处理该异常对象的@catch块,如果找到合适的@catch块,并把该异常交给该@catch块处理。如果找不到捕获异常的@catch块,则运行时环境终止,程序也会退出。

例如:

FKApple* apple = [[FKApple alloc]init];
@try {
    [apple taste];
}
@catch (NSException *exception) {
    NSLog(@"==捕获异常==");
    NSLog(@"捕获异常:%@,%@",exception.name,exception.reason);//捕获异常:NSInvalidArgumentException,-[FKApple taste]: unrecognized selector sent to instance 0x1002029b0
}
@finally {
    NSLog(@"资源回收!");//此处可进行资源回收等操作
}
NSLog(@"程序执行完成");
提示:NSException类是OC所有异类的根类,其他异类都应该通过该类进行派生。

2、访问异常信息

所有的异常对象包含的常用方法:

  • (1)、name:返回该异常详细的名称;
  • (2)、reason:返回引发该异常的原因;
  • (3)、userInfo:返回引发该异常的用户附加信息,该方法的返回值是一个NSDictionary对象。

3、使用@finally回收资源

不管@try块中的代码是否出现异常,也不管哪一个@catch块被执行,甚至在@try块或@catch块中执行了return语句,@finally都会执行。
注意:通常情况下,不要在@finally块中使用如return或@throw等导致方法终止的语句,一旦在@finally块中使用了return或@throw语句,将会导致@try块以及@catch块中的return、@throw失效。

例如:

BOOL test(){
    @try {
        return YES;
    }
    @finally {
        return NO;
    }
}

然后调用时输出:

BOOL a = test();
NSLog(@"A = %d",a);//输出代表NO的0;

当程序执行@try块、@catch块时遇到了return语句或@catch语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流程中是否包含@finally块,如果没有@finally块,程序立即执行return语句或@throw语句,方法终止。如果有@finally块,系统立即开始执行@finally块,只有当@finally块执行完成后,系统才会再次跳回来执行@try块、@catch块里的return或@throw语句,如果@finally块里也使用了return或@throw等导致方法终止的语句,则@finally块已经终止了方法系统将不会跳回去执行@try块,@catch块里的任何语句。

4、抛出异常与自定义异常类

在某些时候,某些数据于业务规则不相匹配,系统无法抛出这种异常,如果需要在程序中自行抛出异常,应使用@throw语句,@throw语句可以单独使用,@throw语句抛出的不适异常类,而是一个异常实例,而且每次只能抛出一个异常实例。语法格式如下:

@throw ExceptionInstance

用户自定义的异常都应该继承NSException基类,

例如:

自定义FKMyException

#import <Foundation/Foundation.h>
@interface FKMyException : NSException
@end

定义FKDog类接口部分

#import <Foundation/Foundation.h>
@interface FKDog : NSObject
@property (nonatomic ,assign)int age;
@end

定义FKDog类实现部分

#import "FKDog.h"
#import "FKMyException.h"
//为FKDog提供实现部分
@implementation FKDog
@synthesize age = _age;
- (void)setAge:(int)age{
    if (self.age != age) {
        //检查年龄是否在0~50之间
        if (age > 50 || age < 0) {
            //手动抛出异常
            @throw [[FKMyException alloc]initWithName:@"IllegalArgumentException" reason:@"狗的年龄必须在0~50之间" userInfo:nil];
        }
        _age = age;
    }
}
@end

六、OC反射机制

OC提供了三种编程方式与运行环境交互

  • (1)、直接通过OC源代码;
  • (2)、通过NSObject类中定义的代码进行动态编程;
    例如:

    isKindOfClass:、isMemberOfClass:方法用于判断该类所属的类;
    respondsToSelector:用于判断该实例是够可调用指定的方法;
    conformsToProtocol:可用于判断该对象是否遵守指定协议;
    methodForSelector:可用于返回指定方法实现的指针。
    
  • (3)、直接调用运行时函数进行动态编程。

1、获得class

每个类都有一个对应的Class,OC程序中获得Class通常有三种方式

  • (1)、使用Class NSClassFromString(NSString* aClassName)函数来获取Class,该函数需要传入字符串参数,该字符串参数的值是某个类的类名。
    例如:
NSLog(@"%@",NSClassFromString(@"FKDog"));//FKDog
  • (2)、调用某个类的class方法来获取该类的Class。
    例如:
NSLog(@"%@",[FKDog class]);//FKDog
  • (3)、调用某个对象的class方法,该方法是NSObject类中的一个方法,所有所有的OC对象都可以调用该方法,该方法将会返回该对象所属类对应的Class。
    例如:
NSLog(@"%@",[dog class]);//FKDog
相比之下第二种方法有两种优势:
(1)、代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在。
(2)、程序性能更高,因为这种方式无须调用方法所以性能更好。
//通过字符串来获取Class
Class clazz = NSClassFromString(@"NSDate");
NSLog(@"%@",clazz);//NSDate
//直接使用Class来创建对象
NSDate* date = [[NSDate alloc]init];
NSLog(@"%@",date);//2014-11-15 08:17:38 +0000
//通过对象来获取Class
NSLog(@"%@",[date class]);//__NSDate
//通过类来获取Class
NSLog(@"%@",[NSDate class]);//NSDate
NSLog(@"%d",clazz == NSDate.class);//1

提示:通过字符串,类名获取的Class对象是相等的,但是通过NSDate对象获取的Class则是__NSDate,原因是:OC的很多设计都采用类簇的设计,NSDate只是这个类簇的前端,当程序调用[[NSDate alloc]init];创建对象时,程序返回的只是NSDate的子类(__NSDate)的实例,而不是NSDate的实例。因此,程序直接调用date对象的class方法来获取Class时返回__NSDate。

2、检查继承关系

如果程序只是需要确定一个类的继承关系,比如,判断它是否为某个类的实例或是否为某个类及其子类的实例,可以调用NSObject提供的如下几个方法进行判断:

  • (1)、isKindOfClass:该方法需要传入一个Class参数,用于判断该对象是否为该类及其子类的实例;
  • (2)、isMemberOfClass:该方法需要传入一个Class参数,用于判断该对象是否为该类的实例。
  • (3)、conformsToProtocol:该方法需要传入一个Protocol参数,用于判断该对象是否为该类的及其子类的实例。

第三种方法需要传入一个Protocol参数,为了在程序中获取Protocol,可通过如下两种方式来完成:

(1)、OC提供的@Protocol指令来实现;
(2)、可调用Protocol* NSProtocolFromString(NSString *namestr);方法根据协议名字符串获取对应的协议。

例如:

FKApple* apple = [[FKApple alloc]init];
//通过对象来判断该对象的Class
NSLog(@"%@",[apple class]);
//判断对象是否为某个类的实例
NSLog(@"apple是否为FKApple的实例:%d",[apple isMemberOfClass:[FKApple class]]);//1
NSLog(@"apple是否为NSObject的实例:%d",[apple isMemberOfClass:[NSObject class]]);//0
//判断对象是否为某个类及其子类的实例
NSLog(@"apple是否为FKApple及其子类的实例:%d",[apple isKindOfClass:[FKApple class]]);//1
NSLog(@"apple是否为NSObject及其子类的实例:%d",[apple isKindOfClass:[FKApple class]]);//1
//判断对象是否实现了指定协议
NSLog(@"apple是否实现FKEatable协议:%d",[apple conformsToProtocol:@protocol(FKEatable)]);//1

3、动态调用方法

如果程序需要动态调用对象的setter、getter方法,可通过OC提供的KVC机制来实现;如果程序需要访问对象的实例变量的值,不管这个实例变量是否在类的接口部分定义,也不管该变量使用哪种访问控制符修饰,或者是否在类的实例部分定义,程序都可通过KVC机制来设置、访问实例变量的值。
如果程序需要判读某个对象是否可调用方法,可通过NSObject的如下方法进行判断:
respondsToSelector:该方法需要传入一个SEL参数,也就是说没需要传入一个SEL对象作为参数,为了在程序中动态获取SEL对象,OC提供如下方法:

  • (1)、使用@selector指令来获取当前类中指定的方法。该指令需要用完整的方法签名关键字作为参数,仅有方法名是不够的;
  • (2)、使用SEL NSSelectorFromString(NSString* aDelectorName)函数根据方法签名关键字的字符串获取对应的方法。

如果程序需要动态的调用对象的普通方法,可通过如下两种方式来实现:

  • (1)、通过NSObject提供的系列performSelect:方法实现,该方法的第一个参数需要传入一个SEL对象。如果调用方法需要传入参数,还可以通过withObject:标签来传入参数;
  • (2)、使用objc_msgSend(receive,selector,…)函数来调用。该函数的第一个参数是方法调用者,第二个参数代表调用的方法,接下来的参数将作为函数调用的参数。

注意:为了在程序中使用objc_msgSend()函数,应该使用#import显示导入<objc/message.h>头文件。除此之外,objc_msgSend()还有两个版本,即objc_msgSend_fpret()、objc_msgSend_stret(),点那个目标方法返回浮点数时使用objc_msgSend_fpret()函数;当目标方法返回结构体数据时,使用objc_msgSend_stret()函数。//貌似不能用了

七、手动内存管理

对于有效的内存管理,通常认为包括两方面的内容

  • (1)、内存管理:当程序创建对象时需要为对象分配内存。采用合理的设计,尽量减少对象的创建,并减少对创建过程中的内存开销,这就是内存管理的一方面的内容。
  • (2)、内存回收:当程序不在需要对象时,系统必须及时回收这些对象所占用的内存,以便程序可以再次使用这些内存。
    典型的内存回收策略有两种:

    • ①、自动回收,在这种策略下系统会自动跟踪所有的对象,并在这些对象失去作用时回收他们占用的内存。
    • ②、混合回收,在这种策略下,系统会做一些工作,但同时也需要程序员在对象失去作用的时候通知系统。

    总结起来:OC程序员可用的内存回收机制有如下几种:

    • (1)、手动引用计数和自动释放池;
    • (2)、自动引用计数(ARC);
    • (3)、自动垃圾回收。

1、对象的引用计数

正常情况下,当一段代码需要访问某个对象,该对象的引用计数加1;当这段代码不在访问该对象时,该对象的引用计数减1,白哦是这段代码不在访问该对象。当对象的引用计数为0时,表明程序已经不在需要该对象,系统就会回收该对象所占用的内存。系统在销毁对象之前,会自动调用该对象的dealloc方法来执行一些回收操作。

手动引用计数回收内存的标准是:当一个对象的引用计数为0时,就表明程序不在需要该对象,从而通知系统回收该对象所占用的内存。

在手动引用计数中,改变对象的引用计数的方式如下:

  • (1)、当程序调用方法名以alloc、new、copy、mutableCopy开头的方法来创建对象时,该赌侠ing的引用计数加1;
  • (2)、程序调用对象的retain方法时,该对象的引用计数加1;
  • (3)、程序调用对象的release方法时,该对象的应用计数减1;

NSObject中提供了有关引用计数的如下方法:

  • (1)、-retain:将该对象的引用计数器加1;
  • (2)、-release:将该对象的应用计数减1;
  • (3)、-autorelease:不改变该对象的引用计数器的值,只是将对象添加到自动释放池中;
  • (4)、-retainCount:返回该对象的引用计数值。

例如:在FKItem的实现部分重写dealloc方法

#import "FKItem.h"
//FKItem提供实现部分
@implementation FKItem
//实现接口部分定义的方法
- (id)init{
    if (self = [super init]) {
        NSLog(@"init方法中,引用计数为:%ld",self.retainCount);
    }
    return self;
}
- (void)dealloc{
    NSLog(@"系统开始销毁我了,再见了");
    [super dealloc];
}
@end

main函数中调用方法实现

//调用new方法创建对象,该对象的引用计数为1;
FKItem* item = [FKItem new];
NSLog(@"%ld",item.retainCount);//1
[item retain];//引用计数加1,retainCount为2
NSLog(@"%ld",item.retainCount);//2
[item retain];//引用计数加1,retainCount为3
NSLog(@"%ld",item.retainCount);//3
[item release];//引用计数减1,retainCount为2
NSLog(@"%ld",item.retainCount);//2
[item release];//引用计数减1,retainCount为1
NSLog(@"%ld",item.retainCount);//1
[item release];//引用计数减1,retainCount为0
//系统会自动调用该对象的dealloc方法来销毁它
//后面代码不要调用item指针的方法,调用item方法看到得都是假象,甚至可能导致崩溃。

2、对象的所属权

例如:在FKUser的借口部分持有FKItem的对象

#import <Foundation/Foundation.h>
#import "FKItem.h"
@interface FKUser : NSObject
{
    FKItem* _item;
}
- (void)setItem:(FKItem *)item;
- (FKItem *)item;
@end

在FKUser的实现部分,对item就行持有

#import "FKUser.h"
@implementation FKUser
- (void)setItem:(FKItem *)item{
    if (_item != item) {
        //让item的引用计数加1
        [item retain];
        _item = item;
    }
}
- (FKItem *)item{
    return _item;
}
- (void)dealloc{
    //让_item的引用计数减1
    NSLog(@"%ld",_item.retainCount);
    [_item release];
    NSLog(@"系统开始销毁FKUser对象");
    [super dealloc];
}
@end

在main函数中调用

//调用new方法创建对象,该对象的引用计数为1;
FKItem* item = [FKItem new];
NSLog(@"创建出来的应用计数为:%ld",item.retainCount);//1
FKUser* user = [[FKUser alloc]init];
[user setItem:item];
NSLog(@"被FKUser持有后的引用计数为:%ld",item.retainCount);//2
//main方法将item的引用计数减1,item的引用计数为1;
[item release];
NSLog(@"调用release之后的引用计数为:%ld",item.retainCount);//1
//user的引用计数减1,user的引用计数为0
//系统调用user的dealloc方法,调用dealloc方法时将会让FKItem的计数减1
//这样item的引用计数为0,系统就会执行item的dealloc方法进行销毁
[user release];

3、方法中的保留和释放

上述中还有问题,应该改成:
在FKUser的实现部分

- (void)setItem:(FKItem *)item{ if (_item != item) { //让item的引用计数加1 [_item release]; _item = [item retain]; } }

然后在main函数调用实现部分

//调用new方法创建对象,该对象的引用计数为1;
FKItem* item = [FKItem new];
NSLog(@"创建出来item的引用计数为:%ld",item.retainCount);//1
FKUser* user = [[FKUser alloc]init];
[user setItem:item];
NSLog(@"被FKUser持有后的引用计数为:%ld",item.retainCount);//2
//重新创建一个对象item2
FKItem* item2 = [[FKItem alloc]init];
//将item2作为参数赋传给setItem:方法
[user setItem:item2];
NSLog(@"重新赋值后item的引用计数为:%ld",item.retainCount);//1
NSLog(@"重新赋值后item2的引用计数为:%ld",item2.retainCount);//2
//main方法将item的引用计数减1,item的引用计数为0;
//这样item的引用计数为0,系统就会执行item的dealloc方法进行销毁
[item release];
//main方法将item2的引用计数减1,item的引用计数为1;
[item2 release];
NSLog(@"调用release之后的引用计数为:%ld",item2.retainCount);//1
//user的引用计数减1,user的引用计数为0
//系统调用user的dealloc方法,调用dealloc方法时将会让FKItem的计数减1
//这样item2的引用计数为0,系统就会执行item2的dealloc方法进行销毁
[user release];

4、使用自动释放池

为了保证函数、方法返回的对象不会再被返回之前就被销毁,我们需要保证被函数、方法返回的对象能被延迟销毁,为了实现这种延迟销毁,有如下两种方法:

  • (1)、程序每次获取并使用完其他方法、函数返回的对象之后,立即调用该对象的release方法将函数、方法返回对象的引用计数减1.
  • (2)、使用自动释放池进行延迟销毁。

    - (id)autorelease:该方法不会修改对象的引用计数,只是将该对象添加到自动释放池中,该方法将会返回调用该方法的对象本身。由于autorelease方法预设了一条在将来某个时间将要调用release方法,该方法会把对象的引用计数减1,当程序在自动释放池上下文中调用某个对象的autorelease方法时,该方法只是将该对象添加到自动释放池中,当该自动释放池释放时,自动释放池会让池中的所有的对象执行release方法。
    

5、自动释放池的销毁时机与工作过程

创建和销毁自动释放池的代码模块如下:

NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];
[pool release];
//解释:当程序创建NSAutoreleasePool对象之后,该对象的引用计数为1,接下来当程序调用该对象的release方法后,该对象的引用计数变为0,系统准备调用NSAutoreleasePool的dealloc方法来回收该对象,系统会在调用NSAutoreleasePool的dealloc方法时回收该池中的所有的对象。
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];
FKItem* item = [[FKItem alloc]init];
//接下来可以调用item的方法
NSLog(@"%ld",item.retainCount);//1
//创建一个FKUser对象 并将它添加到自动释放池中
FKUser* user = [[[FKUser alloc]init]autorelease];
//接下来可以调用user方法
NSLog(@"%ld",user.retainCount);//1
//系统将因为池的引用计数为0而回收自动释放池
//回收自动释放池时会调用池中所有对象的release方法
[pool release];//释放之后user的计数为0

6、临时对象与事件循环中的自动释放池

NSMutableArray* array = [NSMutableArray arrayWithCapacity:20];
  • 临时对象:上述代码创建了一个可变数组,该方法不是以alloc、new、copy、mutableCopy开头的,因此该方法默认创建了一个自动释放池的对象,当该自动释放池释放时,该对象就会被自动释放,因此该对象也被成为临时对象。
  • 事件循环:当程序中某个组件事件被激发后,系统将会回调某个方法来处理改时间。

事件循环步骤:

  1. 创建自动释放池;
  2. 调用事件处理方法;
  3. 销毁自动释放池;
  4. 处理完成,方法返回。

如果程序需要在事件处理结束后依然可以保留临时对象,可采用如下几种方式:

  • (1)、将临时对象赋值之前,先调用临时对象的retain方法将它的引用计数加1;
  • (2)、把临时对象赋值给retain、strong或copy指示符修饰的属性。

注意:assign、week、unsafe_unretain指示符的属性除外。

第一种:

[array retain];

第二种:
在MyView.h中的代码如下

#import <UIKit/UIKit.h>
@interface MyView : UIView
@property(nonatomic ,retain)NSMutableArray* data;
@end

在MyView.m中的代码如下:

- (void)setData:(NSMutableArray *)data{
    if (_data == data) {
        [_data release];
        _data = [data retain];
    }
}
- (void)dealloc{
    [_data release];
    [super dealloc];
}

7、手动内存管理的规则总结

  • (1)、调用对象的release方法并不是销毁该对象,只是将对象的引用计数减1;当一个对象的引用计数为0时,系统会自动调用该对象的dealloc方法来销毁对象。
  • (2)、当自动释放池被回收时,自动释放池会依次调用池中每个对象的release方法。如果该对象调用release方法后引用计数变为0,那么该对象将要被销毁;否则该对象可以从自动释放池中活下来。
  • (3)、当程序使用alloc、new、copy、mutableCopy来头的方法创建对象后,该对象的引用数为1,当不在使用对象时,需要调用该对象的release方法或者autorelease方法。
  • (4)、如果使用retain方法为对象增加过引用计数,则用完该对象后需要调用release方法来减少该对象的引用计数,并保证retain次数与release次数相等。
  • (5)、如果通过其他方法获取了对象,且该对象是一个临时对象,如果在自动释放上下文中使用该对象,那么使用完成后无需理会该对象的回收,系统会自动回收该对象。如果程序需要保留这个临时对象,则需要手动调用retain来增加临时对象的引用计数;或者将该临时对象赋给retain、strong或copy指示符修饰的属性。
  • (6)、在Cocoa和iOS的事件循环中,每个事件处理方法执行之前会创建自动释放池,方法执行完成后会回收自动释放池。如果希望自动释放池被回收后,池中的某些对象能活下来,程序必须增加该对象的引用计数,保证该对象的引用计数大于他调用autorelease的次数。

八、自动引用计数

1、自动引用计数概述(Automatic Reference Counting)

打开ARC

  1. 点击蓝色的项目名称
  2. 点击Build Setting
  3. 找到Objective-C Automatic Reference Counting
  4. 将该选项设为YES即可。

打开关闭某个文件的ARC:

(1)、 打开:-fobjc-arc表明该程序使用了ARC机制,程序员不允许在程序中调用对象的retain、release方法改变对象的引用计数。

  1. 在Xcode中选中项目名称,然后点击TARGETS下的项目名,然后切换到Build Phases,打开Compile Sources的下三角。
  2. 选中(可以多选)想要禁用ARC的文件名,然后按回车键,输入 -fobjc-arc这样的编译器标识。

(2)、禁用:-fno-objc-arc表明关闭了某个文件的ARC机制。

  1. 在Xcode中选中项目名称,然后点击TARGETS下的项目名,然后切换到Build Phases,打开Compile Sources的下三角。
  2. 选中(可以多选)想要禁用ARC的文件名,然后按回车键,输入 –fno-objc-arc 这样的编译器标识

2、@autoreleasePool块

mian函数中的@autoreleasePool{}块,定义自动释放池上下文,任何在该上下文创建的对象昂都有ARC来自动释放,并在自动释放块结束时销毁这些对象。

注意:如果程序在执行过程中创建了大量的临时对象(比如在循环中创建对象,这是一种非常差的做法),程序可能需要多次释放这些临时对象,此时程序可以考虑将@autoreleasePool块放在循环体中。

例如:

for(int i = 0;i < 100;i ++){
    @autoreleasepool {
        //创建临时对象
        //调用临时对象的费方法
        //...
    }
}

你可能感兴趣的:(面向对象,Objective-C,C语言)