目录
-
1.面向对象
- 1.三要素
- 2.属性
-
2.深拷贝与浅拷贝
- 1.Foundation框架中的对象
- 2.自定义对象
-
3.对象等同性
- 1.NSString对象判断相等
- 2.自定义对象判断相等
-
4.KVC / KVO
- 1.KVC的过程
- 2.KVO的原理
-
5.Category
- 1.Category的使用场合
- 2.Category的实现原理
- 3.关联对象
-
6.+load和+initialize
一、面向对象
1.面向对象三个要素
1.1 封装
概念:隐藏对象的数据,不允许外界直接访问,而是需要通过相应的方法来访问和修改数据。
实现:OC使用属性(property)来封装对象中的数据。
1.2 继承
概念:通过继承子类可以得到父类属性和方法,在父类的基础上建立新的类。
实现:OC的消息机制在自己的类信息中找不到对应方法时,会通过superclass指针去父类的类信息中查找。
1.3 多态
概念:子类重写父类方法,父类指针指向子类,父类指针调用方法时会调用子类重写的方法。
实现:
Q:iOS如何实现多继承?
最符合的方式是消息转发;
其他的方式也有
- 组合:将类A和类B的实例对象作为类C的属性;
- delegate和protocol:将C类需要继承的方法以及属性在ClassA和ClassB中各自声明一份协议,C类遵守这两份协议,同时在C类中实现协议中的方法以及属性
Q:OC有重载吗?
重载说的是函数名相同,但是参数不同,OC没有。swift支持重载。
2.属性(@property)
在类定义中声明的属性,编译器会自动生成一个与该属性同名且下划线开头的成员变量,同时自动生成该实例变量的存取方法。
可以通过修改属性的修饰词来控制存取方法的生产,修饰词有四种:
- 原子性:atomic、noatomic
- 内存管理语义:copy、strong、assign、weak
- 读写权限:readonly、readwrite
- 方法名:getter=、setter=
二、深拷贝与浅拷贝
深拷贝就是内容拷贝,浅拷贝就是指针拷贝。本质区别在于:
- 是否开启新的内存地址
- 是否影响内存地址的引用计数

1.Foundation框架中的对象
对Foundation框架中的非容器对象(NSString、NSNumber)和容器对象(NSArray、NSDictionary)使用copy
与mutableCopy
的情况如下:

需要注意的是,容器对象的深拷贝其实是单层深拷贝
。
单层深拷贝:虽然为数组对象开辟了新空间,不过存放在数组的值还是原数组元素值。

实现容器对象的完全深拷贝可以使用专门的方法
- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag;
Q:为什么NSString属性用copy修饰?
因为可能会把NSMutableString对象赋值给NSString属性;
如果使用strong的话,对象外面的可变对象发生改变时,对象内部的NSString属性也会跟着变化。
而使用copy可以避免这个情况,赋值时会拷贝一份不可变副本赋值给NSString属性。
2.自定义对象
遵循 NSCopying 协议,实现- (id)copyWithZone:(NSZone *)zone
方法。
三、对象等同性
1.NSString对象判断相等
NSString中判断相等有三个方法:isEqual
、isEqualToString
、==
,它们的区别在于:
-
==
:直接比较两个对象的地址 -
isEqual
:NSString重写了该方法,首先判断两个对象的类,然后判断两个对象的内容。 -
isEqualToString
:判断两个字符串内容是否相同。
因为少了检测参数类型,所以比isEqual快。
事实上isEqual在检测完参数类型后,就调用了该函数。
2.自定义对象判断相等
重写isEqual
方法:
- 首先,直接判断两个指针是否相等;
- 接下来,判断是否都属于一个类。
- 最后,检测每个属性值是否相等。
另外:重写了isEqual方法,那么也必须重写hash
方法。因为如果两个对象相等,那么它们也必须有相同的哈希值。不过反之则不成立。
@interface HJPerson : NSObject
@property (nonatomic, copy) NSString* firstName;
@property (nonatomic, copy) NSString* lastName;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation HJPerson
- (BOOL)isEqual:(id)other{
if (self == other) return YES;
if ([self class] != [other class]) return NO;
HJPerson* otherPerson = (HJPerson*)other;
if (![self.firstName isEqualToString:otherPerson.firstName]) return NO;
if (![self.lastName isEqualToString:otherPerson.lastName]) return NO;
if (self.age != otherPerson.age) return NO;
return YES;
}
- (NSUInteger)hash{
NSUInteger firstNameHash = [self.firstName hash];
NSUInteger lastNameHash = [self.lastName hash];
NSUInteger ageHash = self.age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
@end
四 、KVC / KVO
1.KVC的过程
(1)按照命名规范,先查找相关的方法,如果没有找到;
(2)按照命名规范,找相关的成员变量,如果没有找到;
(3)调用setValue:forUndefinedKey;
(4)如果都没有找到相关的方法实现就抛出异常。


2.KVO实现机制
一个类添加观察者后,runtime创建了一个该类的派生类!实例对象的isa指向这个派生类。如果该类有setter方法,在派生类中会重写了setter方法。

重写的setter函数的实现大概是这样:

然后在didChangeValueForKey中调用观察者的observeValueForKeyPath。
Q:如何手动触发KVO?
(1)关掉属性的自动触发
(2)在setter方法中,手动调用willChangeValueForKey和didChangeValueForKey
五、Category
1.Category的使用场合
- 将类拆解为几个模块
- 为现有的类添加一些方法
2.Category的实现原理

Category的底层结构其实是struct _category_t 结构体,里面存储着Category的方法、属性、协议等信息。在运行时、runtime初始化的时候,会将category的数据,合并到类信息中。
Q:Category和Extension的区别是什么?
- Extension的数据在编译时就合并到类信息中了。
- Category的数据在运行时才会合并到类信息中。
Q:类和分类中的方法名相同,会调用哪个方法?
-
对于类中和分类中的同名方法,会优先调用分类的方法,因为分类的方法会放在原来的方法前面。
-
对于多个分类中的同名方法,则需要看编译顺序。最后面编译的分类,其方法会放在前面。
注:核心源代码attachCategories()、attachLists()。

3.关联对象
Category不能添加成员变量,关联对象用于给Category“添加成员变量”。
关联对象的API:
-
添加关联对象:void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy) -
获得关联对象:id objc_getAssociatedObject(id object, const void * key)
-
移除所有的关联对象:void objc_removeAssociatedObjects(id object)
关联对象的原理:

关联对象是使用哈希表实现的,将被关联的对象作为key,映射到一个子哈希表上。然后再通过变量名找到值。而不是将变量放在被关联的对象中。
Q:Category能否添加成员变量?为什么?
不能。因为Category的底层数据结构,它的结构体中没有成员变量只有属性。
六、+load和+initialize
1.+load方法
+load方法会在这个类加载到内存中被调用。或者说在runtime加载类、分类的时候调用。
注意:
- 就算在mian.h中没有导入这个类或者没有使用这个类,+load方法也会调用。
- +load方法在被runtime自动调用时,是直接取出+load方法,而不是经过objc_msgSend,所以不遵循继承规则,类和分类的load方法都会被调用。
MJPerson +load
MJStudent +load
MJPerson (Test1) +load
MJPerson (Test2) +load
MJStudent (Test1) +load
MJStudent (Test2) +load
2.+initialize方法
+initialize方法是在类第一次发送消息的时候调用。
网友评论