OC之Foundation框架使用

1.日期的简单用法

void testDate()
{
    NSDate *date1 = [NSDate date];
    NSLog(@"%@",date1); // 结果:2015-09-17 08:53:42 +0000

    NSDate *date2 = [NSDate dateWithTimeIntervalSinceNow:60];
    NSLog(@"%@",date2);

    NSDate *date3 = [NSDate distantFuture]; // 获取一个将来的日期
    NSLog(@"%@",date3); // 结果:4001-01-01 00:00:00 +0000

    NSTimeInterval time = [date2 timeIntervalSinceDate:date1]; // 返回两个日期的时间差
    NSLog(@"%f",time); // 结果:60.004809 date2>date1 ? 正 : 负;

    NSDate *date4 = [date1 earlierDate:date2];
    NSLog(@"%@",date4);

    // 日期格式化
    NSDateFormatter *formater1 = [[NSDateFormatter alloc] init];
    formater1.dateFormat = @"yy年MM月dd日-HH:mm:ss";
    NSString *datestr1 = [formater1 stringFromDate:date1];
    NSLog(@"%@",datestr1);  // 结果: 15年09月17日-17:08:43
    // 字符串转化为日期
    NSDate *date5 = [formater1 dateFromString:@"15年09月17日-17:10:45"];
    NSLog(@"%@",date5); // 结果:2015-09-17 09:10:45 +0000
}

2.用字符串操作目录

void testString()
{
    // 路径操作
    NSMutableArray *marry = [NSMutableArray array];
    [marry addObject:@"Users"];
    [marry addObject:@"QiZhang"];
    [marry addObject:@"Desktop"];

    NSString *path = [NSString pathWithComponents:marry];
    NSLog(@"%@",path);  // 结果:Users/QiZhang/Desktop

    NSLog(@"%@",[path pathComponents]); // 把路径根据“/”分割成字符数组
    /* ( Users, QiZhang, Desktop ) */

    NSLog(@"%i",[path isAbsolutePath]); // 是否绝对路径(其实就是看字符串是否以“/”开头) 返回布尔值 结果:0
    NSLog(@"%@",[path lastPathComponent]); // 取得最后一个目录 结果:Desktop
    NSLog(@"%@",[path stringByDeletingLastPathComponent]); // 删除最后一个目录 结果:Users/QiZhang
    NSLog(@"%@",[path stringByAppendingPathComponent:@"Documents"]);

}

3.数组

非可变数组:

void testArray()
{
    NSArray *array = @[@"abc",@"sdf",@"rer",@"opi"];
    // 利用block遍历数组
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"%ld-%@",idx,obj);
        if (idx == 3) *stop = YES;
    }];
    // 利用迭代器遍历数组
    NSEnumerator *enumerator = [array objectEnumerator]; // 获得一个迭代器
    id obj = nil;
    while (obj = [enumerator nextObject]) {
        NSLog(@"%@",obj);
    }
}

注意:
NSArray中只能存放对象,不能存放基本数据类型,通常我们可以通过在基本数据类型前加@进行转换;
数组中的元素后面必须加nil以表示数据结束;
makeObjectsPerformSelector执行数组中对象的方法,其参数最多只能有一个;
上面数组操作中无论是数组的追加、删除、截取都没有改变原来的数组,只是产生了新的数组而已;
对象的比较除了使用系统自带的方法,我们可以通过自定义比较器的方法来实现;

可变数组:

代码等待

注意:
可变数组中的元素后面必须加nil以表示数据结束;
往一个可变数组中添加一个对象,此时这个对象的引用计数器会加1,当这个对象从可变数组中移除其引用计数器减1。同时当整个数组销毁之后会依次调用每个对象的releaes方法。
在不可变数组中无论对数组怎么排序,原来的数组顺序都不会改变,但是在可变数组中如果使用sortUsingSelector:排序原来的数组顺序就发生了变化。

4.字典

//
// main.m
// FoundationFramework
//
// Created by Kenshin Cui on 14-2-16.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>


void test1(){
    NSDictionary *dic1=[NSDictionary dictionaryWithObject:@"1" forKey:@"a"];
    NSLog(@"%@",dic1);
    /*结果: { a = 1; } */

    //常用的方式
    NSDictionary *dic2=[NSDictionary dictionaryWithObjectsAndKeys:
                        @"1",@"a",
                        @"2",@"b",
                        @"3",@"c",
                        nil];
    NSLog(@"%@",dic2);
    /*结果: { a = 1; b = 2; c = 3; } */


    NSDictionary *dic3=[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"1",@"2", nil] forKeys:[NSArray arrayWithObjects:@"a",@"b", nil]];
    NSLog(@"%@",dic3);
    /*结果: { a = 1; b = 2; } */


    //更简单的方式
    NSDictionary *dic4=@{@"1":@"a",@"2":@"b",@"3":@"c"};
    NSLog(@"%@",dic4);
    /*结果: { 1 = a; 2 = b; 3 = c; } */
}
void test2(){
    NSDictionary *dic1=[NSDictionary dictionaryWithObjectsAndKeys:
                        @"1",@"a",
                        @"2",@"b",
                        @"3",@"c",
                        @"2",@"d",
                        nil];
    NSLog(@"%zi",[dic1 count]); //结果:4
    NSLog(@"%@",[dic1 valueForKey:@"b"]);//根据键取得值,结果:2
    NSLog(@"%@",dic1[@"b"]);//还可以这样读取,结果:2
    NSLog(@"%@,%@",[dic1 allKeys],[dic1 allValues]);
    /*结果: ( d, b, c, a ),( 2, 2, 3, 1 ) */

    NSLog(@"%@",[dic1 objectsForKeys:[NSArray arrayWithObjects:@"a",@"e" , nil]notFoundMarker:@"not fount"]);//后面一个参数notFoundMarker是如果找不到对应的key用什么值代替
    /*结果: ( 1, "not fount" ) */
}
void test3(){
    NSDictionary *dic1=[NSDictionary dictionaryWithObjectsAndKeys:
                        @"1",@"a",
                        @"2",@"b",
                        @"3",@"c",
                        @"2",@"d",
                        nil];
    //遍历1
    for (id key in dic1) {//注意对于字典for遍历循环的是key
        NSLog(@"%@=%@",key,[dic1 objectForKey:key]);
    }
    /*结果: d=2 b=2 c=3 a=1 */

    //遍历2
    NSEnumerator *enumerator=[dic1 keyEnumerator];//还有值的迭代器[dic1 objectEnumerator]
    id key=nil;
    while (key=[enumerator nextObject]) {
        NSLog(@"%@=%@",key,[dic1 objectForKey:key]);

    }
    /*结果: d=2 b=2 c=3 a=1 */

    //遍历3
    [dic1 enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        NSLog(@"%@=%@",key,obj);
    }];
    /*结果: d=2 b=2 c=3 a=1 */
}

void test4(){
    NSMutableDictionary *dic=[NSMutableDictionary dictionaryWithObjectsAndKeys:@"1",@"a",
                              @"2",@"b",
                              @"3",@"c",
                              @"2",@"d",
                            nil];
    [dic removeObjectForKey:@"b"];
    NSLog(@"%@",dic);
    /*结果: { a = 1; c = 3; d = 2; } */

    [dic addEntriesFromDictionary:@{@"e":@"7",@"f":@"6"}];
    NSLog(@"%@",dic);
    /*结果: { a = 1; c = 3; d = 2; e = 7; f = 6; } */

    [dic setValue:@"5" forKey:@"a"];
    NSLog(@"%@",dic);
    /*结果: { a = 5; c = 3; d = 2; e = 7; f = 6; } */


    //注意,一个字典的key或value添加到字典中时计数器+1;字典释放时调用key或value的release一次,计数器-1
}


int main(int argc, const char * argv[]) {
    test1();
    test2();
    test3();
    test4();
    return 0;
}

5.装箱和拆箱

其实从上面的例子中我们也可以看到,数组和字典中只能存储对象类型,其他基本类型和结构体是没有办法放到数组和字典中的,当然你也是无法给它们发送消息的(也就是说有些NSObject的方法是无法调用的),这个时候通常会用到装箱(boxing)和拆箱(unboxing)。其实各种高级语言基本上都有装箱和拆箱的过程,例如C#中我们将基本数据类型转化为Object就是一个装箱的过程,将这个Object对象转换为基本数据类型的过程就是拆箱,而且在C#中装箱的过程可以自动完成,基本数据类型可以直接赋值给Object对象。但是在ObjC中装箱的过程必须手动实现,ObjC不支持自动装箱。

在ObjC中我们一般将基本数据类型装箱成NSNumber类型(当然它也是NSObject的子类,但是NSNumber不能对结构体装箱),调用其对应的方法进行转换:

+(NSNumber *)numberWithChar:(char)value;

+(NSNumber *)numberWithInt:(int)value;

+(NSNumber *)numberWithFloat:(float)value;

+(NSNumber *)numberWithDouble:(double)value;

+(NSNumber *)numberWithBool:(BOOL)value;

+(NSNumber *)numberWithInteger:(NSInteger)value;

拆箱的过程就更加简单了,可以调用如下方法:

-(char)charValue;

-(int)intValue;

-(float)floatValue;

-(double)doubleValue;

-(BOOL)boolValue;
代码:

#import <Foundation/Foundation.h>


/*可以存放基本类型到数组、字典*/
void test1(){
    //包装类NSNumber,可以包装基本类型但是无法包装结构体类型
    NSNumber *number1=[NSNumber numberWithChar:'a'];//'a'是一个C语言的char类型我们无法放倒NSArray中,但是我们可以通过NSNumber包装
    NSArray *array1=[NSArray arrayWithObject:number1];
    NSLog(@"%@",array1);
    /*结果: ( 97 ) */

    NSNumber *number2= [array1 lastObject];
    NSLog(@"%@",number2);//返回的不是基本类型,结果:97


    char char1=[number2 charValue];//number转化为char
    NSLog(@"%c",char1); //结果:a
}

int main(int argc, const char * argv[]) {
    test1();
    return  0;
}
上面我们看到了基本数据类型的装箱和拆箱过程,那么结构体呢?这个时候我们需要引入另外一个类型NSValue,其实上面的NSNumber就是NSValue的子类,它包装了一些基本数据类型的常用装箱、拆箱方法,当要对结构体进行装箱、拆箱操作我们需要使用NSValue,NSValue可以对任何数据类型进行装箱、拆箱操作。

事实上对于常用的结构体Foundation已经为我们提供好了具体的装箱方法:

+(NSValue *)valueWithPoint:(NSPoint)point;

+(NSValue *)valueWithSize:(NSSize)size;

+(NSValue *)valueWithRect:(NSRect)rect;

对应的拆箱方法:

-(NSPoint)pointValue;

-(NSSize)sizeValue;

-(NSRect)rectValue;



#import <Foundation/Foundation.h>

//NSNumber是NSValue的子类,而NSValue可以包装任何类型,包括结构体
void test1(){
    CGPoint point1=CGPointMake(10, 20);
    NSValue *value1=[NSValue valueWithPoint:point1];//对于系统自带类型一般都有直接的方法进行包装
    NSArray *array1=[NSArray arrayWithObject:value1];//放倒数组中
    NSLog(@"%@",array1);
    /*结果: ( "NSPoint: {10, 20}" ) */

    NSValue *value2=[array1 lastObject];
    CGPoint point2=[value2 pointValue];//同样对于系统自带的结构体有对应的取值方法(例如本例pointValue)
    NSLog(@"x=%f,y=%f",point2.x,point2.y);//结果:x=10.000000,y=20.000000
}


int main(int argc, const char * argv[]) {
    test1();
    return  0;
}


那么如果是我们自定义的结构体类型呢,这个时候我们需要使用NSValue如下方法进行装箱:

+(NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type;

调用下面的方法进行拆箱:

-(void)getValue:(void *)value;

//
// main.m
// FoundationFramework
//
// Created by Kenshin Cui on 14-2-16.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef struct {
    int year;
    int month;
    int day;
} Date;


//NSNumber是NSValue的子类,而NSValue可以包装任何类型,包括结构体
void test1(){
    //如果我们自己定义的结构体包装
    Date date={2014,2,28};
    char *type=@encode(Date);
    NSValue *value3=[NSValue value:&date withObjCType:type];//第一参数传递结构体地址,第二个参数传递类型字符串
    NSArray *array2=[NSArray arrayWithObject:value3];
    NSLog(@"%@",array2);
    /*结果: ( "<de070000 02000000 1c000000>" ) */

    Date date2;
    [value3 getValue:&date2];//取出对应的结构体,注意没有返回值
    //[value3 objCType]//取出包装内容的类型
    NSLog(@"%i,%i,%i",date2.year,date2.month,date2.day); //结果:2014,2,28

}


int main(int argc, const char * argv[]) {
    test1();
    return  0;
}
扩展1-NSNull

通过前面的介绍大家都知道无论在数组还是在字典中都必须以nil结尾,否则数组或字典无法判断是否这个数组或字典已经结束(与C语言中的字符串比较类似,C语言中定义字符串后面必须加一个”\0”)。但是我们有时候确实想在数据或字典中存储nil值而不是作为结束标记怎么办呢?这个时候需要使用NSNull,这个类是一个单例,只有一个null方法。简单看一下:

//
// main.m
// FoundationFramework
//
// Created by Kenshin Cui on 14-2-16.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>



int main(int argc, const char * argv[]) {

    NSNull *nl=[NSNull null];//注意这是一个对象,是一个单例,只有一个方法null创建一个对象
    NSNull *nl2=[NSNull null];
    NSLog(@"%i",nl==nl2);//由于是单例所以地址相等,结果:1

    NSArray *array1=[NSArray arrayWithObjects:@"abc",nl,@123, nil];
    NSLog(@"%@",array1);
    /*结果: ( abc, "<null>", 123 ) */

    return  0;
}

6.@解释

我们知道在ObjC中很多关键字前都必须加上@符号,例如@protocol、@property等,当然ObjC中的字符串必须使用@符号,还有就是%@可以表示输出一个对象。其实@符号在新版的ObjC中还有一个作用:装箱。

#import <Foundation/Foundation.h>

typedef enum {
    spring,
    summer,
    autumn,
    winter
} Season;

int main(int argc, const char * argv[]) {
    /*装箱*/
    NSNumber *number1=@100;
    NSArray *array1=[NSArray arrayWithObjects:number1,@"abc",@16,@'A',@16.7,@YES, nil];
    NSLog(@"%@",array1);
    /*结果: ( 100, abc, 16, 65, "16.7" 1 ) */
    NSNumber *number2=@(1+2*3);
    NSLog(@"%@",number2); //结果:7
    NSNumber *number3=@(autumn);
    NSLog(@"%@",number3); //结果:2


    NSArray *array2=@[@"abc",@16,@'A',@16.7,@YES];//使用这种方式最后不用添加nil值了
    NSLog(@"%@",array2[2]); //结果:65
    NSMutableArray *array3=[NSMutableArray arrayWithArray:array2];
    array3[0]=@"def";
    NSLog(@"%@",array3[0]); //结果:def

    NSDictionary *dic1=@{@"a":@123,@"b":@'c',@"c":@YES};
    NSLog(@"%@",dic1);
    /*结果: { a = 123; b = 99; c = 1; } */
    NSMutableDictionary *dic2=[NSMutableDictionary dictionaryWithDictionary:dic1];
    dic2[@"a"]=@456;
    NSLog(@"%@",dic2[@"a"]);//结果:456

    return 0;
}

7.反射

//
// Account.h
#import <Foundation/Foundation.h>

@interface Account : NSObject

@property (nonatomic,assign) double balance;

@end
Account.m

//
// Account.m
#import "Account.h"

@implementation Account

@end
Person.h

//
// Person.h
#import <Foundation/Foundation.h>
@class Account;

@interface Person : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,retain) Account *account;

-(Person *)initWithName:(NSString *)name;

+(Person *)personWithName:(NSString *)name;

-(void)showMessage:(NSString *)infomation;

//自己实现对象比较方法
-(NSComparisonResult)comparePerson:(Person *)person;
@end
Person.m

//
// Person.m
#import "Person.h"

@implementation Person

-(Person *)initWithName:(NSString *)name{
    if(self=[super init]){
        self.name=name;
    }
    return self;
}

+(Person *)personWithName:(NSString *)name{
    Person *person=[[Person alloc]initWithName:name];
    return person;
}

-(void)showMessage:(NSString *)infomation{
    NSLog(@"My name is %@,the infomation is \"%@\".",_name,infomation);
}

//自己实现对象比较方法
-(NSComparisonResult)comparePerson:(Person *)person{
    return [_name compare:person.name];
}

-(NSString *)description{
    return [NSString stringWithFormat:@"name=%@",_name];
}

@end
main.m

//
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"


int main(int argc, const char * argv[]) {
    /*常用方法*/
    Person *person1=[Person personWithName:@"Kenshin"];
    NSLog(@"%i",[person1 isKindOfClass:[NSObject class]]); //判断一个对象是否为某种类型(如果是父类也返回YES),结果:1
    NSLog(@"%i",[person1 isMemberOfClass:[NSObject class]]); //判断一个对象是否是某个类的实例化对象,结果:0
    NSLog(@"%i",[person1 isMemberOfClass:[Person class]]); //结果:1
    NSLog(@"%i",[person1 conformsToProtocol:@protocol(NSCopying)]);//是否实现了某个协议,结果:0
    NSLog(@"%i",[person1 respondsToSelector:@selector(showMessage:)]);//是否存在某个方法,结果:1

    [person1 showMessage:@"Hello,world!"];//直接调用一个方法
    [person1 performSelector:@selector(showMessage:) withObject:@"Hello,world!"];
    //动态调用一个方法,注意如果有参数那么参数类型只能为ObjC对象,并且最多只能有两个参数


    /*反射*/
    //动态生成一个类
    NSString *className=@"Person";
    Class myClass=NSClassFromString(className);//根据类名生成类
    Person *person2=[[myClass alloc]init]; //实例化
    person2.name=@"Kaoru";
    NSLog(@"%@",person2);//结果:name=Kaoru

    //类转化为字符串
    NSLog(@"%@,%@",NSStringFromClass(myClass),NSStringFromClass([Person class])); //结果:Person,Person

    //调用方法
    NSString *methodName=@"showMessage:";
    SEL mySelector=NSSelectorFromString(methodName);
    Person *person3=[[myClass alloc]init];
    person3.name=@"Rosa";
    [person3 performSelector:mySelector withObject:@"Hello,world!"]; //结果:My name is Rosa,the infomation is "Hello,world!".

    //方法转化为字符串
    NSLog(@"%@",NSStringFromSelector(mySelector)); //结果:showMessage:

    return 0;
}

8.拷贝

对象拷贝操作也比较常见,在ObjC中有两种方式的拷贝:copy和mutablecopy,这两种方式都将产生一个新的对象,只是后者产生的是一个可变对象。在ObjC中如果要想实现copy或者mutablecopy操作需要实现NSCopy或者NSMutableCopy协议,拷贝操作产生的新的对象默认引用计数器是1,在非ARC模式下我们应该对这个对象进行内存管理。在熟悉这两种操作之前我们首先需要弄清两个概念:深复制(或深拷贝)和浅复制(或浅拷贝)。

浅复制:在执行复制操作时,对于对象中每一层(对象中包含的对象,例如说属性是某个对象类型)复制都是指针复制(如果从引用计数器角度出发,那么每层对象的引用计数器都会加1)。
深复制:在执行复制操作时,至少有一个对象的复制是对象内容复制(如果从引用计数器角度出发,那么除了对象内容复制的那个对象的引用计数器不变,其他指针复制的对象其引用计数器都会加1)。
注:

指针拷贝:拷贝的是指针本身(也就是具体对象的地址)而不是指向的对象内容本身。

对象复制:对象复制指的是复制内容是对象本身而不是对象的地址。

完全复制:上面说了深复制和浅复制,既然深复制是至少一个对象复制是对象内容复制,那么如果所有复制都是对象内容复制那么这个复制就叫完全复制。

对比copy和mutablecopy其实前面我们一直还用到一个操作是retain,它们之间的关系如下:

retain:始终采取浅复制,引用计数器会加1,返回的对象和被复制对象是同一个对象1(也就是说这个对象的引用多了一个,或者说是指向这个对象的指针多了一个);

copy:对于不可变对象copy采用的是浅复制,引用计数器加1(其实这是编译器进行了优化,既然原来的对象不可变,复制之后的对象也不可变那么就没有必要再重新创建一个对象了);对于可变对象copy采用的是深复制,引用计数器不变(原来的对象是可变,现在要产生一个不可变的当然得重新产生一个对象);

mutablecopy:无论是可变对象还是不可变对象采取的都是深复制,引用计数器不变(如果从一个不可变对象产生一个可变对象自然不用说两个对象绝对不一样肯定是深复制;如果从一个可变对象产生出另一个可变对象,那么当其中一个对象改变自然不希望另一个对象改变,当然也是深复制)。

注:

可变对象:当值发生了改变,那么地址也随之发生改变;

不可变对象:当值发生了改变,内容首地址不发生变化;

引用计数器:用于计算一个对象有几个指针在引用(有几个指针变量指向同一个内存地址);

//
// main.m
#import <Foundation/Foundation.h>
void test1(){
    NSString *name=@"Kenshin";
    NSString *str1=[NSString stringWithFormat:@"I'm %@.",name];//注意此时str1的计数器是1
    NSLog(@"%lu",[str1 retainCount]); //结果:1


    NSMutableString *str2=[str1 mutableCopy];//注意此时str2的计数器为1,str1的计数器还是1
    //NSMutableString *str5 =CFRetain((__bridge CFTypeRef)str2);
    NSLog(@"retainCount(str1)=%lu,retainCount(str2)=%lu",[str1 retainCount],[str2 retainCount]);
    //结果:retainCount(str1)=1,retainCount(str2)=1


    [str2 appendString:@"def"];//改变str2,str1不变
    NSLog(@"%zi",str1==str2);//二者不是向同一个对象,结果:0
    NSLog(@"str1=%@",str1); //结果:str1=I'm Kenshin.
    NSLog(@"str2=%@",str2); //结果:str2=I'm Kenshin.def


    NSLog(@"str1's %lu",[str1 retainCount]);
    NSString *str3=[str1 copy];//str3不是产生的新对象而是复制了对象指针,但是str1的计数器+1(当然既然str3同样指向同一个对象,那么如果计算str3指向的对象引用计数器肯定等于str1的对象引用计数器)
    NSLog(@"%zi",str1==str3);//二者相等指向同一个对象,结果:1
    NSLog(@"retainCount(str1)=%lu,retainCount(str3)=%lu",str1.retainCount,str3.retainCount);
    //结果:retainCount(str1)=2,retainCount(str3)=2

    //需要注意的是使用copy和mutableCopy是深复制还是浅复制不是绝对,关键看由什么对象产生什么样的对象
    NSString *str4=[str2 copy];//由NSMutableString产生了NSString,二者类型都不同肯定是深拷贝,此时str2的计数器还是1,str4的计数器也是1
    [str2 appendString:@"g"];//改变原对象不影响str4
    NSLog(@"%zi",str2==str4); //结果:0
    NSLog(@"str2=%@",str2); //结果:str2=I'm Kenshin.defg
    NSLog(@"str4=%@",str4); //结果:str4=I'm Kenshin.def


    [str1 release];
    str1=nil;
    [str3 release];//其实这里也可以调用str1再次release,因为他们两个指向的是同一个对象(但是一般不建议那么做,不容易理解)
    str3=nil;

    [str2 release];
    str2=nil;
    [str4 release];
    str4=nil;

    //上面只有一种情况是浅拷贝:不可变对象调用copy方法

}

int main(int argc,char *argv[]){
    test1();
    return 0;
}

从上面可以清楚的看到str1和str3同时指向同一个对象,因此这个对象的引用计数器是2(可以看到两箭头指向那个对象),str2和str4都是两个新的对象;另外ObjC引入对象拷贝是为了改变一个对象不影响另一个对象,但是我们知道NSString本身就不能改变那么即使我重新复制一个对象也没有任何意义,因此为了性能着想如果通过copy方法产生一个NSString时ObjC不会再复制一个对象而是将新变量指向同一个对象。

注意网上很多人支招在ARC模式下可以利用_objc_rootRetainCount()或者CFGetRetainCount()取得retainCount都是不准确的,特别是在对象拷贝操作之后你会发现二者取值也是不同的,因此如果大家要查看retainCount最好还是暂时关闭ARC。

要想支持copy或者mutablecopy操作那么对象必须实现NSCoping协议并实现-(id)copyWithZone:(NSZone*)zone方法,在Foundation中常用的可复制对象有:NSNumber、NSString、NSMutableString、NSArray、NSMutableArray、NSDictionary、NSMutableDictionary。下面看一下如何让自定义的类支持copy操作:

//
// Person.h
#import <Foundation/Foundation.h>
@class Account;

@interface Person : NSObject

@property  NSMutableString *name;
@property (nonatomic,assign) int age;

@end


//
// Person.m
#import "Person.h"

@implementation Person


-(NSString *)description{
    return [NSString stringWithFormat:@"name=%@,age=%i",_name,_age];
}

//实现copy方法
-(id)copyWithZone:(NSZone *)zone{
    //注意zone是系统已经分配好的用于存储当前对象的内存
    //注意下面创建对象最好不要用[[Person allocWithZone:zone]init],因为子类如果没有实现该方法copy时会调用父类的copy方法,此时需要使用子类对象初始化如果此时用self就可以表示子类对象,还有就是如果子类调用了父类的这个方法进行重写copy也需要调用子类对象而不是父类Person
    Person *person1=[[[self class] allocWithZone:zone]init];
    person1.name=_name;
    person1.age=_age;
    return person1;
}

@end


//
// main.m
#import <Foundation/Foundation.h>
#import "Account.h"
#import "Person.h"

void test1(){
    Person *person1=[[Person alloc]init];
    NSMutableString *str1=[NSMutableString stringWithString:@"Kenshin"];
    person1.name=str1;
    //由于name定义的时候使用属性参数采用的是copy策略,而根据前面的知识我们知道NSMutableString的copy策略采用的是对象内容复制,因此如果修改str1不会改变person1.name
    [str1 appendString:@" Cui"];
    NSLog(@"%@",str1);//结果:Kenshin Cui
    NSLog(@"%@",person1.name); //结果:Kenshin

}

void test2(){
    Person *person1=[[Person alloc]init];
    person1.name=[NSMutableString stringWithString:@"Kenshin"];
    person1.age=28;
    Person *person2=[person1 copy];
    NSLog(@"%@",person1); //结果:name=Kenshin,age=0
    NSLog(@"%@",person2); //结果:name=Kenshin,age=0
    [person2.name appendString:@" Cui"]
    NSLog(@"%@",person1);//结果:name=Kenshin Cui,age=28
    NSLog(@"%@",person2);//结果:name=Kenshin Cui,age=28
}

int main(int argc,char *argv[]){
    test1();
    test2();
    return 0;
}

在上面的代码中重点说一下test2这个方法,在test2方法中我们发现当修改了person2.name属性之后person1.name也改变了,这是为什么呢?我们可以看到在Person.m中自定义实现了copy方法,同时实现了一个浅拷贝。之所以说是浅拷贝主要是因为我们的name属性参数是直接赋值完成的,同时由于name属性定义时采用的是assign参数(默认为assign),所以当通过copy创建了person2之后其实person2对象的name属性和person1指向同一个NSMutableString对象。通过图形表示如下:

MemoryStore2

上面test2的写法纯属为了让大家了解复制的原理和本质,实际开发中我们很少会遇到这种情况,首先我们一般定义name的话可能用的是NSString类型,根本也不能修改;其次我们定义字符串类型的话一般使用(copy)参数,同样可以避免这个问题(因为NSMutableString的copy是深复制)。那么如果我们非要使用NSMutabeString同时不使用属性的copy参数如何解决这个问题呢?答案就是使用深复制,将-(id)copyWithZone:(NSZone *)zone方法中person1.name=_name改为,person1.name=[_name copy];或person1.name=[_name mutablecopy]即可,这样做也正好满足我们上面对于深复制的定义。

你可能感兴趣的:(OC之Foundation框架使用)