黑马程序员--Objective-C语言基础知识--构造方法和内存管理

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

构造方法

构造方法是用来初始化对象的方法,它是个对象方法,以“-”号开头。完整的创建一个可用的对象首先分配存储空间给对象此时调用类方法+alloc,之后再调用构造方法-init进行初始化。
完整的创建一个可用对象的写法:

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

默认情况下构造方法初始化成员变量的值为0,如果要让对象创建出来成员变量就会有特定的值就要重写构造方法。
重写构造方法要先调用父类的构造方法:[super init],再进行子类内部成员变量的初始化。这里的super关键字指当前调用者的父类或者父类对象,self关键字指当前的调用者。
示例1:

#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age;
@property NSString *name;
@end
@implementation Person
- (Person *)initWithAge:(int)age andName:(NSString *)name/*重写构造方法设置两个参数传给对象相应的成员变量,这样我们就能按照自己的想法初始化对象,当然也可以不设置参数直接在方法内部设置成员变量的值使得每一个对象创建出来都有一样的初值*/
{
    if (self = [super init])/*先将父类对象初始化,继承自父类的成员变量交给父类初始化,这里没有重写父类的构造方法,父类初始化之后赋给当前的对象完成初步的初始化*/
    {
        _age = age;//子类初始化自己的成员变量,将参数的值赋给成员变量
        _name = name;
    }
    return self;//初始化完成,将初始化后的对象(当前调用者自己)返回给调用者
}
- (NSString *)description
{
    //为了体现出我们初始化成功后对象的样子,重写description方法输出对象的属性值
    return [NSString stringWithFormat:@"当前对象的属性是:年龄%d,姓名%@", self.age, self.name];
}
@end
int main()
{
    Person *per = [[Person alloc] initWithAge:36 andName:@"LiYi"];//初始化对象
    NSLog(@"%@", per);//输出初始化之后的对象
    return 0;
}

示例1的运行结果是:

2015-07-19 20:56:36.486 a.out[1715:513018] 当前对象的属性是:年龄36,姓名LiYi

内存管理的基本原理

移动设备的内存极其有限,每个app所能占用的内存是有限制的。当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等等。内存管理就是管理这些存放在堆中的oc对象。
一、
什么是引用计数器:
每个oc对象都有自己的引用计数器,它是一个整数,表示这个对象被引用的次数,既有多少“人”正在使用这个oc对象。oc对象内部专门有4个字节的存储空间来存储引用计数器。
二、
引用计数器的作用:
1. 当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1;
2. 当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数器不为0,那么在整个程序运行过程中,它占用的内存就不可能被回收,除非整个程序已经退出。
三、
引用计数器的操作:
1. 给对象发送一条retain消息,可以使引用计数器+1(retain方法返回对象本身);
2. 给对象发送一条release消息,可以使引用计数器值-1;
3. 给对象发送retainCount消息获得当前的引用计数器值。
四、
对象的销毁:
1. 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收;
2. 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息;
3. 一般会重写dealloc方法,在这里面释放相关资源(该对象引用的其它对象的引用计数器减1)。重写了dealloc方法,就得在最后调用[super dealloc]。dealloc方法通常不会直接调用。
4. 对象被回收之后它所占用的内存就不可再用,坚持使用会导致程序崩溃。
四、
内存管理当中还有几个概念。
1. 僵尸对象:所占用内存已经被回收的对象,僵尸对象不能再使用;
2. 野指针:指向僵尸对象的指针,给野指针发送消息会报错;
3. 空指针:没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错。
示例2:

#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age;
@property NSString *name;
- (void)run;
@end
@implementation Person
- (Person *)initWithAge:(int)age andName:(NSString *)name
{
    if (self = [super init])
    {
        _age = age;
        _name = name;
    }
    return self;
}
- (NSString *)description
{
    return [NSString stringWithFormat:@"当前对象的属性是:年龄%d,姓名%@", self.age, self.name];
}
- (void)run
{
    NSLog(@"跑起来");
}
- (void)dealloc//重写dealloc方法
{
    NSLog(@"对象被回收");
    [super dealloc];
}
@end
int main()
{
    Person *per1 = [[Person alloc] initWithAge:36 andName:@"LiYi"];//创建并初始化一个对象,这时引用计数器默认是1
    int num1 = [per1 retainCount];//获得此时引用计数器的值
    id obj = per1;//定义一个万能指针指向我们创建的对象
    [per1 retain];//有新的指针指向对象,引用计数器加1
    int num2 = [per1 retainCount];//获得此时引用计数器的值
    [per1 release];//release操作放在最后面,我们不用这个对象的时候就要将它回收,这一次的release对应alloc
    [per1 release];//这次的release对应retain
    per1 = nil;//对象已经被回收,那么指针再指向那块内存就变成野指针了,所以让指针清0
    [per1 run];//给空指针发送一条消息是不会报错的,如果指针没有清0发送消息会报野指针错误
    return 0;
}

示例2展示了如何人工进行内存管理,其实oc语言的内存管理就是回收不再使用的对象。当对象被创建出来了(alloc、new、copy)那么最后要release一次,中间如果有别的指针指向它要retain一次,retain了一次之后最后要不忘在加一次release操作。
示例2的运行结果是:

2015-07-19 22:01:06.161 a.out[1797:548763] num1 = 1,num2 = 2
2015-07-19 22:01:06.162 a.out[1797:548763] 对象被回收

五、
内存管理原则:
1. 你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作);
2. 如果不想再使用(占用)某个对象,就应该让对象的计数器-1(让对象做一次release操作);

set方法的内存管理

如果某个对象有个oc对象类型的成员变量,就必须管理这个成员变量的内存。

- (void)setBook:(Book *)book
{
        if(book != _book)
        {
           [_book release];
           _book = [book retain];
        }
}

以上是在set方法中管理成员变量的规范代码。首先我们判断放进来的对象是不是原有的旧值,如果是旧值就不用再做引用计数器的操作了,如果不是旧值则我们先对以前的旧制release一次因为我们的对象已经不用它了它被少引用一次,然后对新值进行一次retain操作因为我们的对象引用了它一次。

- (void)dealloc
{
   [_book release];
   [super dealloc];
}

以上是在dealloc方法里面管理成员变量内存的代码规范。当我们的对象不用被销毁时,那么它之前所引用的成员变量对象也会少一个引用者故此时这个成员变量对象要进行一次release操作。
示例3:

#import <Foundation/Foundation.h>
@interface Book : NSObject
@end
@implementation Book
@end
@interface Person : NSObject
{
    Book *_book;
}
@property int age;
@property NSString *name;
- (void)setBook:(Book *)book;
- (Book *)book;
@end
@implementation Person
- (void)setBook:(Book *)book
{
    if (_book != book)
    {
        [_book release];
        _book = [book retain];
    }
}
- (Book *)book
{
    return _book;
}
- (void)dealloc
{
    [_book release];
    [super dealloc];
}
@end
int main()
{
    Person *per = [[Person alloc] init];
    Book *book = [[Book alloc] init];
    per.book = book;
    [per release];
    [book release];
    return 0;
}

示例3就是一个管理成员变量对象内存的实例。

@property参数

我们知道利用@property可以直接生成set和get方法,通过在@property后面添加一些参数可以控制set和get方法。
多线程管理:
atomic:这个关键字意味着性能较低,默认情况下就是它;
nonatomic:这个关键字性能较高,我们通常使用这个参数。
控制set方法的内存管理:
retain:在set方法中自动生成管理成员变量内存的代码,release旧值,retain新值。这个参数用于oc对象成员变量;
assign:直接赋值,不做任何内存管理,默认情况下就是它,用于非oc对象的管理。
控制需不需要生成set方法:
readwrite:同时生成set方法和get方法,默认就是它;
readonly:只会生成get方法。
控制set方法和get方法的名称:
setter:设置set方法的名称,一定要有个冒号“:”;
getter:设置get方法的名称。
示例4:

#import <Foundation/Foundation.h>
@interface Book : NSObject
@end
@implementation Book
@end
@interface Person : NSObject
@property(nonatomic, readonly)int age;
@property(nonatomic, retain)NSString *name;
@property(nonatomic, retain)Book *book;
@property(getter = isRich)BOOL rich;
@end
@implementation Person
- (void)dealloc
{
    [_book release];
    [super dealloc];
}
@end
int main()
{
    Person *per = [[Person alloc] init];
    Book *book = [[Book alloc] init];
    per.book = book;
    per.rich = YES;
    BOOL is = per.isRich;
    NSLog(@"%d", is);
    [per release];
    [book release];
    return 0;
}

示例4利用@property的各种参数对成员变量的set和get方法进行了各种操作,我们给其中一个成员变量的get方法改了名。示例4的运行结果是:2015-07-20 09:50:23.655 a.out[441:22302] 1

循环引用

循环引用就是“我”引用“你”,“你”引用“我”。两个类互相引用对方做自己的成员变量。

1.
#import "B.h"
@interface A : NSObject
{
    B *b;
}
@end
2.
#import "A.h"
@interface B : NSObject
{
    A *a;
}
@end

以上就是两个类互相引用的情况,这样编译器会报错。
如果我们在.h文件中使用@class 类名; 就可以引用一个类。它的作用是说明跟在它后面的标识符是个类。在.m文件中当我们真的用到这个类的时候再把这个类的声明导入进来。

@class B;
@interface A : NSObject
{
    B *b;
}
@end

这样写就可以避免重复拷贝的错误。
按照我们之前所学在自动生成的set方法中会release旧值retain新值,但是如果两个对象互相引用对方做自己的成员变量就会导致对象的内存该释放时释放不掉。
这种情况下我们让一端用retain,另一端用assign(直接赋值不retain一次新值)。这样两个对象的内存就都可以释放掉了。
示例5:

#import <Foundation/Foundation.h>
@class B;
@interface A : NSObject
@property(nonatomic,retain)B *b;
@end
@implementation A
- (void)dealloc
{
    [_b release];
    [super dealloc];
    NSLog(@"A被释放");
}
@end
@interface B : NSObject
@property(nonatomic, assign)A *a;
@end
@implementation B
- (void)dealloc
{
    [super dealloc];
    NSLog(@"B被释放");
}
@end
int main()
{
    A *a = [[A alloc] init];
    B *b = [[B alloc] init];
    a.b = b;
    b.a = a;
    [a release];
    [b release];
    return 0;
}

示例5就是循环引用的一个示例,它的运行结果是:

2015-07-20 10:26:52.559 a.out[496:40026] A被释放
2015-07-20 10:26:52.561 a.out[496:40026] B被释放

autorelease自动释放池

autorelease自动释放池会对放入里面的对象内存进行管理。
它的基本用法是:
1. 给某对象发送一条autorelease消息,那么这个对象就会加到一个自动释放池中;
2. 当自动释放池销毁时,会给池子里面的所有对象发送一条release消息;
3. 调用autorelease方法时并不会改变对象的计数器,会返回对象本身;
4. autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用release一次。

    @autoreleasepool
    {
        Person *p = [[[Person alloc] init] autorelease];
    }

以上就是自动释放池的创建以及如何把对象放入自动释放池中。
示例6:

#import <Foundation/Foundation.h>
@class B;
@interface A : NSObject
@property(nonatomic,retain)B *b;
@end
@implementation A
- (void)dealloc
{
    [_b release];
    [super dealloc];
    NSLog(@"A被释放");
}
@end
@interface B : NSObject
@property(nonatomic, assign)A *a;
@end
@implementation B
- (void)dealloc
{
    [super dealloc];
    NSLog(@"B被释放");
}
@end
int main()
{
    @autoreleasepool {
        A *a = [[[A alloc] init] autorelease];
        B *b = [[[B alloc] init] autorelease];
        a.b = b;
        b.a = a;
    }//自动释放池销毁
    return 0;
}

示例6的运行结果是:

2015-07-20 10:44:13.178 a.out[522:51336] B被释放
2015-07-20 10:44:13.179 a.out[522:51336] A被释放

我们看到后进入池子的b先被释放,这说明先对b做了一次release操作。放入自动释放池中的对象先进后release,后进先release。

ARC机制

ARC(Automatic Reference Counting)是iOS 5之后增加的新特性,完全消除了手动管理内存的繁琐,编译器会自动在适当的地方插入适当的retain、release、autorelease语句。你不再需要担心内存管理,因为编译器为你处理了一切。
ARC是编译器特性,不是运行时特性,它也不是类似于其它语言中的垃圾收集器,ARC和手动内存管理性能是一样的,有时还能更加快速,因为编译器还可以执行某些优化。
ARC的规则:只要有一个强指针指向对象,对象就会保存在内存中。
strong:给@property添加参数strong说明这个成员变量对象被强指针指,相当于之前所学的retain,默认情况下对象是被强指针指的;
weak:给@property添加参数weak说明这个成员变量对象被弱指针指,相当于我们之前所学的assign。
示例7:

#import <Foundation/Foundation.h>
@class B;
@interface A : NSObject
@property(nonatomic,weak)B *b;
@end
@implementation A
- (void)dealloc
{

    NSLog(@"A被释放");
}
@end
@interface B : NSObject
@property(nonatomic, strong)A *a;
@end
@implementation B
- (void)dealloc
{

    NSLog(@"B被释放");
}
@end
int main()
{
    A *a = [[A alloc] init];
    B *b = [[B alloc] init];
    a.b = b;
    b.a =a;
    return 0;
}

示例7使用了ARC机制,两个互相引用的对象先后被销毁。原先需要我们手动添加的代码releae、retain等都不再需要自己添加。
示例7的运行结果是:

2015-07-20 11:17:12.466 0[637:71993] B被释放
2015-07-20 11:17:12.467 0[637:71993] A被释放
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值