一、
创建一个对象,内存是如何分配
1). 子类对象中有自己的属性和所有父类的属性
2). 代码段中每一个类都有一个isa指针,这个指针指向它的父类.-
结构体与类
- 相同点: 都可以将多个数据封装为1个整体
struct Data{ int year; int month; int day; } @interface Data : NSObject{ int year; int month; int day; }
2). 不同点:
a. 结构体只能封装数据,而类不仅可以封装数据还可以封装行为
b. 结构体变量分配在栈空间(前提是局部变量),而对象分配在堆空间.
栈空间相对较小,但是访问效率高;
堆空间较大, 访问效率低.3). 应用场景:
a. 如果表示的实体不仅有数据还有行为,只能使用类
b. 如果表示的实体只有数据, 没有行为,视属性多少而定.如果属性只有几个,就定义为结构体; 如果属性较多,就定义为类. -
类的本质
栈
堆
BSS段
数据段
代码段
代码段是用来存储代码的.
类加载,当类第一次被访问的时候,这个类就会被加载到代码段存储起来.
类一旦被加载,是不会被回收的,除非程序结束.1). 任何存储在内存中的数据都有1个数据类型.
2). 在代码段存储类的步骤
a. 先在代码段中创建Class对象, Class是Foundation框架中的一个类
b. 将类的信息存储在这个Class对象之中
Class对象至少有三个属性:
类名/属性s/方法s
存储类的这个Class对象,叫做类对象
所以存储类的类对象也有一个isa指针 -
类对象使用
1). 调用类的类方法class,就可以了得到存储类的类对象的地址
Class c1 = [Person class];
2). 调用对象的对象方法,就可以了得到这个对象所属的类的Class对象的地址.
Person p1 = [Person new];
Class c2 = [p1 class];
注意: 声明Class指针的时候,不需要加,因为typedef的时候就已经加*了.
3). 拿到存储类的类对象以后
Class c1 = [Person class];
c1对象就是Person类, c1 完全等价于Person
a. 使用类对象来调用类的类方法,
Class c1 = [Person class];
[c1 sayHi] 等价于 [Person sayHi];
b. 创建对象
Class c1 = [Person class];
[c1 new] 等价于 [Person new];- 注意: 使用类对象,只能调用类的类方法,不能调用对象方法
SEL全程 select 选择器
SEL是一个数据类型. 要在内存中申请空间存储数据,SEL其实是一个类,SEL对象用来存储一个方法
1). 类是以Class对象的形式存储在代码段中
类名: 存储这个类的类名.NSString
a. 如何将方法存储在类对象之中
a). 先创建一个SEL对象
b). 将方法的信息存储在这个SEL对象之中
c). 再将这个SEL作为Class对象的属性
2). 拿到存储方法的SEL对象
a. 因为SEL是typedef类型的,在自定义的时候已经加了,所以声明SEL指针的时候不需要加
b. 取到存储方法的SEL对象
SEL s1 = @selector(方法名);
示例:
SEL s1 = @selector(sayHi);
3). 调用方法的本质
Person p1 = [Person new];
[p1 sayHi];
内部原理:
a. 先拿到存储sayHi方法的SEL对象,也就是拿到sayHi方法的SEL数据,SEL消息
b. 将这个SEL消息发送给p1对象
c. p1对象接收到这个SEL消息之后,就知道调用方法
d. 根据对象的isa指针找到存储类的类对象
e. 如果找到这个类对象之后,搜寻是否有和传入的SEL数据相匹配的,如果有,就执行,如果乜有就找父类,知道NSObject
OC最重要的1个机制: 消息机制
调用方法的本质其实就是为对象发送SEL消息
[p1 sayHi]: 为p1对象发送一个sayHi方法
4). 手动为对象发送SEL消息
a. 先得到方法的SEL数据
b. 把这个SEL数据发送给p1对象
- (id)performSelector:(SEL)aSelector;
示例:Person *p1 = [Person new]; SEL s1 = @selector(sayHi); [p1 performSelector:sel];
c. 调用一个对象的方法有两种
a). [对象名 方法名];
b). 手动的为对象发送SEL消息
5). 注意事项:
a. 如果方法名有参数 那么方法名是带了参数的
b. 如果方法有参数,就调用另外一个方法
- (id)performSelector:(SEL)aSelector withObject:(id)object;
c. 如果方法有多个参数,就将对象传递过去点语法
OC中也可以使用点语法来访问对象的属性,和Java,C#是完全不一样的.
1). 使用点语法访问对象的属性.
语法:对象名.去掉下划线的属性名p1.name = @"jack";
这个时候就会将@"jack"赋值给p1对象的_name属性;NSString *name = p1.name;
把p1对象的_name属性值取出来
2). 点语法原理
p1.age = 18;
这句话的本质并不是把18直接赋值给p1对象的_age属性,点语法在编译器编译的时候,会将点语法转换为调用setter/getter的代码
a. 当使用点语法赋值的时候,这个时候编译器会将点语法转换为调用setter方法的代码.
对象名.去掉下划线的属性名 = 数据;
转换为:
[对象名 set去掉下划线的属性名首字母大写] = 数据;
p1.age = 10相当于[p1 setAge:10];
b. 当使用点语法取值的时候,这个时候编译器会将点语法转换为调用getter方法的代码.
对象名.去掉下划线的属性名;
转换为:
[对象名 去掉下划线的属性名];
int age = p1.ag相当于[p1 age];
3). 注意:
a. 在getter/setter方法中慎用点语法, 因为有可能会造成无线递归导致程序崩溃
b. 点语法在编译器编译的时候会转换为setter/getter方法的代码.
如果我们的setter和getter方法名不符合规范,那么就会报错.
c. 如果属性没有封装getter/setter是无法使用点语法的@property
1). 写一个类
a. 写属性
b. 声明属性的getter/setter
c. 再实现getter/setter
2). @property
a. 作用: 自动生成getter/setter的声明,应该写在@interface声明之中
b. 语法:
@property 数据类型 变量名;
@property int age;
c. 原理:
编译器在编译的时候,会根据@property生成getter/setter方法的实现
@property 数据类型 名称;
生成为:
- (void)set首字母大写的名称:参数;
- (数据类型)名称;
示例:
@property int age;
- (void)setAge:(int)age;
- (int)age;
3). 使用@property注意:
a. @property的类型和属性的类型一致.
@property的名称和属性名称一致(去掉下划线).
b. @property的名称决定了生成getter和setter方法的名称.
@property的数据类型决定了生成的getter和setter方法的数据类型.
c. @property只是生成getter和setter方法的声明,实现要自己写.-
@synthesize
1). 作用: 自动生成getter/setter方法的实现,应该写在类的实现当中
2). 语法:
@synthesize @property名称;// 声明 @interface Person : NSObject{ int _age; } @property int age; @end; // 实现 @implementation Person @synthesize age; @end
3). @synthesize做的事情
a. 生成一个真私有的属性,属性的类型和@synthesize对应的@property类型一致,属性的名字和@synthesize对应的@property名字一致.
b. 自动生成setter方法的实现
实现的方式:将参数直接赋值给自动生成的那个私有属性.并且没有做任何的逻辑验证
c. 自动生成getter方法的实现
4). 希望@synthesize不要自动生成私有属性,getter/setter的实现中操作我们写好的属性.
a. 语法
@synthesize @property 名称 = 已经存在的属性名;
示例: @synthesiez age = _age;
a). 不会再生成私有属性
b). 直接生成getter/setter的实现.
setter实现: 把参数的值直接赋值给指定的属性.
getter的实现: 直接返回指定的属性的值.
5). 注意:
a. 如果直接写一个@synthesize
@synthesize name;
b. 如果指定操作的属性.
@synthesize name = _name;
c. 生成的setter方法实现中没有任何逻辑验证,生成的getter方法的实现中式直接返回属性的值
d. 如果要有自己的逻辑验证,需要自己实现.
6). 批量声明
a. 如果多个@property的类型一致,可以批量声明.
@property float height,weight;
b. @synthesize批量写, @synthesize name = _name, age = _age; @property增强
1). 从4.4之后, 对@property做了增强.
2). 只需要写一个@property,编译器就会自动的生成私有属性、生成getter/setter的声明、生成getter/setter的实现.
3). @property NSString *name;
a. 自动的生成一个私有属性, 属性的类型和@property类型一致,属性的名称和@property的名称一致,属性的名称自动的加下划线
b. 自动的生成getter/setter的声明
c. 自动的生成getter/setter的实现
setter的实现直接将参数的值赋值给自动生成的私有属性.
getter的实现直接返回私有属性的值.
4). 使用注意
a. @property的类型一定要和属性的类型一致,名称要和属性的名称一致
b. 可以批量声明-
动态类型和静态类型
1). OC是一门若语言,编译器在编译的时候,检查的时候没有那么严格.
优点:灵活
缺点:太灵活强类型的语言: 编译器在编译的时候做语言检查的时候,非常严格.
2).
a. 静态类型:指的是一个指针指向的对象是一个本类对象,
b. 动态类型: 指的是一个指针指向的对象不是本类对象。
3). 编译检查:编译器在编译的时候,能不能通过1个指针去调用指针指向的对象的方法.
判断原则:看这个指针所属的类型之中是否有这个方法,如果有就认为可以调用,编译通过,如果没有就报错。
4). 运行检查: 编译检查只是骗过了编译器,但是这个方法究竟能不能执行还不一定,运行时会去检查对象当中是否有这个方法 NSObject是OC中所有类的基类,根据LSP NSObject指针就可以指向任意的OC对象,所有NSObject指针时一个万能指针.可以指向任意的OC对象.
缺点: 如果要调用指向的子类对象的独有的方法,就必须要就类型的强转。id指针是一个万能指针,可以指向任意的OC对象.
1). id是一个typedef类型,在定义的时候已经加了,所以声明id指针的时候就不需要加了
2). id指针是一个万能指针,任意的OC对象都可以指。
3). NSObject与id
相同点: 万能指针,都可以指向任意的OC对象
不同点: 通过NSObject指针去调用对象的方法的时候,编译器会做编译检查.通过id类型的指针去调用对象方法的时候,不会报错.
4). 注意:id指针只能调用方法,不能使用点语法instancetype
1). 如果方法的返回值是instancetype代表方法的返回值是当前类的对象.// 声明 - (instancetype)person; // 实现 - (instancetype)person{ return [self new]; }
建议:
a. 如果方法内部是在创建当前类的对象,不要写死成类名[类名 new],而是用self代替类名
b. 如果方法的返回值是当前类的对象,也不要写死了,而是写instancetype.
2). id和instancetype的区别
a. instancetype只能作为方法的返回值,不能在其他地方使用,id既可以声明指针变量也可以作为参数,也可以作为返回值
b. instancetype是一个有类型的代表当前类的对象,id是一个无类型的指针,仅仅是一个地址,没有类型的指针-
构造方法
1). 创建对象
类名 *指针名 = [类名 new];
new 实际上是个类方法.
new方法作用:
-> 创建对象
-> 初始化对象
-> 把对象的地址返回
new方法的内部,其实是先调用alloc方法,再调用init方法
alloc方法是一个类方法,那个类调用这个方法就创建哪个类对象
init是一个对象方法,初始化对象
创建对象的完整步骤:
应该是先使用alloc创建一个对象,然后使用init初始化这个对象
Person *p1 = [Person new];
完全等价于
Person *p1 = [[Person alloc] init];2). init方法
作用: 初始化对象,为对象的属性赋初始值,这个init方法我们叫做构造方法
init方法做到的事情: 初始化对象.
3). 想要让创建对象的属性的默认值不是nil,NULL,0,那么我们可以重写init方法.按照我们自己的想法为对象的属性赋值.
重写init方法规范:
a. 必须先调用父类的init方法.
b. 调用方法初始化对象失败返回nil
c. 判断父类是否初始化成功,如果不是nil,说明初始化成功
d. 如果初始化成功,就初始化当前对象的属性
e. 最后返回self- (instancetype)init{ // 初始化父类属性的值 self = [super init]; if(self){ self.name = @"jack"; } return self; }
4). 自定义构造方法
规范:
a. 返回值必须是instancetype
b. 自定义构造方法开头必须是initWith
c. 方法的实现与init的要求一样- (instancetype)initWithName:(NSString *)name{ // 初始化父类属性的值 self = [super init]; if(self){ self.name = name; } return self; } Dog *d1 = [[Dog alloc] initWithName:@"小黄"];
-
动态类型检测
1). 判断指针指向的对象当中,指定的方法是否可以调用Person *p1 = [Person new]; BOOL isHave = [p1 responseToSelector:@selector(setName:)];
2). 判断类方法是否可以调用
3). 判断对象是否为指定类对象或者子类对象NSMutableString *str = [NSMutableString class]; BOOL res = [str isKindOfClass:[NSString class]];
4). 判断对象是否为指定类的对象(不包括子类)
NSMutableString *str = [NSMutableString class]; BOOL res = [str isMemberOfClass:[NSString class]];
5). 判断指定的类是否为另外一个类的子类
BOOL res = [NSMutableString isSubclassOfClass:[NSString class]];