语言前期准备

一、OC简介

OC语言在c语言的基础上,增加了一层最小的面向对象语法,完全兼容C语言,在OC代码中,可以混用c,甚至是c++代码。 可以使用OC开发mac osx平台和ios平台的应用程序。

拓展名:c语言 : .c , OC语言 : .m , 兼容C++ : .mm

注:其实c语言和oc甚至任何一门语言都只是我们为了实现一些功能,达到一些效果而采用的工具,抛开语法的差别外,我想最重要的应该是在解决问题的时候考虑的角度和方法不一样而已,然而这也构成了学习一门语言的重要性。

二、语法预览

(一)关键字

基本上所有的关键字都是以@开头的(为了与c语言的关键字区分开来),如@interface ``@implementation @public等,少部分没有以@开头,如id,_cmd等。

(二)字符串以@开头

C语言字符串:"hello"

OC语言字符串:@"hello"

(三)其他语法

基本类型:5种,增加了布尔类型 Nil 相当于是 null , 也就是0。

屏幕输出:

NSLog(@“hello”);//自动换行 NSLog(@“age is %d”,2);

三、OC程序开发过程

#import预处理指令有两个作用:

  1. #include一样,拷贝文件内容
  2. 可以自动防止文件的内容被重复拷贝

程序编译连接过程:

源文件(.m)---(编译)---->目标文件(.0)-----(链接)---->可执行文件(.out)

Foundation框架:

如果要使用框架中的所有头文件那么应该怎么办?

包含框架的主头文件。主头文件是一个框架中最主要的头文件,每个框架的主头文件名和框架名一致。

#import<foundation/foundation.h>

运行过程如下:

  1. 编写OC源文件 .m .c
  2. 编译文件 cc -c xx.m xxx.c
  3. 链接 cc xx.o xxx.o -framework Foundation
  4. 运行 ./a.out

四、类型补充

Int main()
{
  BOOL b=YES;
  BOOL b1=NO;
  BOOL b2=1;//  YES
  BOOL b3=2;//  NO
  NSLog(@“%i”,b);
}

BOOL类型与其他类型的用法一致,BOOL类型的本质是char类型的,定义如下:

Typedef signed char BOOL
//宏定义:
#define YES  (BOOL)1
#define NO   (BOOL)0

布尔类型的输出一般当做整数来用。

语言基础知识

一、面向对象

OC语言是面向对象的,C语言是面向过程的,面向对象和面向过程只是解决问题的两种思考方式,面向过程关注的是解决问题涉及的步骤,面向对象关注的是设计能够实现解决问题所需功能的类。

术语:OO面向对象、OOP面向对象编程

二、类

(一)关于类

类的设计只关注三个东西:类名、属性和方法

注意:一般名词都是类,拥有相同属性和行为的对象都可以抽象为一个类,类名是标识符的一种,需要符合规范,通常类名的第一个字母大写,且不能有下划线,如果有多个单词则使用驼峰标识。在对方法进行类的划分中,一般采取的做法是谁最熟悉这个方法那么就把这个方法划分给谁。在OC中,对象对方法的调用称为消息机制,即向既定的对象发送了什么消息。

(二)简单内存分析

类创建对象,每个对象在内存中都占据一定的存储空间,每个对象都有一份属于自己的单独的成员变量,所有的对象公用类的成员方法,方法在整个内存中只有一份,类本身在内存中占据一份存储空间,类的方法存储于此。

每个对象内部都默认有一个isa指针指向这个对象所使用的类。

[p eat];

表示给p所指向的对象发送一条eat消息,调用对象的eat方法,此时对象会顺着内部的isa指针找到存储于类中的方法,执行。

isa是对象中的隐藏指针,指向创建这个对象的类。

(三)类的声明和实现

1)类的声明:

这里声明了一个Person类,这个类拥有一个@public修饰的属性(成员变量),以及一个对象方法put

# import <Foundation/Foundation.h>

@interface Person : NSObject
{
	@public
	int _age;
}

- (void)put;

@end
2)类的实现:

类的实现可以理解为类中的方法的实现

# import "Person.h"

@implementation Person

- (void)put
  {
    NSLog(@"第一个程序!");
  }

@end
3)类的调用:

在主函数首先创建了一个Person类型的对象(先调用alloc分配存储空间,后调用init方法初始化为0),并定义了一个Person类型的指针指向创建的这个对象,之后初始化对象成员变量_age的值为20,然后调用了对象的put方法,打印输出。

# import <UIKit/UIKit.h>

# import "AppDelegate.h"

# import "Person.h"

int main(int argc, char * argv[]) {
	@autoreleasepool {

        Person *penson = [[Person alloc] init];
        penson->_age = 20;
        [penson put];
        NSLog(@"怎么那么美!");

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
	}
}

//打印结果
2016-05-28 12:04:44.640 test[20234:2570081] 第一个程序!
2016-05-28 12:04:44.641 test[20234:2570081] 怎么那么美!
4)练习,创建一个Person类。

类的声明:

# import <Foundation/Foundation.h>

@interface Person : NSObject
{
    @public
    int _age;
    int _weight;

}

- (void)walk;

@end

类的实现如下:

# import "Person.h"

@implementation Person

- (void)walk {
  NSLog(@"%d岁, %d千克的人, 走了一段路!", self->_age, self->_weight);
  }

@end

类的调用如下:

# import <UIKit/UIKit.h>

# import "AppDelegate.h"

# import "Person.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        Person *p1 = [[Person alloc] init];
        p1->_age = 10;
        p1->_weight = 50;
        [p1 walk];

        Person *p3 = p1;
        p3->_age = 11;
        [p3 walk];
        [p1 walk];

        Person *p2 = [[Person alloc] init];
        p2->_weight = 40;
        p2->_age = 40;
        [p2 walk];

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
	}
}

//打印结果
2016-05-28 12:13:40.795 test[20255:2595399] 10岁, 50千克的人, 走了一段路!
2016-05-28 12:13:40.796 test[20255:2595399] 11岁, 50千克的人, 走了一段路!
2016-05-28 12:13:40.797 test[20255:2595399] 11岁, 50千克的人, 走了一段路!
2016-05-28 12:13:40.797 test[20255:2595399] 40岁, 40千克的人, 走了一段路!

(四)常见错误

  1. @interface @end@implementation  @end 不能嵌套包含
  2. 只有类的声明没有类的实现
  3. 漏写@end
  4. 两个类的声明嵌套(可以把顺序打乱)
  5. 成员变量没有写在{}里
  6. 方法的声明写在了{}里面
  7. 在声明时对类的成员变量进行初始化,请注意成员变量不能脱离对象而独立存在
  8. 方法无法像函数那样的调用
  9. 成员变量和方法不能用static等关键字修饰,不要和c语言混淆
  10. 类的实现可以写在mian函数后面,在使用之前只要有声明就可以

三、OC对象与函数

OC对象与函数有着本质的区别:
  1. 方法的实现只能写在@implementation··@end中,对象方法的声明只能写在@interface···@end中间
  2. 对象方法都以-号开头,类方法都以+号开头
  3. 对象方法只能由对象来调用,类方法只能由类来调用,不能当做函数一样调用
  4. 函数属于整个文件,可以写在文件中的任何位置,包括@implementation··@end中,但写在@interface···@end会无法识别,函数的声明可以在main函数内部也可以在main函数外部。
  5. 对象方法归类对象所有
  6. 函数调用不依赖与对象
  7. 函数内部不能直接通过成员变量名访问对象的成员变量

四、类和方法的设计

工具类:基本没有任何的成员变量,里面的方法基本都是类方法。

注意:在对象方法中可以调用类方法。

需求:设计一个工具类,一个计算器类,要求(1)返回π,(2)计算两个整数的和,(3)计算某个整数的平方。

类的声明部分:

#import <Foundation/Foundation.h>

@interface JiSuanQi : NSObject

/// 返回π
- (double)pi;

/// 计算两个整数的和
- (int)SumWithNumber1:(int)num1 Number2:(int)num2;

/// 计算某个整数的平方
- (int)PingFang:(int)number;

@end

类的实现部分:

#import "JiSuanQi.h"

@implementation JiSuanQi
/// 返回π
- (double)pi {
    return 3.1415926;
}

/// 计算两个整数的和
- (int)SumWithNumber1:(int)num1 Number2:(int)num2 {
    return num1 + num2;
}

/// 计算某个整数的平方
- (int)PingFang:(int)number {
    return number * number;
}

@end

测试程序:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

#import "JiSuanQi.h"

int main(int argc, char * argv[]) {
	@autoreleasepool {


        JiSuanQi *jisuanqi = [[JiSuanQi alloc] init];
        NSLog(@"%f",[jisuanqi pi]);
        NSLog(@"%d",[jisuanqi SumWithNumber1:2 Number2:3]);
        NSLog(@"%d",[jisuanqi PingFang:4]);


        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

//打印结果
2016-05-28 12:37:50.365 test[20296:2661742] 3.141593
2016-05-28 12:37:50.367 test[20296:2661742] 5
2016-05-28 12:37:50.367 test[20296:2661742] 16

方法和文件编译

一、OC方法

(一)对象方法

  1. 对象方法以-开头如 -(void)xx;
  2. 对象方法只能又对象来调用
  3. 对象方法中可以访问当前对象的成员变量
  4. 调用格式 [对象名 对象方法名];
  5. 设计一个学生类和狗类,练习对象方法的使用。

学生类的声明:

#import <Foundation/Foundation.h>
#import "Dog.h"

//设计一个学生类,学生的属性包括:性别,生日,体育,最喜欢的颜色,狗(体重,毛瑟,吃)
//方法: 吃, 跑步, 遛狗, 喂狗

//定义枚举类型,学生的性别
typedef enum {
    Sexman,
    SexWoman
} Sex;

//定义结构体: 生日
typedef struct {
    int year;
    int month;
    int day;  //请注意结构体和枚举的区别
}Date;

//定义枚举类型,学生喜欢的颜色
typedef enum {
    ColorBlack,
    ColorRed,
    ColorGreen
} Color;

@interface Student : NSObject
{
@public
    Sex sex;
    Date birthday;
    int weight;
    Color favColor;
    Dog *dog;
    NSString *name;
}

- (void)eat;
- (void)run;
- (void)print;
- (void)weiDog;
- (void)liuDog;

@end

学生类的实现:

#import "Student.h"

@implementation Student

- (void)eat {
    weight += 1;
    NSLog(@"人吃完这次后的体重是:%d", weight);
}

- (void)run {
    weight -= 1;
    NSLog(@"人跑完这次后的体重是%d", weight);
}

- (void)print {
    NSLog(@"性别=%d,喜欢的颜色=%d,生日=%d-%d-%d,姓名=%@",
          sex, favColor, birthday.year, birthday.month, birthday.day, name);
}

- (void)weiDog {
    [dog eat];
}

- (void)liuDog {
    [dog run];
}
@end

狗类的声明:

#import <Foundation/Foundation.h>

@interface Dog : NSObject
{
    @public
    double weight;
}

- (void)run;
- (void)eat;

@end

狗类的实现:

#import "Dog.h"

@implementation Dog

- (void)run {
    weight -= 1;
    NSLog(@"狗跑完这次的体重的%f", weight);
}
- (void)eat {
    weight += 1;
    NSLog(@"狗吃完这次的体重是%f", weight);
}

@end

主程序:

#import "Dog.h"

@implementation Dog

- (void)run {
    weight -= 1;
    NSLog(@"狗跑完这次的体重的%f", weight);
}
- (void)eat {
    weight += 1;
    NSLog(@"狗吃完这次的体重是%f", weight);
}

@end

//打印结果
2016-05-28 16:56:46.020 test[4047:153793] 性别=0,喜欢的颜色=1,生日=2016-5-28,姓名=xiaoming
2016-05-28 16:57:15.766 test[4047:153793] 人跑完这次后的体重是49
2016-05-28 16:57:17.281 test[4047:153793] 人吃完这次后的体重是:50
2016-05-28 16:57:20.926 test[4047:153793] 狗吃完这次的体重是21.000000
2016-05-28 16:57:22.423 test[4047:153793] 够跑完这次的体重的20.000000
2016-05-28 16:57:24.820 test[4047:153793] 狗吃完这次的体重是21.000000
2016-05-28 16:57:26.641 test[4047:153793] 够跑完这次的体重的20.000000

(二)类方法

  1. 类方法以+开头  如+(void)put;
  2. 类方法只能由类来调用
  3. 类方法中不能访问实例(成员)变量,因为类方法又类来调用,并没有创建存储空间来存储类中的成员变量。
  4. 调用格式:[类名 类方法名];
  5. 类方法的好处:不依赖于对象,执行效率更高;能用类方法解决的问题,尽量使用类方法。适用场合:当方法内部不需要使用到成员变量时,可以改为类方法。
  6. 设计一个计算器类,练习使用类方法。

计算器类的声明:

#import <Foundation/Foundation.h>

@interface JiSuanQi : NSObject
// 实现一个简单的计算器工具类,实现一个类方法,计算三个整数的和.

//工具类一遍没有成员变量
+ (int)SumWithNumber1:(int)num1 Number2:(int)num2 Number3:(int)num3;

@end

计算器类的实现:

#import "JiSuanQi.h"

@implementation JiSuanQi

+ (int)SumWithNumber1:(int)num1 Number2:(int)num2 Number3:(int)num3 {
    return num1 + num2 + num3;
}

@end

主程序: 直接使用类名调用类方法

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

#import "JiSuanQi.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {

        int a = [JiSuanQi SumWithNumber1:10 Number2:20 Number3:30];
        int b = [JiSuanQi SumWithNumber1:100 Number2:200 Number3:300];

        NSLog(@"a = %d, b = %d", a, b);

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

//打印结果
2016-05-28 17:12:29.506 test[4472:200665] a = 60, b = 600
注意1:可以允许类方法和对象方法同名。 注意2:在对象方法中可以调用类方法。

(三)方法名

(1)不带参数的方法

声明:
17007346150664.jpg

调用:
17007346150678.jpg

(2)带参数的方法

声明:
17007346150687.jpg

调用:
17007346150698.jpg

注意:冒号也是方法名的一部分。

二、文件编译

在工作中,通常把不同的类放到不同的文件中,每个类的声明和实现分开,声明写在.h头文件中,实现写在相应的.m文件中去,类名是什么,文件名的前缀就是什么。

假设有两个类,分别是Person类和Dog类,则通常有下面五个文件:

  1. Person.h    Person类的声明文件
  2. Person.m    Person类的实现文件
  3. Dog.h Dog类的声明文件
  4. Dog.m Dog类的实现文件
  5. Main.m 主函数(程序入口)

在主函数以及类的实现文件中要使用#import包含相应的头文件。

补充:import有两个作用:一是和include一样,完完全全的拷贝文件的内容;二是可以自动防止文件内容的重复拷贝(即使文件被多次包含,也只拷贝一份)。

在使用命令行进行编译链接文件的时候,通常是把.m文件单文件编译,然后再把所有的目标文件链接,但是在Xcode中,是把所有的.m文件都进行编译链接的,如果出现重复定义的错误,那大部分问题根源应该就是文件内容被重复包含或者是包含.m文件所引起的。

源文件中不论是使用include还是import,都不能包含.m或者是.c文件,只能放声明。因此,在OC中通常把类拆分开来,拆分成声明和实现两个部分。

提示:这也是编程思想的一种体现,可以说.h和.m文件时完全独立的,只是为了要求有较好的可读性,才要求两个文件的文件名一致,这也是把接口和实现分离,让调用者不必去关心具体的实现细节。

Xcode是写一行编译一行,有简单的修复功能,红色是错误提示,黄色警告。如果在程序中声明了一个变量,但是这个变量没有被使用也会产生警告信息。在调试程序的时候,如果发现整个页面都没有报错,但是一运行就错误,那么一定是链接报错。

内存管理

一、基本原理

(一)为什么要进行内存管理。

由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存较多时,系统就会发出内存警告,这时需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等。

管理范围:任何继承 NSObjec t的对象,对其他的基本数据类型无效。

本质原因是因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露。

(二)对象的基本结构

每个OC对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象销毁。

在每个OC对象内部,都专门有4个字节的存储空间来存储引用计数器。

(三)引用计数器的作用

判断对象要不要回收的唯一依据就是计数器是否为0,若不为0则存在。

(四)操作

给对象发送消息,进行相应的计数器操作。

Retain消息:使计数器+1,改方法返回对象本身

Release消息:使计数器-1(并不代表释放对象)

retainCount消息:获得对象当前的引用计数器值

(五) 对象的销毁

当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收。

当对象被销毁时,系统会自动向对象发送一条dealloc消息,一般会重写dealloc方法,在这里释放相关的资源,dealloc就像是对象的“临终遗言”。一旦重写了dealloc方法就必须调用[super dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。

一旦对象被回收了,那么他所占据的存储空间就不再可用,坚持使用会导致程序崩溃(野指针错误)。

二、相关概念和使用注意

野指针错误:访问了一块坏的内存(已经被回收的,不可用的内存)。

僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用。(打开僵尸对象检测)

空指针:没有指向任何东西的指针(存储的东西是0,null,nil),给空指针发送消息不会报错

注意:不能使用[p retaion]让僵尸对象起死复生。

三、内存管理原则

(一)原则

只要还有人在使用某个对象,那么这个对象就不会被回收;

只要你想使用这个对象,那么就应该让这个对象的引用计数器+1;

当你不想使用这个对象时,应该让对象的引用计数器-1;

(二)谁创建,谁release

  1. 如果你通过alloc,new,copy来创建了一个对象,那么你就必须调用release或者autorelease方法
  2. 不是你创建的就不用你去负责

(三)谁retain,谁release

只要你调用了retain,无论这个对象时如何生成的,你都要调用release

(四)总结

有始有终,有加就应该有减。曾经让某个对象计数器加1,就应该让其在最后-1。

四、内存管理代码规范

  1. 只要调用了alloc,就必须有release(autorelease)
  2. Set方法的代码规范
(1)基本数据类型:直接复制
-(void)setAge:(int)age
{
	_age=age;
}
(2)OC对象类型
-(void)setCar:(Car *)car
{
//1.先判断是不是新传进来的对象
    If(car!=_car) {
    //2 对旧对象做一次release
    [_car release];//若没有旧对象,则没有影响
    //3.对新对象做一次retain
    _car=[car retain];
    }
}

(三)dealloc方法的代码规范

(1)一定要[super dealloc],而且要放到最后
(2)对self(当前)所拥有的的其他对象做一次release操作
-(void)dealloc
{
    [_car release];
    [super dealloc];
}

五、@property的参数

(1)内存管理相关参数

Retain:对对象release旧值,retain新值(适用于OC对象类型)

Assign:直接赋值(默认,适用于非oc对象类型,assign是指针赋值,不对引用计数操作,使用之后如果没有置为nil,可能就会产生野指针)

Copy:release旧值,copy新值

一个对象赋值给另一个对象的属性,当修改外面的对象的时候,想要里面的属性不发生改变,copy
一个对象赋值给另一个对象的属性,当修改外面的对象的时候,希望里面的属性跟着发生改变,retain

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

Readonly:只读,只会生成getter的声明和实现

Readwrite:默认的,同时生成setter和getter的声明和实现

(3)多线程管理(苹果在一定程度上屏蔽了多线程操作)

Nonatomic:高性能,一般使用这个

Atomic:低性能

(4)Set和get方法的名称

修改set和get方法的名称,主要用于布尔类型。因为返回布尔类型的方法名一般以is开头,修改名称一般用在布尔类型中的getter。

@propery(setter=setAbc,getter=isRich) BOOL rich;
BOOL b=p.isRich;// 调用

六、内存管理中的循环引用问题以及解决

案例:每个人有一张身份证,每张身份证对应一个人,不能使用`#import

的方式相互包含,这就形成了循环引用。

新的关键字:@class类名;—— 解决循环引用问题,提高性能。

@class仅仅告诉编译器,在进行编译的时候把后面的名字作为一个类来处理。
@class的作用:声明一个类,告诉编译器某个名称是一个类

  • 开发中引用一个类的规范
  • 在.h文件中使用@class来声明类
  • 在.m文件中真正要使用到的时候,使用#import来包含类中的所有东西
  • 两端循环引用的解决方法 : 一端使用retain,一端使用assign(使用assign的在dealloc中也不用再release)

七、Autorelease

(一)基本用法

  1. 会将对象放到一个自动释放池中
  2. 当自动释放池被销毁时,会对池子里的所有对象做一次release
  3. 会返回对象本身
  4. 调用完autorelease方法后,对象的计数器不受影响(销毁时影响)

(二)好处

  1. 不需要再关心对象释放的时间
  2. 不需要再关心什么时候调用release

(三)使用注意

  1. 占用内存较大的对象,不要随便使用autorelease,应该使用release来精确控制
  2. 占用内存较小的对象使用autorelease,没有太大的影响

(四)错误写法

  1. 连续调用多次autorelease,释放池销毁时执行两次release(-1吗?)
  2. Alloc之后调用了autorelease,之后又调用了release。

(五)自动释放池

  1. 在ios程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的。
  2. 当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中

(六)自动释放池的创建方式

1. ios 5.0以前的创建方式
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
[pool  release];//[pool drain];用于mac 
2. ios5.0以后
  @autoreleasepool
  {//开始代表创建自动释放池
  ·······
  }//结束代表销毁自动释放池

(七)Autorelease注意

  1. 系统自带的方法中,如果不包含alloc new copy等,则这些方法返回的对象都是autorelease的,如[NSDate date];
  2. 开发中经常会写一些类方法来快速创建一个autorelease对象,创建对象时不要直接使用类名,而是使用self

(八)Autorelease应用(面试题)

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self demo];
}

- (void)demo
{
    int largeNumber = 10;
    /**
    1. 以下代码有问题吗?
    2. 如果有,如何修改,有几种解决方法?

     所有类方法创建的对象,都是autorelease的
    
     问题所在:在循环体中,会重复为字符串开辟内存空间,如果循环次数过大,会把自动释放池撑满
    
     所有自动释放的对象,在出了作用域之后,会被自动添加到最近一次创建的自动释放池中!
    
     解决办法1:添加自动释放池,for循环之后统一释放,不会干扰主运行循环中的自动释放池!
     解决方法2:当largeNumber非常大,无法保证一次循环的正常完成! 
            解决办法,就是在每次循环中都释放一次
     */
    @autoreleasepool {
        for (int i = 0; i < largeNumber; i++) {
            NSString *str = [NSString stringWithFormat:@"Hello iOS - %d", i];
            NSLog(@"%p", str);
           
            str = [str uppercaseString];
            NSLog(@"%p", str);
           
            str = [str stringByAppendingString:@" abc"];
            NSLog(@"%p", str);
           
            NSLog(@"%@", str);
        }
    }

    // 方法2,效率不高!慎用!
    for (int i = 0; i < largeNumber; i++) {
        @autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"Hello iOS - %d", i];
            NSLog(@"%p", str);
           
            str = [str uppercaseString];
            NSLog(@"%p", str);
           
            str = [str stringByAppendingString:@" abc"];
            NSLog(@"%p", str);
           
            NSLog(@"%@", str);
        }
    }
}

八、ARC内存管理机制

(一)ARC的判断准则:

只要没有强指针指向对象,对象就会被释放。

(二)指针分类:

(1)强指针:默认的情况下,所有的指针都是强指针,关键字strong
(2)弱指针:__weak关键字修饰的指针

声明一个弱指针如下:

__weak Person *p;

ARC中,只要弱指针指向的对象不在了,就直接把弱指针做清空操作。

__weak Person *p=[[Person alloc]  init];//不合理,对象一创建出来就被释放掉,对象释放掉后,ARC把指针自动清零。

ARC中在property处不再使用retain,而是使用strong,在dealloc中不需要再[super dealloc]

@propertynonatomic,strong)Dog *dog;// 意味着生成的成员变量_dog是一个强指针,相当于以前的retain。	

如果换成是弱指针,则换成 weak,不需要加__

(三)ARC的特点总结:

(1)不允许调用release,retain,retainCount
(2)允许重写dealloc,但是不允许调用[super dealloc]
(3)@property的参数:
  1. strong:相当于原来的retain(适用于OC对象类型),成员变量是强指针
  2. weak:相当于原来的assign,(适用于OC对象类型),成员变量是弱指针
  3. assign:适用于非OC对象类型(基础类型)
  4. copy : 用于NSString/block

(四)补充

让程序兼容ARC和非ARC部分。转变为非ARC  -fno-objc-arc  转变为ARC的, -f-objc-arc 。

ARC也需要考虑循环引用问题:一端使用retain,另一端使用assign。

提示:字符串是特殊的对象,但不需要使用release手动释放,这种字符串对象默认就是autorelease的,不用额外的去管内存。