SEL
SEL的概念
在Objective-C中,SEL是选择器(selector)的一个类型。选择器就是指向方法的一个指针。
可以简单理解成:SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据。去找对应的方法地址。找到方法地址就可以调用方法。
//Person类中有 +test1方法和 -test2方法
Person *p = [Person new];
//调用对象方法 -test2 方法1
[p test2]
//方法2:利用sel间接调用
[p performSelector:@selector(test2)];
若调用的方法需要传参数
[p test3:@"chenfanfang"];
//或者用sel方法
[p performSelector:@selector(test3:)withObject:@"chenfanfang"];
//注意:方法若有参数,则写方法名不能漏了冒号:
创建SEL类型的数据
SEL s = @selector(test3);
根据字符串调用方法
//背景:已知有一个方法名叫 test2,根据方法名调用该方法
NSString *name = @"test2";
//将字符串转成SEL
SEL s = NSSelectorFromString(name);
//根据SEL调用方法
[p performSelector:s];
SEL总结
方法的存储位置:
- 每个类的方法列表都存储在类对象中
- 每个方法都有一个与之对应的SEL类型的对象
- 根据一个SEL对象就可以找到方法的地址,进而调用方法
- SEL类型的定义
typedef struct objc selector *SEL
SEL对象的创建
SEL s = @selector(test);
SEL s2 = NSSelectorFromString(@"test");
SEL对象的其他用法
//将SEL对象转为NSString对象
NSString *str = NSStringFromSelector(@selector(test));
Person *p = [Person new];
//调用对象的test方法
[p performSelector:@selector(test)];
SEL的变量 _cmd
每个方法里面都有 _cmd这个SEL变量,指向当前方法(即代表当前方法)
- (void)print{
NSString *str = NSStringFromSelector(_cmd);
NSLog(@"%@",str);
}
调用方法
Person *p = [Person new];
[p print];
输出结果:
2015-03-23 21:47:53.400 oc知识点[6369:72252] print
_cmd使用注意死循环
注意:不能在方法中使用如下代码
[self performSelector:_cmd];
//会调用当前方法,从而产生死循环
内存管理(引用计数器、野指针、@property参数、循环retain、autorelease)
内存管理的范围
- 管理任何继承了NSObject的对象。
- 对其他基本数据类型(int ,char,float,double,struct,enum等)无效 。
对象的基本结构
- 每个oc对象都有自己的引用计数器,是一个整数,表示对象被引用的次数,即有多少人正在使用这个oc对象
- 应用计数器:4字节
每个oc对象内部专门有4个字节的存储空间来存储引用计数器
引用计数器的作用
- 当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1
- 当一个对象的引用计数器值为0时,对象占用的内存就会被回收。换句话说:如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出。
引用计数器的操作
- 给对象发一条retain消息,可以使引用计数器值+1。retain方法返回对象本身。
- 给对象发送一条release消息,可以使引用计数器值-1。
- 可以给对象发送retainCount消息获得当前的引用计数器值。
Person *p = [[Person alloc] init];
//1、retain
[p retain]; //retain方法返回的是对象本身
//2、release
[p release];
//3、retainCount
NSInteger c = [p retainCount];
对象的销毁
- 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收
- 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
- 一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
- 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在代码的最后面调用
- 不要直接调用dealloc方法
- 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
野指针概念
指向僵尸对象(指向不可用内存)的指针。
[p release];//若此刻计数器为0,对象p就被回收了,则下面的代码会报错
[p retain];//发生野指针错误
预防野指针错误
Person *p = [Person new];
p = nil; //指针为空指针,则下面运行没意义,不会报错
[p release];
[p release];
[p release];
oc中不存在空指针错误(即给空指针发送消息,不报错)
内存管理代码规范:
- 只要调用了alloc,必须有release (autorelease)
- set方法代码规范
//情况1:基本数据类型,直接赋值
1. (void)setAge:(int)age
{
_age = age;
}
//情况2:oc对象类型
2. (void)setCar:(Car *)car
{
//1.先判断是不是新传进来的的对象
if(car != _car){
[_car release];
_car = [car retain];
}
}
- dealloc方法代码规范
一定要调用[super dealloc],而且放到最后面
对self(当前对象)拥有的其他对象做一次release
- (void)dealloc
{
[_car release];
[super dealloc];
}
@property参数(retain)与内存管理
- 普通的@property Book *book ;只是简单的赋值,若想用@property给set方法赋值并且计数,只需加一个参数,如下:
@property (retain) Book *book;
//retain:在生成的set方法里面,release旧值,retain新值。
- 注意:@property只影响set和get方法。- (void) dealloc 函数里关于内存管理的代码需要自己写。
@property里的参数
1、set方法内存管理相关的参数
- retain :release旧值,retain新值(适用oc对象类型)
- assign :直接赋值(默认),(适用于非oc对象类型)
- copy :release旧值,copy新值
2、是否要生成set方法
- readwrite :同时生成setter和getter的申明、实现
- readonly :只会生成getter的申明、实现
3、多线程管理
- nonatomic :性能高(一般用这个)
- atomic :性能低(默认)
4、setter和getter方法的名称
@property (getter=方法名1,setter=方法名2:) int weitht;
//注:setter方法需要传入参数,故方法名2后面不能漏了冒号:
setter和getter方法的名称的参数一般用于Bool类型的getter方法
例如:
@property (getter=isRich) BOOL rich;
//返回BOOL类型的方法名一般用is开头
循环retain(两端循环引用解决方案)
- 一端用retain。一端用assign
autorelease
int main(int argc, const char * argv[]) {
@autoreleasepool {
// { 开始代表创建了释放池
//autorelease方法会返回对象本身
//当自动释放池被销毁时,会把池子里面的所有对象做一次release操作
//调用完autorelease方法后,对象的计数器不变
Person * p = [[[Person alloc] init] autorelease];
}
}
autorelease的好处
- 不用再关心对象释放的时间,即不再关心什么时候调用release
autorelease的使用注意
- 占用内存较大的对象不要随便使用autorelease(需要精确控制内存的释放时间)。
- 占用内存较小的对象使用autorelease。
autorelease常见错误
- alloc之后调用了 autorelease,之后又调用了release
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
[p release];
}
- 连续调用多次autorelease
例如:
@autoreleasepool {
Person *p = [[[[Person alloc] init] autorelease] autorelease];
}
自动释放池
- 在ios程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)
- 当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池
autorelease可以写在类方法中
背景:
Person *p = [[[Person alloc] init] autorelease];
//这行代码太长,我们常在类中写一个类方法
//在Person类中,将autorelease写在类方法person中
+ (id)person
{
return [[[Person alloc] init] autorelease];
//最好把Person写成self
}
//在main中创建Person对象
Person *p = [Person person];
//若需要参数,可写成
+ (id) personWithAge:(int)age
{
Person *p = [[[Person alloc] init] autorelease];
p.age = age;
return p;
}
//在main中创建Person对象
Person *p = [Person personWithAge:100];
有些对象默认是autorelease的
- 系统自带的方法里面的方法名没有包含 alloc、new、copy等字眼,说明返回的对象都是autorelease的
例如下面的都不用调用release
NSString *str = @"chenfanfang";
NSString *str2 = [NSString stringwithFormat:@"age is %d",23];
开发中经常会提供一些方法(可以自己写的方法),快速创建一个已经autorelease过的对象。
如下:
//注意:创建对象不要直接用类名,一般用self
+ (id)person
{
return [[[self alloc] init] autorelease];
//最好不用 return [[[Person alloc] init] autorelease];
}