目录
OC语言基础
OC与C
OC完全兼容C, 所以可以在OC程序中编写C语言代码,并且可以将C语言的源文件和OC的源文件组合在一起生成可执行文件。
printf与NSLog的区别
- NSLog会自动换行
- NSLog在输出内容时会附加一些系统信息(含有对象的内存地址)
- NSLog和printf接收的参数不一样
printf("c hello world\n");
NSLog(@"OC hello World");
函数和方法的区别
- 函数属于整个文件, 方法属于某一个类
方法如果离开类就不行 - 函数可以直接调用, 方法必须用对象或者类来调用
注意: 虽然函数属于整个文件, 但是如果把函数写在类的声明中会不识别 - 不能把函数当做方法来调用, 也不能把方法当做函数来调用
方法的注意点
方法可以没有声明只有实现
方法可以只有声明没有实现, 编译不会报错, 但是运行会报错
如果方法只有声明没有实现, 那么运行时会报:
reason: '+[Person demo]: unrecognized selector sent to class 0x100001140’
发送了一个不能识别的消息, 在Person类中没有+开头的demo方法
reason: '-[Person test]: unrecognized selector sent to instance 0x100400000’
类也有一个注意点:
类可以只有实现没有声明
注意: 在开发中不建议这样写
初学常见报错情况
// 1.只有类的声明,没有类的实现
// 2.漏了@end
// 3. @interface和@implementation嵌套
// 4.成员变量没有写在括号里面
// 5.方法的声明写在了大括号里面
// 6.成员变量不能在{}中进行初始化、不能被直接拿出去访问
// 7.方法不能当做函数一样调用
// 8.OC方法只能声明在@interface和@end之间,只能实现在@implementation和@end之间。也就是说OC方法不能独立于类存在
// 9.C函数不属于类,跟类没有联系,C函数只归定义函数的文件所有
// 10.C函数不能访问OC对象的成员
// 11.低级错误:方法有声明,但是实现的时候写成了函数
// 12.OC可以没有@interface同样可以定义一个类
#pragma mark的使用
#pragma mark - 类1
……
#pragma mark - 类2
……
#pragma mark - 类3
……
OC面向对象概念
1.什么是面向对象?
找对象使用对象的方法(功能)
2.对象
3.什么是类?
类就是用于描述对象的共性特征
主要用于描述对象的属性和行为
4.如何定义一个类
4.1类的声明
声明类的目的: 告诉系统我们这个类中有哪些属性和方法
类名: 首字母大写
: NSObject 为了让我们的类具备创建对象的能力, 也就是可以使用new方法
@interface 类名 : NSObject
{
属性; // 属性必须写到{}中, 属性名称以_开头
}
方法; // 方法必须写到{}外面
@end
4.2类的实现
@implementation 类名
方法的实现
@end
5.如何创建对象
要想创建对象必须给类发送一个new消息, 调用类的new方法
>开辟存储空间
>初始化属性
>返回地址
类名 *p = [类名 new];
5.1如何发送消息? [类名/对象 方法名称]
6.修改对象的属性和获取对象的属性
因为类的本质是一个结构体, 所以我们是用一个指向结构体的指针保存了对象的地址, 所以我们可以通过指针操作结构体的方式来操作对象
p->属性名称 = 值; // 对象的属性默认是受保护的, 必须把属性变为公开的才可以直接访问@public
NSLog(@"age = %i", p->age);
7.类中可以定义两种方法
7.1对象方法
以-开头
可以直接访问对象的属性
必须使用对象调用
对象方法中可以直接调用其它对象方法
对象方法中可以直接调用类方法
7.2类方法
以+开头
不可以直接访问对象的属性
必须使用类调用
类方法中不可以直接调用对象方法
类方法中可以直接调用类方法
7.3方法定义的注意点
方法名采用 驼峰命名
方法中的数据类型都必须使用()括起来
方法中每个参数的数据类型前面都必须加上:
:也是方法名的一部分
NString基本使用
int main(int argc, const char * argv[]) {
/*
// C语言中的字符串不是对象
char *name1 = "lnj";
char name2[] = "lmj";
// OC中的字符串是一个对象
// 正是因为OC中的字符串是一个对象, 所以它就具备了很多功能
NSString *str = @"lk";
Iphone *p = [Iphone new];
// 注意: 输出C语言的字符串使用%s
// 输出OC的字符串使用%@, %@就专门用于输出对象类型的
// NSLog(@"content = %s", [p loadMessage]);
NSLog(@"content = %@", [p loadMessage]);
*/
// 1.如何创建OC字符串
// NSString *str = @"lnj";
// printf("age = %i, height = %f\n", 30, 1.75);
// NSString *str = [NSString stringWithFormat:@"age = %i, height = %f\n", 30, 1.75];
// NSLog(@"str = %@", str);
// 2.如何计算字符串的长度
// char name[] = "lnj"; // l n j \0
char name[] = "李";
// 包含\0
size_t size = sizeof(name);
printf("size = %lu\n", size);
// 不包含\0
size_t length = strlen(name);
printf("length = %lu\n", length);
// 不包含\0的
// 计算出来的是个数, 而不是占用的字节数
// NSString *str = @"lnj";
NSString *str = @"李南江";
NSUInteger len = [str length];
NSLog(@"len = %lu", len);
return 0;
}
修改项目模板
/*
修改项目模板以及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
如果没有该文件夹可以自己创建一个
*/
匿名对象
// .h和.m之间切换快捷键 command + control + ⬆️
int main(int argc, const char * argv[]) {
// 匿名就是没有名字, 匿名对象就是没有名字的对象
// 1.有名字的对象
// 只要用一个指针保存了某个对象的地址, 我们就可以称这个指针为某个对象
// 称p为Person对象
Person *p =[Person new]; // 0ffc12
p->_age = 30;
p->_name= @"lnj";
[p say];
/*
0ffc12->_age = 30;
0ffc12->_name= @"lnj";
[0ffc12 say];
*/
// 2.没有名字的对象
// 无论有没有名字, 只要调用new方法都会返回对象的地址
// 每次new都会新开辟一块存储空间
[Person new]->_age = 30;
[Person new]->_name = @"LMJ";
[[Person new] say];
// 3.匿名对象的应用场景
// 3.1当对象只需要使用一次的时候就可以使用匿名对象
Iphone *phone = [Iphone new]; // 0ffb11 phone = 0ffb11
[phone brand]; // [0ffb11 brand];
[[Iphone new] brand]; // [0fff5 brand];
// 3.2匿名对象可以作为方法的参数(实参)
Person *p1 = [Person new];
// Iphone *phone1 = [Iphone new];
// [p1 signal:phone1];
[p1 signal:[Iphone new]];
return 0;
}
description
// 可以重写description方法, 返回我们需要打印的内容
// 只要利用%@打印对象, 就会调用description
// 如果打印的是对象就会调用-号开头的description方法
/*
如果通过%@打印对象就会调用-号开头的
如果通过%@打印类对象就会调用+号开头的
*/
// +仅仅作为了解, 开发中99%的情况使用的都是-号开头的description
int main(int argc, const char * argv[]) {
Person *p = [Person new];
[p setAge:30];
[p setName:@"lnj"];
[p setHeigth:1.75];
[p setWeight:65];
[p setTel:@"13554499311"];
[p setEmail:@"lnj@520it.com"];
// NSLog(@"age = %i, name = %@, height = %f, weight = %f, tel = %@, email = %@", [p age], [p name], [p height], [p weight], [p tel], [p email]);
// %@是用来打印对象的, 其实%@的本质是用于打印字符串
// 只要利用%@打印某个对象, 系统内部默认就会调用父类的description方法
// 调用该方法, 该方法会返回一个字符串, 字符串的默认格式 <类的名称: 对象的地址>
NSLog(@"person = %@", p);
NSLog(@"%@", p);
// class注意c是小写, 只要给类发送class消息, 就会返回当前类的类对象
// 1.获取Person对应的类对象
Class c = [Person class];
// 2.打印Person的类对象
NSLog(@"当前对象对应的类 = %@", c);
NSLog(@"当前对象的地址 = %p", p);
return 0;
}
getter和setter方法
/*
setter方法:
作用: 设置成员变量的值
格式:
1. setter方法一定是对象方法
2. 一定没有返回值
3. 一定以set开头, 并且set后面跟上需要设置的成员变量的名称去掉下划线, 并且首字母大写
4. 一定有参数, 参数类型一定和需要设置的成员变量的类型一致, 并且参数名称就是成员变量的名称去掉下划线
*/
- (void)setSize:(int)size;
/*
getter方法:
作用: 获取成员变量的值
格式:
1. getter方法一定是对象方法
2.一定有返回值, 而且返回值一定和获取的成员变量的类型一致
3.方法名称就是获取的成员变量的名称去掉下划线
4. 一定没有参数
*/
- (int)size;
super关键字
super是个编译器的指令符号,只是告诉编译器在执行的时候,去调谁的方法,而self则是方法调用时的一个隐私参数;
// 只需要利用super给父类的方法发送一个消息, 那么系统就会自动调用父类的方法
// 如果以后想在子类中调用父类的方法可以使用super
// 如果想在给父类方法进行扩展的同时保留父类的方法, 那么可以使用super调用父类同名的方法
/*
super在类方法中, 一定会调用父类的类方法
super在对象方法中, 一定会调用父类的对象方法
可以利用super在任意方法中调用父类中的方法
*/
self关键字
OC语言中的self,就相当于C++、Java中的this指针。
要理解什么是self,什么是成员变量,什么是对象方法,什么是类方法
成员变量:成员变量是一个实例对象的具体状态特征,并且这些状态特征是可以改变的,如张三的年龄,身高,体重等
对象方法:一个实例对象的行为,比如张三具有吃的行为,张三做出这样行为的时候,有可能会影响,自身的某些状态特征,比如张三吃可能会增加张三体重和身高。
类方法:类方法是某个类的行为,可以直接通过类名调用;如果在类方法中需要使用某些数据,必须通过参数传入;它不能访问成员变量。
类方法中的self
在整个程序运行过程中,一个类有且仅有一个类对象。
通过类名调用方法就是给这个类对象发送消息。
类方法的self就是这个类对象
在类方法中可以通过self来调用其他的类方法
不能在类方法中去调用对象方法或成员变量,因为对象方法与成员变量都是属于具体的实例对象的。
对象方法中的self
在整个程序运行过程中,对象可以有0个或多个
通过对象调用方法就是给这个对象发送消息
对象方法中self就是调用这个方法的当前对象。
在对象方法中,可以通过self来调用本对象上的其他方法
在对象方法中,可以通过self来访问成员变量
self总结
谁调用self所在的方法,那么self就是谁
self在类方法中,就是这个类的类对象,全局只有一个,可通过self调用本类中的其他类方法,但是不能通过self来调用对象方法或访问成员变量
self在对象方法中,就是调用这个方法的那个对象, 可以通过self调用本类中其他的对象方法,访问成员变量,但不能通过self调用本类的类方法。
通过self调用方法的格式:[self 方法名];
通过self访问成员变量格式:self->成员变量名
self使用注意
同时有对象方法和类方法存在的时候,self不会调错
self只能在方法中使用;不要使用self来调用函数,也不可以在函数内部使用self;
使用self调用本方法,导致死循环调用。
全局变量、成员变量、局部变量
全局变量:只要是有声明它的地方都能使用
成员变量:只能在本类和其子类的对象方法中使用
局部变量:只能在本函数或方法中使用
从作用域的范围来看:全局变量 > 成员变量 > 局部变量
当不同的作用域中出现了同名的变量,内部作用域的变量覆盖外部作用域变量,所以同名变量的覆盖顺序为:局部变量覆盖成员变量,成员变量覆盖全局变量
如果在对象方法中出现与成员变量同名的局部变量,如果此时想使用该成员变量可以通过self->成员变量名的方式
点语法
当点语法使用在 “=“赋值符号左侧的时候,点语法会被展开为setter方法的调用,其他情况(等号右侧、直接使用)为点语法展开为getter方法的调用
点语法的本质是方法的调用,而不是访问成员变量,当使用点语法时,编译器会自动展开成相应的方法调用。
切记点语法的本质是转换成相应的对setter和getter方法调用,如果没有set和get方法,则不能使用点语法。
不要在getter 与 setter方法中使用本属性的点语法
- (void) setAge:(int)age {
// 下面的代码会引发死循环
self.age = age;
//编译器展开后 [self setAge:age]
}
- (int) age {
// 下面的代码会引发死循环
return self.age;
// 编译器展开后 [self age]
}
继承
基本概念
基类的私有属性能被继承,不能在子类中访问。
OC中的继承是单继承:也就是说一个类只能一个父类,不能继承多个父类
子类与父类的关系也称为isA(是一个)关系,我们说 子类isA父类,也就是子类是一个父类,比如狗类继承动物类,那么我们说狗isA动物,也就是狗是一个动物。在如汽车继承交通工具,那么们说汽车isA交工工具,也就是汽车是一个交通工具
继承的合理性:引用《大话西游》里的一句话来描述继承的。“人是人他妈生的,妖是妖他妈生的!”
@interface 子类名称 : 父类名称
@end
相关特性
1.方法重写
在子类中实现与父类中同名的方法,称之为方法重写;
重写以后当给子类发送这个消息的时候,执行的是在子类中重写的那个方法,而不是父类中的方法。
如果想在子类中调用被子类重写的父类的方法,可以通过super关键字
使用场景:当从父类继承的某个方法不适合子类,可以在子类中重写父类的这个方法。
2.继承中方法调用的顺序
1、在自己类中找
2、如果没有,去父类中找
3、如果父类中没有,就去父类的父类中
4、如果父类的父类也没有,就还往上找,直到找到基类(NSObject)
5、如果NSObject都没有就报错了
3.继承的注意事项
子类不能定义和父类同名的成员变量,私有成员变量也不可以;因为子类继承父类,子类将会拥有父类的所有成员变量,若在子类中定义父类同名成员变量 属于重复定义。
OC类支持单一继承,不支持多继承;也就是说一个类只能有一个直接父类
OC类支持多层继承
实例变量修饰符
作用域
1)@public (公开的)在有对象的前提下,任何地方都可以直接访问。
2)@protected (受保护的)只能在当前类和子类的对象方法中访问
3)@private (私有的)只能在当前类的对象方法中才能直接访问
4)@package (框架级别的)作用域介于私有和公开之间,只要处于同一个框架中相当于@public,在框架外部相当于@private
在子类中的访问
1)@private私有成员是能被继承,也不能被外部方法访问。
2)@public 公有成员能被继承,也能被外部方法访问。
3)@protected 保护成员能够被继承,不能够被外部方法访问。
实例变量作用域使用注意事项
在@interface @end之间声明的成员变量如果不做特别的说明,那么其默认是protected 的。
一个类继承了另一个类,那么就拥有了父类的所有成员变量和方法,注意所有的成员变量它都拥有,只是有的它不能直接访问。例如@private的
私有变量和私有方法
OC中的私有变量
在类的实现即.m文件中也可以声明成员变量,但是因为在其他文件中通常都只是包含头文件而不会包含实现文件,所以在.m文件中声明的成员变量是@private的。在.m中定义的成员变量不能和它的头文件.h中的成员变量同名,在这期间使用@public等关键字也是徒劳的。
@implementation Dog
{
@public
int _age;
}
@end
OC中的私有方法
私有方法:只有实现没有声明的方法
原则上:私有方法只能在本类的方法中才能调用。
注意: OC中没有真正的私有方法
@interface Dog : NSObject
@end
@implementation Dog
- (void)eat
{
NSLog(@"啃骨头");
}
@end
int main(int argc, const char * argv[]) {
Dog *d = [Dog new];
SEL s1 = @selector(eat);
[d performSelector:s1];
return 0;
}
@property
基本概念
@property是编译器的指令
什么是编译器的指令 ?
编译器指令就是用来告诉编译器要做什么!
@property会让编译器做什么呢?
@property 用在声明文件中告诉编译器声明成员变量的的访问器(getter/setter)方法
这样的好处是:免去我们手工书写getter和setter方法繁琐的代码
@property基本使用
在Xocde4.4之前, 可以使用@porperty来代替getter/setter方法的声明
也就是说我们只需要写上@porperty就不用写getter/setter方法的声明
用@property int age;就可以代替下面的两行
-(int)age; // getter
-(void)setAge:(int)age; // setter
@property编写步骤
1.在@inteface和@end之间写上@property
2.在@property后面写上需要生成getter/setter方法声明的属性名称, 注意因为getter/setter方法名称中得属性不需要_, 所以@property后的属性也不需要_.并且@property和属性名称之间要用空格隔开
3.在@property和属性名字之间告诉需要生成的属性的数据类型, 注意两边都需要加上空格隔开
@property属性
@interface Person : NSObject
如果给一个属性同时提供了getter/setter方法, 那么我们称这个属性为可读可写属性
如果只提供了getter方法, 那么我们称这个属性为只读属性
如果只提供了setter方法, 那么我们称这个属性为只写属性
如果既没有提供getter也没有提供setter方法, 那么我们称这个属性为私有属性
格式:
@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;
@end
@property增强
@interface Person : NSObject
/*
{
@public
int _age;
int age;
}
*/
从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;
@end
@synthesize
基本概念
@synthesize是编译器的指令
什么是编译器的指令 ?
编译器指令就是用来告诉编译器要做什么!
@synthesize会让编译器做什么呢?
@synthesize 用在实现文件中告诉编译器实现成员变量的的访问器(getter/setter)方法
这样的好处是:免去我们手工书写getterr和setter方法繁琐的代码
@synthesize基本使用
在@implementation中, 用来自动生成setter和getter的实现
用@synthesize age = _age;就可以代替
- (int)age{
return _age;
}
- (void)setAge:(int)age{
_age = age;
}
@synthesize编写步骤
1.在@implementation和@end之间写上@synthesize
2.在@synthesize后面写上和@property中一样的属性名称, 这样@synthesize就会将@property生成的什么拷贝到@implementation中
3.由于getter/setter方法实现是要将传入的形参 给属性和获取属性的值,所以在@synthesize的属性后面写上要将传入的值赋值给谁和要返回哪个属性的值, 并用等号连接
以下写法会赋值给谁?
@interface Person : NSObject
{
@public
int _age;
int _number;
}
@property int age;
@end
@implementation Person
@synthesize age = _number;
@end
int main(int argc, const char * argv[]) {
Person *p = [Person new];
[p setAge:30];
NSLog(@"_number = %i, _age = %i", p->_number, p->_age);
return 0;
}
@synthesize注意点
@synthesize age = _age;
setter和getter实现中会访问成员变量_age
如果成员变量_age不存在,就会自动生成一个@private的成员变量_age
@synthesize age;
setter和getter实现中会访问@synthesize后同名成员变量age
如果成员变量age不存在,就会自动生成一个@private的成员变量age
多个属性可以通过一行@synthesize搞定,多个属性之间用逗号连接
@synthesize age = _age, number = _number, name = _name;
id类型
静态类型和动态类型
静态类型
将一个指针变量定义为特定类的对象时,使用的是静态类型,在编译的时候就知道这个指针变量所属的类,这个变量总是存储特定类的对象。
Person *p = [Person new];
动态类型
这一特性是程序直到执行时才确定对象所属的类
id obj = [Person new];
为什么要有动态类型?
我们知道NSObject是OC中的基类
那么任何对象的NSObject类型的指针可以指向任意对象,都没有问题
但是NSObject是静态类型,如果通过它直接调用NSObject上面不存在的方法,编译器会报错。
你如果想通过NSObject的指针调用特定对象的方法,就必须把NSObject * 这种类型强转成特定类型。然后调用。如下
//定义NSObject * 类型
NSObject* obj = [Cat new];
Cat *c = (Cat*)obj;
[c eat];
id 是一种通用的对象类型,它可以指向属于任何类的对象,也可以理解为万能指针 ,相当于C语言的 void *
因为id是动态类型,所以可以通过id类型直接调用指向对象中的方法, 编译器不会报错
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
id obj = [C at new];
[obj eat]; // 不用强制类型转换
[obj test]; //可以调用私有方法
注意
在id的定义中,已经包好了*号。id指针只能指向OC中的对象
为了尽可能的减少编程中出错,Xcode做了一个检查,当使用id 类型的调用本项目中所有类中都没有的方法,编译器会报错
id类型不能使用.语法, 因为.语法是编译器特性, 而id是运行时特性
虽然说id数据类型可以存储任何类型的对象,但是不要养成滥用这种通用类型的习惯
如没有使用到多态尽量使用静态类型
静态类型可以更早的发现错误(在编译阶段而不是运行阶段)
静态类型能够提高程序的可读性
使用动态类型前最好判断其真实类型
动态类型判断类型
- -(BOOL)isKindOfClass:classObj //判断实例对象是否是这个类或者这个类的子类的实例
new方法实现原理
new方法的内部会分别调用两个方法来完成3件事情:
(1)使用alloc方法来分配存储空间(返回分配的对象);
(2)使用init方法来对对象进行初始化。
(3)返回对象的首地址
构造方法
重写init方法
想在对象创建完毕后,成员变量马上就有一些默认的值就可以重写init方法
重写init方法格式:
- (instancetype)init {
if (self = [super init]) {
// Initialize self.
}
return self;
}
构造方法中instancetype的作用
instancetype与id相似,不过instancetype只能作为方法返回值,它会进行类型检查,如果创建出来的对象,赋值了不相干的对象就会有一个警告信息,防止出错。
// init此时返回值是id
NSString *str = [[Person alloc] init];
// Person并没有length方法, 但是id是动态类型, 所以编译时不会报错
NSLog(@"length = %i", str.length);
// init此时返回值是instancetype
// 由于instancetype它会进行类型检查, 所以会报警告
NSString *str = [[Person alloc] init];
NSLog(@"length = %i", str.length);
instancetype *p = [[person alloc] init];
// 错误写法instancetype只能作为返回值
自定义构造方法
有时候仅仅靠重写构造方法(初始化方法),不能满足需求。比如一个班级中不可能所有学生的年龄都一样,这时候我们需要在创建某个学生的时候能够传入这个学生的年龄。这时候就需要来自定义构造函数(初始化函数)
自定义构造方法的规范
(1)一定是对象方法,以减号开头
(2)返回值一般是instancetype类型
(3)方法名必须以initWith开头
示例:
@interface Person : NSObject
@property int age;
@property NSString *name;
// 当想让对象一创建就拥有一些指定的值,就可以使用自定义构造方法
- (id)initWithAge:(int)age;
- (id)initWithName:(NSString *)name;
- (id)initWithAge:(int)age andName:(NSString *)name;
@end
自定义类工厂方法
什么是工厂方法(快速创建方法)?
类工厂方法是一种用于分配、初始化实例并返回一个它自己的实例的类方法。类工厂方法很方便,因为它们允许您只使用一个步骤(而不是两个步骤)就能创建对象. 例如new
自定义类工厂方法的规范:
(1)一定是+号开头
(2)返回值一般是instancetype类型
(3)方法名称以类名开头,首字母小写
+ (id)person;
+ (id)person
{
return [[Person alloc]init];
}
+ (id)personWithAge:(int)age;
+ (id)personWithAge:(int)age
{
Person *p = [[self alloc] init];
[p setAge:age];
return p;
}
类的本质及内存分配
- 类的本质其实也是一个对象(类对象)
- 程序中第一次使用该类的时候被创建,在整个程序中只有一份。
- 此后每次使用都是这个类对象,它在程序运行时一直存在。
- 类对象是一种数据结构,存储类的基本信息:类大小,类名称,类的版本,继承层次,以及消息与函数的映射表等
- 类对象代表类,Class类型,对象方法属于类对象
- 如果消息的接收者是类名,则类名代表类对象
- 所有类的实例都由类对象生成,类对象会把实例的isa的值修改成自己的地址,每个实例的isa都指向该实例的类对象
因为类也是一个对象,那它也必须是另一个类的实例,这个类就是元类 (metaclass)。
- 元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有则该元类会向它的父类查找该方法,直到一直找到继承链的头。
- 元类(metaclass)也是一个对象,那么元类的isa指针又指向哪里呢?为了设计上的完整,所有的元类的isa指针都会指向一个根元类(root metaclass)。
- 根元类(root metaclass)本身的isa指针指向自己,这样就行成了一个闭环。上面说到,一个对象能够接收的消息列表是保存在它所对应的类中的。在实际编程中,我们几乎不会遇到向元类发消息的情况,那它的isa 指针在实际上很少用到。不过这么设计保证了面向对象的干净,即所有事物都是对象,都有isa指针。
- 由于类方法的定义是保存在元类(metaclass)中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。
类的加载
+load方法
- 在程序启动的时候会加载所有的类和分类,并调用所有类和分类的+load方法(只会调用一次)、
- 先加载父类,再加载子类;也就是先调用父类的+load,再调用子类的+load
- 先加载元原始类,再加载分类
- 不管程序运行过程有没有用到这个类,都会调用+load加载
@implementation Person
+ (void)load
{
NSLog(@"%s", __func__);
}
@end
@implementation Student : Person
+ (void)load
{
NSLog(@"%s", __func__);
}
@end
输出结果:
+[Person load]
+[Student load]
+initialize方法
- 在第一次使用某个类时(比如创建对象等),只会调用一次+initialize方法
- 一个类只会调用一次+initialize方法,先调用父类的,再调用子类的
@implementation Person
+ (void)initialize
{
NSLog(@"%s", __func__);
}
@end
@implementation Student : Person
+ (void)initialize
{
NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
Student *stu = [Student new];
return 0;
}
输出结果:
+[Person initialize]
+[Student initialize]
SEL类型
什么是SEL类型
SEL类型代表着方法的签名,在类对象的方法列表中存储着该签名与方法代码的对应关系
每个类的方法列表都存储在类对象中
每个方法都有一个与之对应的SEL类型的对象
根据一个SEL对象就可以找到方法的地址,进而调用方法
SEL类型的定义
- typedef struct objc_selector *SEL;
首先把test这个方法名包装成sel类型的数据
根据SEL数据到该类的类对象中,去找对应的方法的代码,如果找到了就执行该代码
如果没有找到根据类对象上的父类的类对象指针,去父类的类对象中查找,如果找到了,则执行父类的代码
如果没有找到,一直像上找,直到基类(NSObject)
如果都没有找到就报错。
注意:
在这个操作过程中有缓存,第一次找的时候是一个一个的找,非常耗性能,之后再用到的时候就直接使用。
SEL使用
定义普通的变量
如:SEL sel = @selector(show);
作为方法实参与NSObject配合使用
检验对象是否实现了某个方法
- -(BOOL) respondsToSelector: (SEL)selector 判断实例是否实现这样方法
- +(BOOL)instancesRespondToSelector:(SEL)aSelector;
BOOL flag;
// [类 respondsToSelector]用于判断是否包含某个类方法
flag = [Person respondsToSelector:@selector(objectFun)]; //NO
flag = [Person respondsToSelector:@selector(classFun)]; //YES
Person *obj = [[Person alloc] init];
// [对象 respondsToSelector]用于判断是否包含某个对象方法
flag = [obj respondsToSelector:@selector(objectFun)]; //YES
flag = [obj respondsToSelector:@selector(classFun)]; //NO
// [类名 instancesRespondToSelector]用于判断是否包含某个对象方法
// instancesRespondToSelectorr只能写在类名后面, 等价于 [对象 respondsToSelector]
flag = [Person instancesRespondToSelector:@selector(objectFun)]; //YES
flag = [Person instancesRespondToSelector:@selector(classFun)]; //NO
让对象执行某个方法
- -(id)performSelector:(SEL)aSelector;
- -(id)performSelector:(SEL)aSelector withObject:(id)object;
- -(id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
Person *p = [Person new];
SEL s1 = @selector(objectFun);
[p performSelector:s1];
SEL s2 = @selector(objectFun:);
[p performSelector:s2 withObject:@"lnj"];
SEL s3 = @selector(objectFun:value2:);
[p performSelector:s3 withObject:@"lnj" withObject:@"lmj"];
SEL s4 = @selector(classFun);
[Person performSelector:s4];
SEL s5 = @selector(classFun:);
[Person performSelector:s5 withObject:@"lnj"];
SEL s6 = @selector(classFun:value2:);
[Person performSelector:s6 withObject:@"lnj" withObject:@"lmj"];
作为方法形参
@implementation Person
- (void)makeObject:(id) obj performSelector:(SEL) selector
{
[obj performSelector:selector];
}
@end
int main(int argc, const char * argv[]) {
Person *p = [Person new];
SEL s1 = @selector(eat);
Dog *d = [Dog new];
[p makeObject:d performSelector:s1];
return 0;
}
OC方法查找顺序
1.给实例对象发送消息的过程(调用对象方法)
根据对象的isA指针去该对象的类方法中查找,如果找到了就执行
如果没有找到,就去该类的父类类对象中查找
如果没有找到就一直往上找,直到跟类(NSObject)
如果都没有找到就报错
2.给类对象发送消息(调用类方法)
根据类对象的isA指针去元对象中查找,如果找到了就执行
如果没有找到就去父元对象中查找
如果如果没有找到就一直往上查找,直到根类(NSOject)
如果都没有找到就报错