(一)OC类的研究:
1.类的本质:
在OC中,类其实也是一个对象。这个对象是Class类型的(Class里面有*),称作类对象。而由类对象创建出来的这个
类 类型的对象叫做实例
对象。就是说,假如创建一个Car类,并且通过方法创建Car对象:Car *c =
[ [ Car alloc ] init ]
,代码实现完成后,编译器会先利用class类型
创建Car类对象,然后再用Car类对象创建Car类型的对象。
2.获取内存中得类对象:
可以通过对象方法获取内存中的类对象 class d = [ c class ];
可以通过类方法获取内存中得类对象 class d1 = [ Car class ];
3.运行思路:
OC程序运行时,会把类,并且只会把这个类对象加载到内存一次。而对象却是无数多的,现在内存中只有一个Car类对
象,类对象里面放着
Car类方法的生命。然后通过分配存储空间和初始化后,Car类对象可以拥有无数Car类型的对象,
这些Car类型的对象都拥有一个继承自
NSObject父类的isa指针,指针指向自己类型的类对象,也就是Car类对象。用于调用Car类对象的方法。
通过上述方法找到类对象后,d d1数值肯定就是相等的,因为内存中当且仅有一个类对象的。这时候d d1就代表这个类
对象,可以使用类方法 [
[ d alloc ] init ],当然,类名本身也表示类对象。
4.类的加载过程:
当整个程序加载到内存中时,会先加载父类,再加载子类。哪怕是main主函数中没有用到这个类,都要进行加载。类只会被加载一次,在类被
加载的时候,会调用一个系统的+ (void)load方法,这个方法是自动加载的。
当main函数中用到哪个类时(第一次使用这个类),这时候会进行更深层的加载,会调用一个+(void)initialize方法。
下面进行总结:
当程序启动时,会加载所有类和分类,而且加载后会调用每个分类的+load方法,只会调用一次。
当第一次使用一个类时,就会调用当前类的+initialize方法。
先加载父类,再加载子类(先调用父类的+load方法,再调用子类的+load方法)。
先初始化父类,在初始化子类(先调用父类的+initialize方法,在调用子类的+initialize方法)。
当分类和类中都有+initialize方法时候,根据规则,则会调用分类中得+initialize方法。
通过这些方法,可以进行监听。当类第一次使用时做一些操作,就可以重写+initialize方法。
5.description方法(NSObject自带):
在NSLog输出一个%@类型的对象时,会碰到两种情况,如果%@是一个NSString *类型,则输出相应的字符串。如果%@是一个对象,或者是
一个类对象,那么就会拥有一个输出规则--自动调用description。
①输出遇到类对象: 自动调用+description。
②输出遇到对象:自动调用-description。
而description是有返回值的,它的返回值是NSString *,NSLog遇到%@输出后,会调用description,拿到返回值然后输
出在屏幕上。所以,当
我们想输出整个类的所有属性时,一个一个用NSLog访问太麻烦,万一成员变量很多就大大的降
低了效率,这时候我们可以选择重写父类NSO
bject的方法description,让其return我们想要的值就可以了。
下面我们通过代码来分析一下工作原理:
Person.h
#import <Foundation/Foundation.h>
@interface Person :NSObject
@property int age;
@property NSString *name;
@end
Person.m
#import "Person.h"
@implementation Person
-(NSString *)description
{
return [NSString stringWithFormat:@"人的年龄是%d,姓名是%@",_age,_name ];
}
+(NSString *)description
{
return @"ABC";
}
@end
main();
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
Person *p = [[Person alloc]init];
p.age =100;
p.name =@"dsa";
NSLog(@"%@",p);
Class c = [Person class];
NSLog(@"%@",c);
return 0;
}
count:
2015-04-0317:41:09.939 o[1989:508218]人的年龄是100,姓名是dsa
2015-04-0317:41:09.940 o[1989:508218] ABC
由上代码可以看出,+description默认输出类对象名称。-description默认输出类名+内存地址。在重写后,他们在被NSLog调用时返回了相应的
返回值。这样就实现了一次性输出所有属性的功能。
6.打印增强
- 指针变量地址:NSLog(@"%p",&p);
- 对象的地址:NSLog(@"%p",p);
- 类名:对象地址:NSLog(@"%@",p);
- %s---__FILE__---打印文件路径(OC NSLog不允许有中文,可以使用printf)
- %d---__LINE__---打印当前行号。
7.SEL数据类型:
我们以前在调用一个方法[p test]-->称作发送消息机制。其实就是把要调用的方法封装成sel数据类型发送给类,发消息发得就是sel数据类型。
一个sel数据类型代表一个方法。每一个方法地址都会有一个sel数据类型的数据相对应。
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
Person *p = [[Person alloc] init];
[p performSelector:@selector(test2)];
return 0;
}
如上为sel的发送。调用了p的performselector方法把test2封装成sel数据类型发送给Person类,调用成功。步骤是:
①把test2包装成sel类型的数据。
②根据sel数据找到对应的方法地址。
③根据方法低值调用对应方法。
字符串包装sel:
NSString *d =@"wjds";
SEL f = NSSelectorFromString(d);
[d performSelector:f];
(二)OC内存初识:
1.内存管理
为什么要管理内存呢,因为不同的数据类型在内存中的储存方式是不一样的,一些局部变量,如基本数据类型,结构体
,枚举,指针等类型
,都会存放在内存的栈中,而对象储存在内存的堆中。OC系统中栈数据是自动回收的。它拥有自
己的代码块作用域,当代码块执行完毕,就会
从内存中清除。但是堆空间是动态分配的,是不可能会主动回收的。此时
需要程序员人工进行内存回收。任何继承了NSObject类的对象都要管
理内存
。
2引用计数器:
每一个OC对象都有引用计数器(4字节),表示被用的次数。当对象分配内存后(代码表示为alloc,new),引用计数器默认值变为1.引用计
数器用三个对象方法。
- 给对象发送retain消息,计数器+1,retain返回值返回对象本身。
- 给对象发送release消息,计数器-1,release没有返回值。
- 给对象发送retainCount消息,获取计数器值--->NSUInteger类型--->%ld-->当计数器为0时,就从内存中移除。对象刚诞生时,计数器为1。
Person *p = [[Person alloc] init];
[p retain];
[p release];
// NSUInteger c = [p retainCount];
[p release];
[p retain];
return 0;
当计数器为0时,系统自动向对象发送dealloc消息。一般会选择重写dealloc方法,在这里释放相应的资源。dealloc就是对象的遗言。
3.僵尸对象:
当对象计数器为0时,称这时候的对象为僵尸对象(不能再使用,所占用的内存已经被回收的对象)。
4.野指针:
指向不可用内存的指针叫做野指针。当然,指向僵尸对象的指针也是野指针。
5.空指针:
不指向任何对象的指针,指针=nil ,NULL,0.空指针在OC语言中是允许的,比关切给空指针发送消息并不会报错。
6机制分析:
当一个对象刚被创建出来时候(new alloc表示分配内存空间给对象。而init不算创建对象,只是初始化)。对象引用计数器+1,然后当对象发送
retain消息,计数器+1,发送release消息,计数器-1.当对象调用完毕,不需要的时候,需要手动清除,此时release对象使计数器变为0,然后
对象自动发送dealloc消息,告知已“死亡”,此时这个对象是僵尸对象,还储存在堆中,原来指向他的那个指针变成了野指针。
7.EXC_BAD_ACCESS----->100%内存出错。访问了一块坏内存(不可用,被回收的)。--->野指针常见错误。