类的定义
- 类名需要大写开头
- 属性开头下划线
@inteface 类名: NSObject {
@public // 外部可以访问
NSString *_name;
int _age;
}
@end
类加载
- 程序运行期间,当类被第一次访问,会将其存储到代码段区域,叫做类加载
- 直到程序结束才会被释放
对象在内存中存储
栈 | 堆 | 代码段 |
---|---|---|
指针变量 | 对象(属性) isa 指针 | 类 |
- 在栈内存中申请一块空间存放指针变量
- 在堆内存创建一块大小合适的空间,根据类的模板创建对象
- 对象会有
isa
指针指向代码段的对象所属的类 - 初始化对象的属性,如果是基本数据类型为零,
C语言
指针为NULL
,OC的类指针类型为nil
- 访问对象属性:
指针名->属性名;
- 调用方法:
[指针名 方法名];
- 在栈根据指针名找到在堆的对象,发现对象调用方法,再根据对象的isa指针在代码段找到类,然后调用类里面的方法
- 每个对象的方法代码实现一样,没必要为每个对象保存一个方法,浪费空间,所有将其存在代码段里面
- 代码段中每个类都有
isa
指针,指向他的父类,父类指向他的父类,最后指向元类
对象作为参数或者返回值
- (void)test:(Dog *)dog;
- (Dog *)test:(Dog *)dog;
- 本质是地址传递
- instancetype作为返回值指返回类型为该类
对象方法
- 对象方法需要创建对象 ,可以直接调用类方法
- (返回值类型)方法名;
类方法
- 类方法不依赖对象,直接通过类名调用
- 优点:节约空间,提高效率,跳过堆,直接执行类中的类方法
- 缺点:不能直接访问属性,也不能通过
self
直接调用当前类的其他的对象方法,但是可以在类方法创建对象并访问属性,
// 声明
+ (返回值类型)方法名;
// 调用
[类名 类方法名]
- 类方法的规范:如果要求这个类提供一个和类同名的类方法,这个方法创建一个最纯洁的对象返回
- 类方法和对象方法可以重名
对象方法中的self
self
是一个指针,在对象方法中self
指向当前对象,在类方法中指向当前类- 如果在方法中存在和属性同名的局部变量,访问局部变量直接写,访问对象同名属性,必须用
self
- 在对象方法中调用当前对象其他对象方法,必须使用
self
匿名对象
- 没有名字的对象,没有指针存储其地址,只能使用一次,每次创建都是不同的对象
new
实际上是一个类方法,可以创建匿名对象
面向对象的三大特征(封装、继承、多态)
封装
- 屏蔽内部实现,方便操作,后期维护方便
- 实现方法:去掉
@public
,方法名需要set/get
开头,首字母大写,去掉下划线
对象之间的关系
- 组合关系:一个类是由其他几个类组合起来的
- 依赖关系:一个对象的参数是另一个对象
- 关联关系:一个类拥有另外一个类
继承
- 继承后拥有父类所有成员,属性,方法
- 不继承默认
NSObject
- 只能有一个父类,不能同时继承多个
- 子类不能再定义一个同名的属性
@interface 类名 :父亲类的名称
@end
A从B继承,A是子类,B是父类
A从B派生,A是派生类,B是基类
重写
直接在类的实现中将方法重写
多态
同一个行为,不同事物不同表现
- 通过继承+重写+里氏替换原则
- 或者通过设计模式
里氏替换原则
子类可以替换父类的位置,并且程序并不受影响
// 里氏替换原则
Father *p = [Son new];
- 1个指针不仅可以存储本类对象的地址,还可以存储子类对象的地址
NSOject
类型指针可以存储任意OC
对象的地址- 数组元素是
OC
指针类型,这个数组可以存储子类和父类对象 - 数组元素是
NSObject
指针类型,任意OC
对象都可以存储 - 当父类指针指向子类对象的时候,这个父类指针只能去调用子类对象中的父类成员,子类独有的无法访问,如果重写则是重写完的方法
- 如果指向子类对象的独有方法,就必须做类型转换
NSObject *obj1 = [Person new];
// 强制转换数据类型
[(Person *)obj1 sayHi];
NSObject类
NSObject
类是Foundation
框架的类,这类有个类方法new
,这个方法是用来创建对象,方法的返回值是创建这个对象的指针,这个类有个属性叫做isa
指针
NSObject
类是所有OC
类的基类,根据LSP
NSObject
指针就可以指向任意OC
对象,所有NSObject
指针是一个万能指针
id指针
万能指针,可以指向任意的
OC
对象
id
是一个typedef
自定义类型,在定义的时候已经加了*
,所有声明不需要再加
id
指针不能用点语法
id
指针和NSObject
指针的相同点:万能指针,都可以执行任意的OC
对象id
指针和NSObject
指针的不同点:NOSject
指针调用对象方法,编译器会检查,而id
指针不会,故正常用id
指针
/// 可被继承的创建自身类对象
+ (id)newself {
return [self new];
}
instancetype类型
方法的返回值是
instancetype
,代表方法的返回值是当前这个类的对象
id
和instancetype
的区别:instancetype
只能做方法的返回值,不能在别的地方使用,id既可以声明指针变量,也可以作为参数和返回值
Super关键字
- 只可以用在类方法和对象方法中,不可以调用属性
- 在对象方法和类方法中可以使用
super
关键字调用当前对象从父类继承过来的对象方法,也可以通过self
来调用
访问修饰符
@private // 私有的,只能在本类的方法实现中访问,子类继承属性但不能直接访问,但可以调用父类的方法
@protected // 受保护的,只能在本类和子类中的方法访问
@package // 可以在当前框架中访问
@public // 可以在任意地方访问
- 不指定默认为
protected
- 作用域是修饰符到另一个修饰符为止
- 修饰符只能修饰属性,不能修饰方法
私有属性
@implementation {
属性 // 真正的私有属性,修饰符无效,外界不显示
}
私有方法
方法不写声明只写实现,只能在本类中的其他方法调用,不能被外界调用
@class的使用
- 当两个类相互包含的时候,
Person.h
中包含Book.h
,而Book.h
又包含Person.h
的时候,就会出现循环引用的问题,无限递归导致编译无法通过 - 解决方案:其中一边不要使用
#import
引入对方的头文件,而是使用@class
类名;来标注这是一个类,在.m文件中再引入对方的头文件
#import
是将指定文件的内容拷贝到写指令的地方
@class
并不会拷贝任何内容,只是告诉编译器这个一个类
结构体和类的区别
- 结构体和类都能将多个数据封装为一个整体
- 结构体只能封装数据,类还能封装行为
- 结构体分配在栈空间,空间小但是访问效率高
- 对象分配在堆空间,空间大但是访问效率低
方法的本质是SEL
SEL
全称叫做selector
选择器
SEL
是一个数据类型,需要在内存中申请空间存储数据
SEL
是一个类,SEL
对象是用来存储一个方法
SEL
是一个typedef
类型,自定义时候已经加*
了,声明时不需要再加
创建一个SEL
对象 -> 将方法的信息存储在这个SEL
对象之中 -> 再将这个SEL
对象作为类对象的属性
// 获取SEL对象
SEL s1 = @selector(方法名);
// 发送SEL消息
[p1 performSelector:s1];
// 等价于
[p1 方法名]
OC
最重要的机制:消息机制- 调用方法的本质其实就是为对象发送
SEL
消息
动态类型检测
// 检测是否有这个对象方法(常用)
-(BOOL)respondsToSelector:(SEL)aSelector;
// 检测是否有这个类方法
-(BOOL)instanceRespondToSelector:(SEL)aSelector;
// 判断对象是否为指定类的对象或者子类的对象
-(BOOL)isKindOfClass:(Class)aClass;
// 判断对象是否为指定类的对象,不包括子类对象
-(BOOL)isMemberOfClass:(Class)aClass;
// 判断类是否为指定类的子类
+(BOOL)isSubclassOfClass:(Class)aClass;
Person *p1 = [Person new];
BOOL b1 = [p1 respondsToSelector:@selector(length)];
BOOL b2 = [p1 isKindOfClass:[Person class]];
BOOL b3 = [p1 isMemberOfClass:[Person class]];
new方法
new
方法的内部是先调用alloc
方法,再调用init
方法alloc
方法是一个类方法,一个类调用这个方法 就创建那个类的对象,并把对象返回init
方法是一个对象方法,作用是初始化对象
Person *p1 =[Person new];
// 等价于
Person *p1 = [[Person alloc] init];
重写init方法规范
init
方法是一个对象方法,作用是初始化对象
- (instancetype)init {
// 调用父类初始化
self = [super init];
// 检查初始化是否成功 if(self)也可以
if (self != nil) {
self.name = @"jack";
}
// 返回初始化的对象
return self;
}
重写构造方法
initWith
开头的函数才能是构造函数
- (instancetype)initWithName:(NSString *)name andAge:(int)age {
if(self = [super init]) {
self.name = name;
self.age = age;
}
return self;
}
// 调用
Dog *d1 = [[Dog alloc] initWithName:@"huang" andAge:2];