内存管理(3)

10、【掌握】@property参数(一)

  • @property 4.4前

    • 1) @property + 手动实现
    • 2) @property int age; + @synthesize age;//get和set方法的声明和实现都帮我们做了
    • 3) @property int age + @synthesizes age = _b;
  • @property 4.4增强 .h

  • @property int age;
    • 1) 生成_age
    • 2) 生成_age的get和set方法的声明
    • 3) 实现_age的get和set方法

1.@property参数

  • 格式:@property (参数1,参数2) 数据类型 方法名
    内存管理(3)_第1张图片
// 基本数据类型
int _age;

// set方法的写法
-(void)setAge:(int) age
{
_age = age;
}

// 用assign修饰后,仍旧会生成以上标准的set方法
@property (assign) int age;


// oc对象类型
@property (retain) Car *car ;

// 用retain修饰后,生成如下内存管理代码
-(void)setCar:(int) car
{
   if(_car ! = car)
   {
      [_car release];
      _car = [car retain];
  }
}
  • 1.内存管理相关参数

    • retain : release旧值,retain新值(用于OC对象)
    • assign : 直接赋值,不做任何内存管理(默认,用于非OC对象类型)
    • copy : release旧值,copy新值(一般用于NSString *)
  • 验证assign如果作用在对象上,实际上就是直接赋值

//使用@property增强型 生成get和set方法
@property(nonatomic,assign)Car *car;

//.m文件中实际上生成的是
- (void)setCar:(Car *)cat
{
   _car = car; //当对象release后,将无法使用该对象
}


// main函数
int main()
{
 Person *p = [Person new];
 Car *c = [Car new];

 // 给人一辆车
 p.car = c;

 // 释放车
 [c release];

 // 让人开车
 [p drive]; //此时p指向了僵尸对象
}
  • 使用@property增强型 生成get和set方法
//使用@property增强型 生成get和set方法
@property(nonatomic,retain)Car *car;

//.m文件中实际上生成的是
- (void)setCar:(Car *)cat
{
   if(_car != car)
   {
     [_car release];
     _car = [car retain];
   }
}


// main函数
int main()
{
 Person *p = [Person new];
 Car *c = [Car new];

 // 给人一辆车
 p.car = c;

 // 释放车
 [c release];

 // 让人开车
 [p drive];
}
  • 此时,会发生内存泄露。
  • 解决办法
- (void)dealloc
{
    NSLog(@"Person -- dealloc");
    [_car release];
    [super dealloc];
}

11.【掌握】@property参数(二)

1.@property 参数(二)

  • 1、是否要生成set方法(若为只读属性,则不生成)

    • readonly:只读,只会生成get的声明和实现
    • readwrite:默认的,同时生成set和get的声明和实现
  • 2.多线程管理(苹果在一定程度上屏蔽了多线程操作)

    • nonatomic:高性能,一般使用这个
    • atomic:低性能,默认

    • atomic是Objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。

  • 3.set和get方法的名称

    • 修改set和get方法的名称,主要用于布尔类型。因为返回布尔类型的方法名一般以is开头,修改 名称一般用在布尔类型中的getter。
    控制set方法和get方法的名称
    setter : 设置set方法的名称,一定有个冒号:
    getter : 设置get方法的名称
    
    @property(nonatomic,assign, setter=abc:,getter=haha)int age
    
  • 可以理解为:

    • [p setAge: ]——> [p abc:],
    • [p age] ———> [p haha];
    • p.age 不会报错(内部优化)

    “`objc
    @property(nonatomic,assign, setter=setVip:,getter=isVip) BOOL vip;

![](http://img.blog.csdn.net/20161009213635575)
* 用法: 设置值
![](http://img.blog.csdn.net/20161009213720825)
* 获取值
![](http://img.blog.csdn.net/20161009213749107)
##12、【理解】应用:电商App练习
####电商App类的设计
* 要求:利用OC+面向对象设计下面的三个类:
* 一、商品类-Goods
 * 属性: 商品名称
    * 单价
    * 重量
    * 商品展示图片
    * 生产日期(暂时用结构体表示)produceDate
    * 过期日期 expireDate
* 二、买家类(用户) Buyer
 * 属性:
   * 姓名
   * 性别(枚举)
   * 年龄
   * 身高(单位:cm)
* 三、卖家类 - Seller
 * 属性:
   * 姓名
   * 性别(枚举)
   * 年龄
   * 身高(单位:cm)
   * 所出售商品(假设一个卖家就卖一件商品)
* 四、买家类、卖家类(抽象父类Person)

* 五、在main函数中创建卖家、商品、买家类的对象。

```objc
// 创建一个表示日期时间的结构体
typedef struct
{
  int hour;
  int min;
  int sec;
}Time;

//表示日期
typedef struct{
  int year;
  int month;
  int day;
  Time time;
 }MyDate;


MyDate produceDate = (MyDate){2011, 9, 10, {15, 16, 30}}; //注意,这句话是错误的




"se-preview-section-delimiter">

  • 原因是:
    • goods.expireDate—–>得到这个成员expireDate的值,是结构体类型
    • expireDate.Time 这是访问结构体变量,time的值
    • goods.expireDate.time 会被误认为是点语法,导致出错
// 创建一个表示性别的枚举
typedef enum
{
  GenderMale, // 男
  GenderFemale // 女
} Gender;
![](image1/12.1.png)

* 原因是:
 * goods.expireDate----->得到这个成员expireDate的值,是结构体类型
 * expireDate.Time 这是访问结构体变量,time的值
 * goods.expireDate.time 会被误认为是点语法,导致出错

```objc
// 创建一个表示性别的枚举
typedef enum
{
  GenderMale, // 男
  GenderFemale // 女
} Gender;




<div class="se-preview-section-delimiter">div>

13、【理解】@class的使用

  • 场景

1.@class的使用

  • 作用
    • 可以简单地引用一个类
  • 简单使用
    • @class Dog; //类的引入
    • 仅仅是告诉编译器:Dog是一个类;并不会包含Dog这个类的所有内容
  • 具体使用

    • 在.h文件中使用@class引用一个类
    • 在.m文件中使用#import包含这个类的.h文件
  • 如下面代码:

  A.h文件
 #import "B.h"

 @interface A : NSObject
  {
     B *b;
  }
  @end
  • 为了简单起见:A类是引用类,B类是被引用类,这里先不考虑A类的实现文件。
  • 通常引用一个类有两种办法:

    • 一种是通过#import方式引入;
    • 另一种是通过@class引入;
  • 这两种的方式的区别在于:

    • 1)#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在 A.h文件中 B *b只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息;

    • 2)使用@class方式由于只需要知道被引用类(B类)的名称就可以了,而在实现类由于要用到被引用类中的实体变量和方法,所以需要使用#import来包含被引用类的头文件;

    • 3)通过上面2点也很容易知道在编译效率上,如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt(A->B, B->C,C->D…),一旦最开始的头文件稍有改动,后面引用到这个文件的所有类 都需要重新编译一遍,这样的效率也是可想而知的.而相对来讲,使用@class方式就不会出现这种问题了;

    • 所以:我们实际开发中尽量在.h头文件中使用@class

    • 4)对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类,B类的代码:
  • 当程序运行时,编译会报错,当使用@class在两个类相互声明,就不会出现编译报错。


* 把其中的一个头文件中的import换成@class


* 面试题:#import和@class的区别。
* 作用上的区别
* import会包含引用类的所有信息(内容),包括引用类的变量和方法 @class仅仅是告诉编译器有这么一个类,具体这个类里有什么信息,完全不知道。
* 效率上的区别
* 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#import,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,编译效率非常低相对来讲,使用@class方式就不会出现这种问题了。

##13、【理解】@class的使用
* 场景
![](http://img.blog.csdn.net/20161009214217550)
####1.@class的使用
* 作用
 * 可以简单地引用一个类
* 简单使用
 * @class Dog; //类的引入
 * 仅仅是告诉编译器:Dog是一个类;并不会包含Dog这个类的所有内容
* 具体使用
 * 在.h文件中使用@class引用一个类
 * 在.m文件中使用#import包含这个类的.h文件

* 如下面代码:
```objc
  A.h文件
 #import "B.h"

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




class="se-preview-section-delimiter">
  • 为了简单起见:A类是引用类,B类是被引用类,这里先不考虑A类的实现文件。
  • 通常引用一个类有两种办法:

    • 一种是通过#import方式引入;
    • 另一种是通过@class引入;
  • 这两种的方式的区别在于:

    • 1)#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在 A.h文件中 B *b只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息;

    • 2)使用@class方式由于只需要知道被引用类(B类)的名称就可以了,而在实现类由于要用到被引用类中的实体变量和方法,所以需要使用#import来包含被引用类的头文件;

    • 3)通过上面2点也很容易知道在编译效率上,如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt(A->B, B->C,C->D…),一旦最开始的头文件稍有改动,后面引用到这个文件的所有类 都需要重新编译一遍,这样的效率也是可想而知的.而相对来讲,使用@class方式就不会出现这种问题了;

    • 所以:我们实际开发中尽量在.h头文件中使用@class

    • 4)对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类,B类的代码:
      内存管理(3)_第2张图片
  • 当程序运行时,编译会报错,当使用@class在两个类相互声明,就不会出现编译报错。
    内存管理(3)_第3张图片
    内存管理(3)_第4张图片
  • 把其中的一个头文件中的import换成@class
    内存管理(3)_第5张图片
  • 面试题:#import和@class的区别。
    • 作用上的区别
    • import会包含引用类的所有信息(内容),包括引用类的变量和方法 @class仅仅是告诉编译器有这么一个类,具体这个类里有什么信息,完全不知道。
    • 效率上的区别
    • 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#import,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,编译效率非常低相对来讲,使用@class方式就不会出现这种问题了。

14、【理解】循环retain问题

循环retain的使用

  • 问题:人有一只狗,狗有一个主人。
class "Dog.h";
@interface Person :NSObject
@property(nonatomic,retain)Dog *dog;
@end

@class "Person.h";
@interface Dog:NSObject
@property(nonatomic,retain)Person *owner;
@end

//main.m

//创建对象
Dog *d = [Dog new];
Person *p = [Person new];

//循环引用
p.dog = d;
d.owner = p;

//看似正确的释放代码
[d release];
[p release];




class="se-preview-section-delimiter">
  • 程序执行结果: p和d都没有被释放掉

  • 原理分析:
    14.1

p.dog  = d; //因为dog的set方法中是 进行了 [dog retain];dog2
d.owner = p;//会让p的引用计数+1  owner2

当执行了
[d release];
[p release];

dog和owner 的引用计数变成1
  • 如图:
    14.1

  • 循环retain的场景

    • 比如A对象retain了B对象,B对象retain了A对象
    • 循环retain的弊端
    • 这样会导致A对象和B对象永远无法释放
  • 循环retain的解决方案

    • 当两端互相引用时,应该一端用retain、一端用assign
* 程序执行结果: p和d都没有被释放掉

* 原理分析:
![](http://img.blog.csdn.net/20161009215834728)
```objc
p.dog  = d; //因为dog的set方法中是 进行了 [dog retain];dog2
d.owner = p;//会让p的引用计数+1  owner2

当执行了
[d release];
[p release];

dog和owner 的引用计数变成1
  • 如图:
    内存管理(3)_第6张图片
  • 循环retain的场景

    • 比如A对象retain了B对象,B对象retain了A对象
    • 循环retain的弊端
    • 这样会导致A对象和B对象永远无法释放
  • 循环retain的解决方案

    • 当两端互相引用时,应该一端用retain、一端用assign

你可能感兴趣的:(OC)