Day11.oc的入门
// Foundation.h我们称之为主头文件, 主头文件中又拷贝了该工具箱中所有工具的头文件, 我们只需要导入主头文件就可以使用该工具箱中所有的工具, 避免了每次使用都要导入一个对应的头文件
// 工具箱的地址: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks
/*
因为OC完全兼容C, 所以可以在OC程序中编写C语言代码
并且可以将C语言的源文件和OC的源文件组合在一起生成可执行文件
*/
1,第一个类
// 1.如何编写类的声明
// 以@interface开头 , 以@end结尾, 然后再class name对应的地方写上 事物名称, 也就是类名即可
// 注意: 类名的首字符必须大写
// 声明一个类的目的就是为了告诉系统, 我们这个类中有哪些属性和行为
// OC类声明中属性只能在写@interface和@end之间的{}中
// 注意: 编写OC类属性的时, 建议将所有属性的名称前面都加上_
// 类名后面的 :NSObject 是为了让我们的Iphone类具备创建对象的能力
@interface Iphone : NSObject
{
// 注意: 默认情况下, OC对象中的属性是不能直接访问的
@public // 只要让类中的属性公开, 以后就可以直接通过一个指向结构体的指针来操作对象中的属性
float _model; // 型号 0
int _cpu; // cup 0
double _size; // 尺寸 0
int _color; // 颜色 0
}
// 行为
@end
// 2.如何编写类的实现
// 以@implementation开头, 以@end结尾, 然后在class对应的地方写上声明时声明的类的名称, 必须和声明的类名一模一样
@implementation Iphone
// 行为的实现
@end
int main(int argc, const char * argv[]) {
// 如何通过一个类来创建对象
// 在OC中想要通过一个类来创建一个对象, 必须给类发送一个消息(好比C语言中调用函数一样)
// 如何发送消息? 在OC中只要想要发送消息就先写上 [类名称/对象名称 方法名称];
// 发送什么消息(调用什么方法)可以创建一个对象呢? new
/*
只要通过一个类调用类的new方法, 也就是给类发送一个叫做new的消息之后
系统内部就会做3件事情
1. 为Iphone类创建出来得对象分配存储空间
2. 初始化Iphone类创建出来的对象中的属性
3. 返回Iphone类创建出来的对象对应的地址
*/
// 通过一个Iphone类型的指针接收了 Iphone对象的地址
// 如果使用给一个指针保存了某一个对象的地址, 那么我们就称这个指针位之为某个类型的对象
// 利用Iphone类型的指针保存了Iphone对象的地址, 那么我们就称Iphone类型的指针p之为Iphone对象
Iphone *p = [Iphone new];
p->_size = 3.5;
p->_color = 0;
p->_model = 4;
p->_cpu = 1;
// OC中的类其实本质就是一个结构体, 所以p这个指针其实就是指向了一个结构体
NSLog(@"size = %f, color = %i, model = %f, cpu = %i", p->_size, p->_color, p->_model, p->_cpu);
/*
struct Person
{
int age;
char *name;
};
struct Person sp;
struct Person *sip = &sp;
// (*sip).age = 30;
// (*sip).name = "lnj";
sip->age = 30;
sip->name = "lnj";
printf("age = %i, name = %s\n", sip->age, sip->name );
*/
// 什么是用于保存地址的? 指针
return 0;
}
2,类-方法
对象方法:
// 注意: 当前这个有参数的方法它的方法名称是 signal:
// 冒号也是方法名称的一部分
- (int)signal:(int)number;
// 为了提高我们的阅读性, OC方法允许我们给每个参数添加一个标签来说明当前参数的含义
// 注意: 标签也是方法名的一部分
// 方法名是 sendMessageWithNumber:andContent:
- (int)sendMessageWithNumber:(int)number andContent:(char *)content;
类方法:
// 如果你不想每次使用方法都需要创建对象开辟存储空间
// 并且如果该方法中没有使用到属性(成员变量), 那么你可以把这个方法定义为类方法
// 对象方法用对象调用 类方法用类调用
//- (int)sumWithValue1:(int)value1 andValue2:(int)value2;
// 如果定义类方法, 类方法的写法和对象方法一模一样, 除了前面的-号不同以外 \
只需要将对象方法的-号换成+, 那么就定义了一个类方法
+ (int)sumWithValue1:(int)value1 andValue2:(int)value2;
// 注意: 如果声明的是对象方法那么就必须实现对象方法
// 如果声明的是类方法那么就必须实现类方法
/*
类方法和对象方法的区别
0. 对象方法以-开头
类方法以+开头
1. 对象方法必须用对象调用
类方法必须用类来调用
2. 对象方法中可以直接访问属性(成员变量)
类方法中不可以直接访问属性(成员变量)
3. 类方法和对象方法可以进行相互调用
4.1对象方法中可以直接调用类方法
4.2类方法中间接调用对象方法 (注意: 不建议这样使用)
4.3类方法中可以直接调用其它类方法
4.4对象方法中可以直接调用对象方法
类方法的应用场景
如果方法中没有使用到属性(成员变量), 那么能用类方法就用类方法
类方法的执行效率比对象方法高
类方法一般用于定义工具方法
字符串查找
文件操作
数据库操作
*/
类方法中可以直接调用类方法
类方法中不可以直接调用对象方法
类方法中不能访问成员变量
3,类对象
1.开辟存储空间, 通过new方法创建对象会在堆 内存中开辟一块存储空间
2.初始化所有属性
3.返回指针地址
创建对象的时候返回的地址其实就是类的第0个属性的地址
但是需要注意的是: 类的第0个属性并不是我们编写的_age, 而是一个叫做isa的属性
isa是一个指针, 占8个字节
其实类也是一个对象, 也就意味着Person也是一个对象
平时我们所说的创建对象其实就是通过一个 类对象 来创建一个 新的对象
类对象是系统自动帮我们创建的, 里面保存了当前对象的所有方法
而实例对象是程序自己手动通过new来创建的, 而实例对象中有一个isa指针就指向了创建它的那个类对象
4,局部变量和全局变量以及成员变量的区别
@interface Person : NSObject
{
// 写在类声明的大括号中的变量, 我们称之为 成员变量(属性, 实例变量)
// 成员变量只能通过对象来访问
// 注意: 成员变量不能离开类, 离开类之后就不是成员变量 \
成员变量不能在定义的同时进行初始化
// 存储: 堆(当前对象对应的堆的存储空间中)
// 存储在堆中的数据, 不会被自动释放, 只能程序员手动释放
int age;
}
@end
// 写在函数和大括号外部的变量, 我们称之为全局变量
// 作用域: 从定义的那一行开始, 一直到文件末尾
// 局部变量可以先定义在初始化, 也可以定义的同时初始化
// 存储: 静态区
// 程序一启动就会分配存储空间, 直到程序结束才会释放
int a;
int b = 10;
int main(int argc, const char * argv[]) {
// 写在函数或者代码块中的变量, 我们称之为局部变量
// 作用域: 从定义的那一行开始, 一直到遇到大括号或者return
// 局部变量可以先定义再初始化, 也可以定义的同时初始化
// 存储 : 栈
// 存储在栈中的数据有一个特点, 系统会自动给我们释放
int num = 10;
{
int value;
}
return 0;
}
Day12.面向对象
1,NSString基本使用
NSString *str = @"李南江";
NSUInteger len = [str length];
NSLog(@"len = %lu", len);
2,成员变量是结构体
Student *stu = [Student new];
// 2.设置学生对象的属性
stu->_name = @"lnj";
// 1.结构体只能在定义的时候初始化
// 2.系统并不清楚它是数组还是结构体
//初始化结构体属性
//方法一:强制转换
// stu->_birthday = (Date){1986, 1, 15};
//方法二:定义一个新的结构体,给d赋值,将d赋值给_birthday
Date d = {1986, 1, 15};
stu->_birthday = d;
//方法三:分别赋值
// stu->_birthday.year = 1986;
// stu->_birthday.month = 1;
// stu->_birthday.day = 15;
3,pragma mark用法
//#pragma mark -
//#pragma mark 枪
井号pragma mark - 枪
4,修改项目模板
/*
修改项目模板以及main函数中的内容
/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/Project Templates/Mac/Application/Command Line Tool.xctemplate/
修改OC文件头部的描述信息
/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File Templates/Source/Cocoa Class.xctemplate
*/
/*
Xcode文档安装的位置1:
/Applications/Xcode.app/Contents/Developer/Documentation/DocSets
注意: 拷贝之前最好将默认的文档删除, 因为如果同时存在高版本和低版本的文档, 那么低版本的不会显示
Xcode文档安装的位置2:
/Users/你的用户名/Library/Developer/Shared/Documentation/DocSets
如果没有该文件夹可以自己创建一个
*/
5,setter和getter方法
/*
setter方法:
作用: 设置成员变量的值
格式:
1. setter方法一定是对象方法
2. 一定没有返回值
3. 一定以set开头, 并且set后面跟上需要设置的成员变量的名称去掉下划线, 并且首字母大写
4. 一定有参数, 参数类型一定和需要设置的成员变量的类型一致, 并且参数名称就是成员变量的名称去掉下划线
*/
- (void)setSize:(int)size;
/*
getter方法:
作用: 获取成员变量的值
格式:
1. getter方法一定是对象方法
2.一定有返回值, 而且返回值一定和获取的成员变量的类型一致
3.方法名称就是获取的成员变量的名称去掉下划线
4. 一定没有参数
*/
- (int)size;
6,点语法
// 如果给属性提供了getter和setter方法, 那么访问属性就又多了一种访问方式 , 点语法
// 点语法其实它的本质是调用了我们的setter和getter方法
// 点语法是一个编译器的特性, 会在程序翻译成二进制的时候将.语法自动转换为setter和getter方法
// 如果点语法在=号的左边, 那么编译器会自动转换为setter方法
// 如果点语法在=号的右边, 或者没有等号, 那么编译器就会自动转换为getter方法
点语法的注意点:
点语法一般用于给成员变量赋值, 如果不是给成员变量赋值一般情况下不建议使用, 但是也可以使用
继承
Day13.类的进阶
1,私有的方法和变量
Person *p = [Person new];
// 无论使用什么成语变量修饰符修饰成员变量, 我们都可以在其它类中看到这个变量
// 只不过有得修饰符修饰的变量我们不能操作而已
// p->_age;
// p->_height;
// p->_name;
// p->_weight;
// [p test];
// id pp = [Person new];
// [pp test];
[p performSelector:@selector(test)];
Person类:
// 如果只有方法的实现, 没有方法的声明, 那么该方法就是私有方法
// 在OC中没有真正的私有方法, 因为OC是消息机制
//- (void)test;
@implementation Person
{
// 实例变量(成员变量)既可以在@interface中定义, 也可以在@implementation中定义
// 写在@implementation中的成员变量, 默认就是私有的成员变量, 并且和利用@private修饰的不太一样, 在@implementation中定义的成员变量在其它类中无法查看, 也无法访问
// 在@implementation中定义的私有变量只能在本类中访问
@public
double _score;
}
2,Property, synthesize基本使用
/*
@porperty是一个编译器指令
在Xocde4.4之前, 可以使用@porperty来代替getter/setter方法的声明
也就是说我们只需要写上@porperty就不用写getter/setter方法的声明
编译器只要看到@property, 就知道我们要生成某一个属性的getter/setter方法的声明
- (void)setAge:(int)age;
- (int)age;
*/
@property int age;
/*
- (void)set_age:(int)_age;
- (int)_age;
*/
@property int _age;
/*
@synthesize是一个编译器指令, 它可以简化我们getter/setter方法的实现
什么是实现:
在声明后面写上大括号就代表着实现
1.在@synthesize后面告诉编译器, 需要实现哪个@property生成的声明
2. 告诉@synthesize, 需要将传入的值赋值给谁和返回谁的值给调用者
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
*/
//@synthesize age = _age;
/*
- (void)setAge:(int)age
{
_number = age;
}
- (int)age
{
return _number
;
}
*/
//@synthesize age = _number;
// 如果在@synthesize后面没有告诉系统将传入的值赋值给谁, 系统默认会赋值给和@synthesize后面写得名称相同的成员变量
// _age? age;
@synthesize age;
/*
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
*/
3,*****重点要记住
/*
从Xcode4.4以后apple对@property进行了一个增强, 以后只要利用一个@property就可以同时生成setter/getter方法的声明和实现
没有告诉@property要将传入的参数赋值给谁, 默认@property会将传入的属性赋值给_开头的成员变量
@property有一个弊端: 它只会生成最简单的getter/setter方法的声明和实现, 并不会对传入的数据进行过滤
如果想对传入的数据进行过滤, 那么我们就必须重写getter/setter方法
如果不想对传入的数据进行过滤, 仅仅是提供一个方法给外界操作成员变量, 那么就可以使用@property
如果利用@property来生成getter/setter方法, 那么我们可以不写成员变量, 系统会自动给我们生成一个_开头的成员变量
注意: @property自动帮我们生成的成员变量是一个私有的成员变量, 也就是说是在.m文件中生成的, 而不是在.h文件中生成的
*/
// age? _age;
/*
- (void)setAge:(int)age;
- (int)age;
*/
@property int age;
// 如果重写了setter方法实现, 那么property就只会生成getter方法
// 如果重写了getter方法实现, 那么property就只会生成setter方法
// 如果同时重写了getter/setter方法实现, 那么property就不会自动帮我们生成私有的成员变量
4,属性修饰符
格式:
@property(属性修饰符) 数据类型 变量名称;
*/
// readwrite: 代表既生成getter方法 , 也生成setter方法
// 默认情况下 @property就是readwrite的
@property(readwrite) int age;
/*
- (void)setHeight:(double)height;
- (double)height;
- (void)setHeight:(double)height;
- (double)abc;
*/
@property(getter=abc) double height;
/*
- (void)setWeight:(double)weight;
- (void)tiZhong:(double)weight;
*/
@property(setter=tiZhong:) double weight;
// readonly: 代表只生成getter方法不生成setter方法
@property(readonly) NSString * name;
// 是否已婚
// 程序员之间有一个约定, 一般情况下获取BOOL类型的属性的值, 我们都会将获取的方法名称改为isXXX
@property(getter=isMarried) BOOL married;
5,id类型
/*
id是一个数据类型, 并且是一个动态数据类型
既然是数据类型, 所以就可以用来
1.定义变量
2.作为函数的参数
3.作为函数的返回值
默认情况下所有的数据类型都是静态数据类型
静态数据类型的特点:
在编译时就知道变量的类型,
知道变量中有哪些属性和方法
在编译的时候就可以访问这些属性和方法,
并且如果是通过静态数据类型定义变量, 如果访问了不属于静态数据类型的属性和方法, 那么编译器就会报错
动态数据类型的特点:
在编译的时候编译器并不知道变量的真实类型, 只有在运行的时候才知道它的真实类型
并且如果通过动态数据类型定义变量, 如果访问了不属于动态数据类型的属性和方法, 编译器不会报错
id == NSObject * 万能指针
id和NSObject *的区别:
NSObject *是一个静态数据类型
id 是一个动态数据类型
*/
// 通过静态数据类型定义变量, 不能调用子类特有的方法
// 通过动态数据类型定义变量, 可以调用子类特有的方法
// 通过动态数据类型定义的变量, 可以调用私有方法
// 弊端: 由于动态数据类型可以调用任意方法, 所以有可能调用到不属于自己的方法, 而编译时又不会报错, 所以可能导致运行时的错误
// 应用场景: 多态, 可以减少代码量, 避免调用子类特有的方法需要强制类型转换
// 为了避免动态数据类型引发的运行时的错误, 一般情况下如果使用动态数据类型定义一个变量, 在调用这个对象的方法之前会进行一次判断, 判断当前对象是否能够调用这个方法
// id obj = [Person new];
id obj = [Student new];
/*
if ([obj isKindOfClass:[Student class]]) {
// isKindOfClass , 判断指定的对象是否是某一个类, 或者是某一个类的子类
[obj eat];
}
*/
if ([obj isMemberOfClass:[Student class]]) {
// isMemberOfClass : 判断指定的对象是否是当前指定的类的实例
[obj eat];
}
6,new方法
/*
new做了三件事情
1.开辟存储空间 + alloc 方法
2.初始化所有的属性(成员变量) - init 方法
3.返回对象的地址
*/
// Person *p = [Person new];
// alloc做了什么事情: 1.开辟存储空间 2.将所有的属性设置为0 3.返回当前实例对象的地址
Person *p1 = [Person alloc];
// 1.初始化成员变量, 但是默认情况下init的实现是什么都没有做 2.返回初始化后的实例对象地址
Person *p2 = [p1 init];
// [[Person alloc] init];
// 注意: alloc返回的地址, 和init返回的地址是同一个地址
NSLog(@"p1 = %p, p2 = %p", p1, p2);
// [[Person alloc] init]; == [Person new];
// 建议大家以后创建一个对象都使用 alloc init, 这样可以统一编码格式
7,构造方法
在OC中init开头的方法, 我们称之为构造方法
构造方法的用途: 用于初始化一个对象, 让某个对象一创建出来就拥有某些属性和值
/*
// 重写init方法, 在init方法中初始化成员变量
// 注意: 重写init方法必须按照苹果规定的格式重写, 如果不按照规定会引发一些未知的错误
// 1.必须先初始化父类, 再初始化子类
// 2.必须判断父类是否初始化成功, 只有父类初始化成功才能继续初始化子类
// 3.返回当前对象的地址
- (instancetype)init
{
// 1.初始化父类
// 只要父类初始化成功 , 就会返回对应的地址, 如果初始化失败, 就会返回nil
// nil == 0 == 假 == 没有初始化成功
self = [super init];
// 2.判断父类是否初始化成功
if (self != nil) {
// 3.初始化子类
// 设置属性的值
_age = 6;
}
// 4.返回地址
return self;
}
*/
/*
- (instancetype)init
{
self = [super init]; // self == nil == 0
if (self) {
// 初始化子类
_age = 6;
}
return self;
}
*/
- (instancetype)init
{
// self = [super init];
// 注意: 不要把 = 号写为 ==
// 一定要将[super init]的返回值赋值给self
if (self = [super init]) {
// 初始化子类
_age = 6;
}
return self;
}
8,instancetype和id的区别
// instancetype == id == 万能指针 == 指向一个对象
// id在编译的时候不能判断对象的真实类型
// instancetype在编译的时候可以判断对象的真实类型
// id和instancetype除了一个在编译时不知道真实类型, 一个在编译时知道真实类型以外, 还有一个区别
// id可以用来定义变量, 可以作为返回值, 可以作为形参
// instancetype只能用于作为返回值
// 注意: 以后但凡自定义构造方法, 返回值尽量使用instancetype, 不要使用id
- (instancetype)init
//- (id)init
{
if (self = [super init]) {
_age = 5;
}
return self;
}
9,自定义构造方法
/*
自定义构造方法:
其实就是自定义一个init方法
1.一定是对象方法
2.一定返回id/instancetype
3.方法名称一定以init开头
*/
- (instancetype)initWithAge:(int)age;
// 一个类可以有0个或者多个自定义构造方法
- (instancetype)initWithName:(NSString *)name;
// 自定义构造方法可以有1个或多个参数
- (instancetype)initWithAge:(int)age andName:(NSString *)name;
10,类工厂方法
@interface Person : NSObject
@property int age;
/*
什么是类工厂方法:
用于快速创建对象的类方法, 我们称之为类工厂方法
类工厂方法中主要用于 给对象分配存储空间和初始化这块存储空间
规范:
1.一定是类方法 +
2.方法名称以类的名称开头, 首字母小写
3.一定有返回值, 返回值是id/instancetype
*/
+ (instancetype)person;
+ (instancetype)personWithAge:(int)age;
@end
@implementation Person
+ (instancetype)person
{
// return [[Person alloc] init];
// 注意: 以后但凡自定义类工厂方法, 在类工厂方法中创建对象一定不要使用类名来创建
// 一定要使用self来创建
// self在类方法中就代表类对象, 到底代表哪一个类对象呢?
// 谁调用当前方法, self就代表谁
return [[self alloc] init];
}
+ (instancetype)personWithAge:(int)age
{
// Person *p = [[Person alloc] init];
Person *p = [[self alloc] init];
p.age = age;
return p;
}
@end
@interface Student : Person
@property int no;
@end
用法:
/*
Student *stu = [Student person]; // [[Person alloc] init];
Person *p = [Person person];
// stu.age = 55;
// NSLog(@"age = %i", stu.age);
stu.no = 888;
NSLog(@"no = %i", stu.no);
*/
Student *stu = [Student personWithAge:30];
Person *p = [Person personWithAge:30];
stu.no = 888;
11,类的本质
/*
类的本质:
类其实也是一个对象, 这个对象会在这个类第一次被使用的时候创建
只要有了类对象, 将来就可以通过类对象来创建实例对象
实例对象中有一个isa指针, 指向创建自己的类对象
类对象中保存了当前对象所有的对象方法
当给一个实例对象发送消息的时候, 会根据实例对象中的isa指针去对应的类对象中查找
*/
/*
Person *p = [[Person alloc] init];
[p setAge:30];
[Person test];
*/
// 1.如何获取类对象
// [实例对象 class]; [类名 class];
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
// 一个类再内存中只有一份类对象
Class c1 = [p1 class];
Class c2 = [p2 class];
Class c3 = [Person class];
NSLog(@"c1 = %p, c2 = %p, c3 = %p", c1, c2, c3);
// 2.类对象的应用场景
// 2.1用于创建实例对象
Person *p3 = [[c1 alloc] init];
p3.age = 30;
NSLog(@"%i", p3.age);
// 2.2用于调用类方法
// [Person test];
[c1 test];
demo(c1);
demo([Car class]);
return 0;
}
void demo(Class c)
{
id obj = [[c alloc] init];
NSLog(@"%@", obj);
}
12,类的启动过程
@implementation Person
// 只要程序启动就会将所有类的代码加载到内存中, 放到代码区
// load方法会在当前类被加载到内存的时候调用, 有且仅会调用一次
// 如果存在继承关系, 会先调用父类的load方法, 再调用子类的load方法
+ (void)load
{
NSLog(@"Person类被加载到内存了");
}
// 当当前类第一次被使用的时候就会调用(创建类对象的时候)
// initialize方法在整个程序的运行过程中只会被调用一次, 无论你使用多少次这个类都只会调用一次
// initialize用于对某一个类进行一次性的初始化
// initialize和load一样, 如果存在继承关系, 会先调用父类的initialize再调用子类的initialize
+ (void)initialize
{
NSLog(@"Person initialize");
}
@end
13,SEL类型
// 1.SEL类型的第一个作用, 配合对象/类来检查对象/类中有没有实现某一个方法
/*
SEL sel = @selector(setAge:);
Person *p = [Person new];
// 判断p对象中有没有实现-号开头的setAge:方法
// 如果P对象实现了setAge:方法那么就会返回YES
// 如果P对象没有实现setAge:方法那么就会返回NO
BOOL flag = [p respondsToSelector:sel];
NSLog(@"flag = %i", flag);
// respondsToSelector注意点: 如果是通过一个对象来调用该方法那么会判断该对象有没有实现-号开头的方法
// 如果是通过类来调用该方法, 那么会判断该类有没有实现+号开头的方法
SEL sel1 = @selector(test);
flag = [p respondsToSelector:sel1];
NSLog(@"flag = %i", flag);
flag = [Person respondsToSelector:sel1];
NSLog(@"flag = %i", flag);
*/
// 2.SEL类型的第二个作用, 配合对象/类来调用某一个SEL方法
/*
SEL sel = @selector(demo);
Person *p = [Person new];
// 调用p对象中sel类型对应的方法
[p performSelector:sel];
SEL sel1 = @selector(signalWithNumber:);
// withObject: 需要传递的参数
// 注意: 如果通过performSelector调用有参数的方法, 那么参数必须是对象类型,
// 也就是说方法的形参必须接受的是一个对象, 因为withObject只能传递一个对象
[p performSelector:sel1 withObject:@"13838383438"];
SEL sel2 = @selector(setAge:);
[p performSelector:sel2 withObject:@(5)];
NSLog(@"age = %i", p.age);
// 注意:performSelector最多只能传递2个参数
SEL sel3 = @selector(sendMessageWithNumber:andContent:);
[p performSelector:sel3 withObject:@"138383438" withObject:@"abcdefg"];
*/
// 3.配合对象将SEL类型作为方法的形参
Car *c = [Car new];
SEL sel = @selector(run);
Person *p = [Person new];
[p makeObject:c andSel:sel];
Day15.内存管理
/*
ARC: Automatic(自动) Reference(引用) Counting(计数)
什么是自动引用计数?
不需要程序员管理内容, 编译器会在适当的地方自动给我们添加release/retain等代码
注意点: OC中的ARC和java中的垃圾回收机制不太一样, java中的垃圾回收是系统干得, 而OC中的ARC是编译器干得
MRC: Manul(手动) Reference(引用) Counting(计数)
什么是手动引用计数?
所有对象的内容都需要我们手动管理, 需要程序员自己编写release/retain等代码
内存管理的原则就是有加就有减
也就是说, 一次alloc对应一次release, 一次retain对应一次relese
*/
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 只要创建一个对象默认引用计数器的值就是1
Person *p = [[Person alloc] init];
NSLog(@"retainCount = %lu", [p retainCount]); // 1
// 只要给对象发送一个retain消息, 对象的引用计数器就会+1
[p retain];
NSLog(@"retainCount = %lu", [p retainCount]); // 2
// 通过指针变量p,给p指向的对象发送一条release消息
// 只要对象接收到release消息, 引用计数器就会-1
// 只要一个对象的引用计数器为0, 系统就会释放对象
[p release];
// 需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1
NSLog(@"retainCount = %lu", [p retainCount]); // 1
[p release]; // 0
NSLog(@"--------");
}
// [p setAge:20];
return 0;
@implementation Person
- (void)dealloc
{
NSLog(@"Person dealloc");
// 注意:super dealloc一定要写到所有代码的最后
// 一定要写在dealloc方法的最后面
[super dealloc];
}
@end
在xcode中的Build Setting 中搜索automatic r 更改后面的YES还是no
1,野指针
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init]; // 1
// 只要一个对象被释放了, 我们就称这个对象为 "僵尸对象"
// 当一个指针指向一个僵尸对象, 我们就称这个指针为野指针
// 只要给一个野指针发送消息就会报错
[p release]; // 1-1 = 0
// *** -[Person release]: message sent to deallocated instance 0x1001146b0
// 空指针 nil 0
// 为了避免给野指针发送消息会报错, 一般情况下, 当一个对象被释放后我们会将这个对象的指针设置为空指针
// 因为在OC中给空指针发送消息是不会报错的
p = nil;
[p release];
[p release];
[p release];
[p release];
[p release];
[p release];
[p release];
}
return 0;
}
Day16.ARC和分类Block使用
1,ARC基本概念
int main(int argc, const char * argv[]) {
/*
// Person *p = [[[Person alloc] init] autorelease];
// 默认情况下所有的指针都是强指针
// Person *p = [[Person alloc] init];
// [p retain];
// [p release];
*/
/*
{
// ARC的判断准则: 只要没有强指针指向对象, 对象就会释放
// 默认情况下所有的指针都是强指针
// Person *p = [[Person alloc] init];
// p = nil;
// __strong Person *p = [[Person alloc] init];
// // 弱指针
// __weak Person *p2 = p;
// p = nil;
// 在开发中, 千万不要使用一个弱指针保存一个刚刚创建的对象
// 立即释放
__weak Person *p = [[Person alloc] init];
}
*/
Person *p = [[Person alloc] init];
p = nil;
return 0;
}
2,arc和mrc混编
加上这个:-fno-objc-arc
3,MRC转ARC
3,分类Category
/*
方法:
方法的声明:
方法的实现:
所以: 通过分类给某一个类扩充方法, 也分为声明和实现两个部分
// 分类的声明
@interface ClassName (CategoryName)
NewMethod; //在类别中添加方法
//不允许在类别中添加变量
@end
ClassName: 需要给哪个类扩充方法
CategoryName: 分类的名称
NewMethod: 扩充的方法
// 分类的实现
@implementation ClassName(CategoryName)
NewMethod
... ...
@end
ClassName: 需要给哪个类扩充方法
CategoryName: 分类的名称
NewMethod: 扩充的方法
*/
/*
方法的调用顺序:
1.分类
2.本类
3.父类
*/
4,Block基本使用
#import
void printRose(int num)
{
for (int i = 0; i < num; ++i) {
printf(" {@} \n");
printf(" | \n");
printf(" \\|/ \n");
printf(" | \n");
}
}
int sum(int value1, int value2)
{
return value1 + value2;
}
int main(int argc, const char * argv[]) {
/*
printf(" {@} \n");
printf(" | \n");
printf(" \\|/ \n");
printf(" |");
*/
/*
// printRose();
// printRose();
// void代表指向的函数没有返回值
// ()代表指向的函数没有形参
// (*roseP)代表roseP是一个指向函数的指针
// void (*roseP) ();
// roseP = printRose;
// roseP();
// 定义一个block变量,
// block和函数一样,可以没有返回值,也没有形参
// 也可以没有返回值有形参
// 也可以有返回值没有形参
// 也可以有返回值有形参
// 所以, 在定义一个block变量的时候, 也需要告诉该变量将来保存的代码有没有返回值和形参
// void代表block将来保存的代码没有返回值
// ()代表block将来保存的代码没有形参
// (^roseBlock) 代表reseBlock是一个block变量, 可以用于保存一段block代码
void (^roseBlock) ();
// 如果block没有参数, 那么^后面的()可以省略
roseBlock = ^(){
printf(" {@} \n");
printf(" | \n");
printf(" \\|/ \n");
printf(" | \n");
};
// 要想执行block保存的代码, 必须调用block才会执行
roseBlock();
roseBlock();
*/
/*
// printRose(10);
// void (*roseP)(int);
// roseP = printRose;
// roseP(3);
void (^roseBlock) (int);
roseBlock = ^(int num){
for (int i = 0; i < num; ++i) {
printf(" {@} \n");
printf(" | \n");
printf(" \\|/ \n");
printf(" | \n");
}
};
roseBlock(2);
*/
/*
// int (*sumP)(int, int);
// sumP = sum;
// NSLog(@"sum = %i", sumP(10 , 20));
int (^sumBlock) (int, int);
sumBlock =^(int value1, int value2){
return value1 + value2;
};
NSLog(@"sum = %i", sumBlock(10, 40));
*/
// block是一种数据类型
int (^printBlock)(int)= ^int (int num){
for (int i=0; i
5,Block和typedef
#import
int sum(int value1, int value2)
{
return value1 + value2;
}
int minus(int value1, int value2)
{
return value1 - value2;
}
typedef int (*calculte)(int, int);
// 注意: 利用typedef给block起别名, 和指向函数的指针一样, block变量的名称就是别名
typedef int (^calculteBlock)(int , int);
int main(int argc, const char * argv[]) {
/*
// int (*sumP)(int, int);
// sumP = sum;
calculte sumP = sum;
NSLog(@"sum = %i", sumP(20, 10));
// int (*minusP)(int, int);
// minusP = minus;
calculte minusP = minus;
NSLog(@"minus = %i", minusP(20, 10));
*/
// int (^sumBlock)(int , int );
calculteBlock sumBlock = ^(int value1, int value2){
return value1 + value2;
};
NSLog(@"sum = %i", sumBlock(20, 10));
// int (^minusBlock)(int , int);
calculteBlock minusBlock = ^(int value1, int value2){
return value1 - value2;
};
NSLog(@"minus = %i", minusBlock(20, 10));
return 0;
}
6,Block的应用场景
#import
/*
void goToWorkPrefix()
{
NSLog(@"起床");
NSLog(@"穿衣服");
NSLog(@"洗漱");
NSLog(@"喝早茶");
NSLog(@"驾车去上班");
}
void goToWorkSubfix()
{
NSLog(@"收拾东西");
NSLog(@"驾车回家");
NSLog(@"吃晚饭");
NSLog(@"洗澡");
NSLog(@"睡觉");
}
void goToWorkInday1()
{
goToWorkPrefix();
NSLog(@"认识新同事");
goToWorkSubfix();
}
void goToWorkInday2()
{
goToWorkPrefix();
NSLog(@"熟悉公司代码");
goToWorkSubfix();
}
void goToWorkInday3()
{
goToWorkPrefix();
NSLog(@"开始编写代码");
goToWorkSubfix();
}
void goToWorkInday4()
{
goToWorkPrefix();
NSLog(@"应用程序上架");
goToWorkSubfix();
}
*/
// 当发现代码的前面和后面都是一样的时候, 这个时候就可以使用block
void goToWork(void (^workBlock)())
{
NSLog(@"起床");
NSLog(@"穿衣服");
NSLog(@"洗漱");
NSLog(@"喝早茶");
NSLog(@"驾车去上班");
// 不一样
workBlock();
NSLog(@"收拾东西");
NSLog(@"驾车回家");
NSLog(@"吃晚饭");
NSLog(@"洗澡");
NSLog(@"睡觉");
}
void goToWorkInDay1()
{
goToWork(^{
NSLog(@"认识新同事");
});
}
void goToWorkInDay2()
{
goToWork(^{
NSLog(@"熟悉公司代码");
});
}
void goToWorkInDay3()
{
goToWork(^{
NSLog(@"开始编写代码");
});
}
void goToWorkInDay4()
{
goToWork(^{
NSLog(@"应用程序上架");
});
}
/*
找到需要读取的文件
读取文件
操作文件
关闭文件
*/
int main(int argc, const char * argv[]) {
goToWorkInDay2();
return 0;
}
7,Block的注意点
#import
#import "Person.h"
int main(int argc, const char * argv[]) {
// 1.block中可以访问外面的变量
/*
int a = 10;
void (^myBlock)() = ^{
NSLog(@"a = %i", a);
};
myBlock();
int a=10;
void( ^myBlock)()=^{
Nslog(@"a= %i",a);
}
myBlock();
*/
// 2.block中可以定义和外界同名的变量, 并且如果在block中定义了和外界同名的变量, 在block中访问的是block中的变量
/*
int a = 10;
void (^myBlock)() = ^{
int a = 20;
NSLog(@"a = %i", a);
};
myBlock();
*/
// 3.默认情况下, 不可以在block中修改外界变量的值
// 因为block中的变量和外界的变量并不是同一个变量
// 如果block中访问到了外界的变量, block会将外界的变量拷贝一份到堆内存中
// 因为block中使用的外界变量是copy的, 所以在调用之前修改外界变量的值, 不会影响到block中copy的值
/*
int a = 10;
NSLog(@"&a = %p", &a);
void (^myBlock)() = ^{
// a = 50;
NSLog(@"&a = %p", &a);
NSLog(@"a = %i", a);
};
a = 20;
myBlock();
*/
/*
// 如果想在block中修改外界变量的值, 必须在外界变量前面加上__block
// 如果在block中修改了外界变量的值, 会影响到外界变量的值
__block int a = 10;
NSLog(@"&a = %p", &a);
void (^myBlock)() = ^{
a = 50;
NSLog(@"&a = %p", &a);
NSLog(@"a = %i", a);
};
myBlock();
NSLog(@"a = %i", a);
*/
/*
// int a = 10; // 如果没有添加__block是值传递
// void (*myBlock)() = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, a);
// (myBlock)->FuncPtr)(myBlock);
// 为什么不加__block不能在block中修改外界变量的值
int a = 10;
void (^myBlock)() = ^{
// a = 10;
NSLog(@"a = %i", a);
};
myBlock();
*/
/*
// a = 10; // 如果加上__block之后就是地址传递, 所以可以在block中修改外界变量的值
// void (*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344);
// 为什么加了__block就可以在block中修改外界变量的值
__block int a = 10;
void (^myBlock)() = ^{
a = 10;
NSLog(@"a = %i", a);
};
myBlock();
*/
// block是存储在堆中还是栈中
// 默认情况下block存储在栈中, 如果对block进行一个copy操作, block会转移到堆中
// 如果block在栈中, block中访问了外界的对象, 那么不会对对象进行retain操作
// 但是如果block在堆中, block中访问了外界的对象, 那么会对外界的对象进行一次retain
// 如果在block中访问了外界的对象, 一定要给对象加上__block, 只要加上了__block, 哪怕block在堆中, 也不会对外界的对象进行retain
// 如果是在ARC开发中就需要在前面加上__weak
__block Person *p = [[Person alloc] init]; // 1
// 如果在做iOS开发时, 在ARC中不这样写容易导致循环引用
// Person *p = [[Person alloc] init];
// __weak Person *weakP = p;
NSLog(@"retainCount = %lu", [p retainCount]);
void (^myBlock)() = ^{
NSLog(@"p = %@", p); // 2
// NSLog(@"p = %p", weakP);
NSLog(@"block retainCount = %lu", [p retainCount]);
};
Block_copy(myBlock);
myBlock();
[p release]; // 1
return 0;
}