OC语言(五)

时间:2023-03-08 19:04:37

三十七.SEL类型-方法的包装


发送消息其实就是发送SEL。

每个方法都有与之对应的SEL类型数据。

第一次调用方法,先把方法包装成为SEL数据,再根据SEL去找方法地址,最后根据方法地址调用相应的方法(缓存机制提高性能)。
通过SEL调用方法:
调用无参方法Test

[p performSelector: @selector(Test)];

调用带参数的函数Test:(NSString *)str

假如Test接收一个NSString *参数,注意方法名后面那个冒号。

[p performSelector: @selector(Test:)
withObject:@"test”];

创建SEL数据

SEL s = @selector(xxx:); //冒号表示xxx方法带参数

字符串转SEL:

SEL s = NSSelectorFromString(<NSString>);

每个对象中都有一个SEL变量_cmd,指向当前方法,不能直接打印SEL,要先转成字符串

NSString *s = NSStringFromSEL(_cmd);

三十八.内存管理的概述


管理范围:任何继承了NSObject的对象

局部变量入栈,对象入堆。

作用域消失后,栈内的内容将会回收,但是堆内的对象不会自动回收。

三十九.引用计数器(唯一依据)


手动关闭ARC:
OC语言(五)
4字节的引用计数器,表示对象被引用的次数。

当一个对象的引用计数器=0应该回收。

当使用new alloc copy创建新对象,新对象的引用计数器默认为1,计数器为0就会被回收。

给对象发送一条retain消息,对象计数器+1;当给对象发送一条release消息,对象计数器-1。
给对象发送一条retainCount消息获得当前引用计数器值。

当一个对象被销毁,系统自动调用对象的dealloc方法(死之前要做的事情)。一般会重写dealloc方法释放一些资源。

NSUInterger其实是unsigned long,用%ld打印。

IOS的main函数是个死循环,一直在执行。

验证对象有没有回收:重写dealloc方法。一定要在最后调用super的dealloc。

使用release方法可以让计数器-1。

retain返回的是对象本身。

计数器为0对象变为僵尸对象,指向僵尸对象(不可用内存)的指针成为野指针。

EXC_BAD_ACCESS错误,访问了一块坏内存。

release为僵尸对象后,用 p = nil 清空指针。

给空指针发送内容不会报错,OC里面不存在空指针错误。

检测僵尸对象
OC语言(五)


四十.多对象内存管理(set)

原则:使用

类似建立房间,每有人加入房间,就retain,有一个人离开,则release

当值为0(房间中无人),则释放房间。

原则:使用对象就+1,不再使用就-1。

谁alloc,谁release。
谁retain,谁release。
有始有终,有加就有减。

写了alloc就先写好release,然后在中间写其他代码。

例如人占用书

先建立一本书,指针是一个占有者,这时候count=1,如果人占有,则count + 1,人不再占有,则count-1。
所以set方法中应该使用retain操作,而且retain操作返回的是当前对象,因此只需要一行即可。

- (void)setBook:(Book *)book{

_book = [book retain];

}

这样是不严谨的,如果人换了书,无法保证原来的对象不-1,因此做法是先把当前对象-1,然后再把新传入的+1,这样一来,如果

换了书,可以保证原来的-1,没换则不变。这里还用到的一个技巧是空对象发送消息不会报错。

- (void)setBook:(Book *)book{

[_book release];

_book = [book retain];

}

这个依然不严谨,因为有可能_book指向的已经是一个僵尸对象,这时候如果继续release僵尸对象,会出问题。

应该判断_book和传入的book是否是一个对象,如果是一个则不再release,这样既可以避免对僵尸对象操作,又可以避免-1+1这种无意义操作

还有一种可能是_book这时候计数器为1,如果这样,先release则无法再retain。

- (void)setBook:(Book *)book{

if( book!= _book){

[_book release];

_book = [book retain];

}

}

与之对应的,要有release,两种情况,第一种是换了本书,第二种是人死掉了。

- (void)dealloc{

NSLog(@"Person对象被回收");

[_book release];

[super dealloc];

}

set方法规范总结:

1.基本数据类型直接复制 _xxx = xxx;

2.OC对象

if( xxx != _xxx ){ //先判断是不是新对象传入

[_xxx release]; //旧对象release

_xxx = [xxx retain]; //新对象retain

}

dealloc规范:

对当前对象所占有的其他对象进行release。

super的dealloc一定要放到最后吗。

四十一.@property参数


使用@property来简化。

直接使用@property生成的是直接赋值的set和get方法,对于OC对象会引起内存泄露。

1.方法是使用@property,然后再覆盖set方法。

2.声明property加入内存管理代码

@property (retain) 就会生成内存管理的set方法。
例如:

@interface Person : NSObject

{

Car *_car;

int _age;

}

@property int age;

@property (retain) Car* car;

@end


注意,因为NSString也是对象,应该也这么写。

缺点是dealloc方法还是得自己写释放。

property参数分三大类:可以多个参数,但是同一类型的不能并列。

1.内存管理参数
retain:release旧值,return新值,适用于OC对象类型。
assign:直接赋值(默认),适用于非OC对象类型。
copy:release旧值,copy新值。

2.是否要生成set方法
readonly:只会生成get方法。
readwrite:默认可读写。

3.多线程管理
nonatomic:set方法不加锁(不加多线程代码,性能高)。一定要加!
atomic:默认是atomic。

4.setter和getter的名称(一般用来改写get)
默认的名称是setXxx和xxx,可以自定义
getter = xxx (不用写字符串类型,直接写)。
注意setter方法要加冒号!!!
setter = xxx:
用途一般为,例如有个BOOL变量为rich代表是否富有
@property (nonatomic,assign,readwrite, getter = isRich) Bool rich;


点语法与set和get的名称无关,会自动检测set和get方法的名称。

四十二.模型类
一般是用于将字典数据封装起来,防止键值写错。
模型类一般还会提供类的构造方法和对象的初始化方法用来接受数据生成模型。

四十三.循环引用

例如Person类拥有Card类的卡片,每个Card又对应一个人,如果二者相互引用会带来循环引用。

解决方法:使用@class <类名>

两边都用@class
Person.h:

#import <Foundation/Foundation.h>

@class Card;

@interface Person : NSObject

@property (nonatomic, retain) Card *card;

@end

Car.h:

#import <Foundation/Foundation.h>

@class Person;

@interface Card : NSObject

@property (nonatomic, retain) Person *person;

@end

缺点:没有导入类的内容,在.m中如果用到这个类注意引入.h文件。

#import "Person.h"

#import "Card.h"

@implementation Person

- (void)dealloc{

[_card release];

[super dealloc];

}

@end

#import "Card.h"

#import "Person.h"

@implementation Card

- (void)dealloc{

[_person release];

[super dealloc];

}

@end

总结:

@class是用来声明一个类,仅仅告诉编译器这是一个类名。

.h中用@class声明用到的类,.m中用#import(为了提高编译效率)

@class和#import的区别面试中可能考。

循环retain的解决方案:

一端用retain,另一端用assign。

Person *p = [[Person alloc] init];

Card *c = [[Card alloc] init];

p.card = c;

c.person = p;

[c release];

[p release];

这样c和p均不能释放。

一端assign,一段release,用assign的一端注意去掉dealloc的release。

四十四.autorelease方法


半自动释放

-autorelease返回对象本身(id类型)。

连着写:

Person *p = [[[Person alloc ] init] autorelease];

autorelease会将对象放到自动释放池中,当自动释放池被销毁时,会对池子里面的所有对象做一次release操作。

自动释放池的创建

@autoreleasepool {

Person *p = [[[Person alloc ] init] autorelease];

p.;

}

大括号结束就代表销毁池子。

autorelease使用注意:

1.释放池是可以嵌套的,池子嵌套内层池子入池子。

2.池子是个栈(LIFO),最先入栈的最后释放。

3.池子不能精确控制,因此对于占用内存较大的,和需要精确控制的(例如子弹),应该用release手动释放。

4.返回对象本身,不改变计数器。

5.连续两次调用autorelease,相当于加入了两次对象,会被release两次。

在IOS运行过程中,会创建无数个池子,都是以栈(LIFO)形式存在。

当一个对象调用autorelease方法时,会将对象放到栈顶的释放池。

自动释放的时机:如触摸时。

IOS5.O以前创建NSAutoreleasePool对象pool来创建。

直到[pool release]会销毁中间的对象。

[pool drain]也是一种销毁池子的方式。

用类方法返回autorelease对象。

+ (id)person{

return [[[Person alloc] init] autorelease];

}

NSString创建出的是默认autorelease的。

stringWithFormat方法创建的也不需要自己release。

技巧:只要方法名里面没有alloc,都是autorelease,就不需要release。方法名里有alloc但是没有autorelease就需要。

规范:设计这种autorelease方法是将类名小写然后加上WithXxx。

为了解决继承的问题,注意不要将类名写死,而是使用self。方法内部尽量使用self,这样可以满足子类要求。

+ (id)person{

return [[[self alloc] init] autorelease];

}

+ (id)personWithAge:(int)age{

Person *p = [self person];

p.age = age;

return p;

}

四十五.ARC基本原理


编译器特性,自动生成release。与JAVA的垃圾回收不同,JAVA的垃圾回收是运行时特性。

ARC里重写dealloc不准调用父类的这个方法。

ARC的判断准则:只要没有强指针指向对象,就会释放对象。

指针分为:强指针和弱指针。

默认情况下所有的指针都是强指针。 __strong(双下划线)开头为强指针

弱指针:__weak开头,如果只有它指向对象,ARC会将对象释放。(用虚线表示指向)。

只要弱指针指向的对象被释放,弱指针会被清空。会变成nil,对nil传递消息是不会包错的。

注意不要让弱指针指向新创建的对象,这样一创建就会销毁,指针也会变为空指针。

ARC里面的property参数不用retain,而是使用strong代替retain,用weak代替assign。

strong的意义:假设有Person和Dog对象
Person指向Dog,如果Person是以强指针的方式指向Dog,这样如果指向Dog的指针清空,则还有Person指向Dog,可以保证Dog不会被清空。

旧项目转化为ARC项目。
OC语言(五)


注意事项:有些项目用到了第三方框架,不支持ARC。
ARC与非ARC共存。
给非ARC文件加上编译标记:
OC语言(五)

反过来,加ARC的编译标记 -f-objc-arc

ARC循环引用:
一端用strong,一端用weak。