1.分类相关
(1)特点:
- 运行时决议
在编好分类文件之后,它并没有将分类当中对应添加的内容附加到相应的宿主类中,实际上宿主类中还没有分类中方法,而是在运行时,通runtime将分类中添加的内容添加到宿主类中。 - 可以为系统类添加分类
(2)实战
分类中可以添加哪些内容?
- 实例方法
- 类方法
- 协议
- 属性(我们在分类中定义一个属性,实际上只是生成了对应的get方法和set方法,并没有为我们在分类中添加实例变量)
你用分类都做了哪些事情?
- 声明私有方法
- 分解体积庞大的类文件
- 把Framework的私有方法公开
(3)分类的结构体
(4)分类加载调用栈
static void remethodizeClass(Class cls){
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
/*
我们只分析分类当中实例方法添加的逻辑
因此在这里我们假设 isMeta = NO
*/
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
// 获取cls中未完成整合的所有分类
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}}
static void attachCategories(Class cls, category_list *cats, bool flush_caches){
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
/*
我们只分析分类当中实例方法添加的逻辑,
因此在这里我们假设isMeta = NO
*/
bool isMeta = cls->isMetaClass();
/*
二维数组 [[method_t, method_t,...],[method_t, method_t,...], [method_t, method_t,...],...]
*/
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {//这里是倒叙遍历,最先访问最后编译的分类
// 获取一个分类
auto& entry = cats->list[i];
// 获取该分类的方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//最后编译的分类最先添加到分类数组中
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//属性列表添加规则 同方法列表添加规则
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 添加方法
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
// 添加属性
rw->properties.attachLists(proplists, propcount);
free(proplists);
// 添加协议
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
如果两个分类中都有一个同名的方法,最后编译的分类当中的同名方法才会最终生效。
2.关联对象
能否给分类添加"成员变量" ?
id objc_getAssociatedObject(id object,const void * key)
void objc_setAssociatedObject(id object,const void *key,id value,objc_AssociationPolicy policy)
void objc_removeAssociatedObjects(id object)
关联对象的本质
关联对象由AssociationsManager
管理并在AssociationsHashMap
存储,所有对象的关联内容都在同一个全局容器
中。
3.扩展属性
(1) 特点
- 编译时决议
- 只以声明的形式存在,多数情况下寄生于宿主类.m中。
- 不能为系统类添加扩展。
(2) 一般用扩展做什么?
- 声明私有属性
- 声明私有方法
- 声明私有成员变量
4.代理
准确的说是一种软件设计模式,iOS当中以@protocol形式体现,传递方式一对一。
工作流程
注意点
一般声明为weak以规避循环引用。
5.通知
概念:
通知是使用观察者模式
来实现的用于跨层传递消息的机制,传递方式为一对多
。
如何实现通知机制?
6.KVO
- KVO是Objective-C对观察者设计模式的一种实现
-
apple使用了isa混写(isa-swizzling)来实现KVO
- 使用setter方法改变值kvo才会生效。
- 使用setValue:forkey:改变KVO才会生效
- 成员变量直接修改需求手动添加KVO才会生效。
#import <Foundation/Foundation.h>
@interface MObject : NSObject
@property (nonatomic,assign)int value;
- (void)increase;
@end
#import "MObject.h"
@implementation MObject
- (instancetype)init{
self = [super init];
if(self){
_value = 0;
}
return self;
}
- (void)increase{
_value = _value + 1;
}
@end
#import <Foundation/Foundation.h>
@interface MObserver : NSObject
@end
#import "MObserver.h"
#import "MObject.h"
@implementation MObserver
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if([object isKindOfClass:[MObject class]] && [keyPath isEqualToString:@"value"]){
NSNumber *valueNum = [change valueForKey:NSKeyValueChangeNewKey];
NSLog(@"value is %@",valueNum);
}
}
@end
打印结果
NSKVONotifying_A的setter实现
- (void)setValue:(id)value forKey:(NSString *)key{
[self willChangeValueForKey:@"keyPath"];
//调用父类实现,也即原类的实现
[super setValue:value forKey:key];
[self didChangeValueForKey:@"keyPath"];
}
问题
- 通过kvc设置value能否生效?
2.通过成员变量直接赋值value能否生效?
7.KVC
苹果系统为我们提供的一种键值编码技术。
(1) -(id)valueForKey:(NSString *)key
a.Accessor Method
- <getKey>
- <key>
- <isKey>
b.Instance var
- _key
- _isKey
- key
- isKey
(2)-(void)setValue:(id)value forKey:(NSString *)key
8.属性关键字
- 读写权限
- 原子性
- 引用技术
(1)读写权限
- readonly
- readwrite
(2)原子性
- atomic (赋值和获取是线程安全的)
例如一个数组使用atomic关键字,对数组进行赋值和获取是线程安全的,但是对数组进行添加数据,删除数据,不是线程安全的 - nonatomic
(3)引用计数
retain/strong
assign/unsafe_unretained
weak
-
copy
assign
- 修饰基本数据类型,如int , BOOL等
- 修饰对象类型时,不改变其引用计数
- 会产生悬垂指针(assign所修饰的对象被释放后,assign指针仍然指向原对象内存地址,这时通过assign继续访问对象,由于悬垂指针的存在,会导致程序内存泄漏或者程序异常)
weak
- 不改变修饰对象的引用计数。
- 所指对象在被释放之后,weak指针会自动置为nilcopy
(a) 浅拷贝
特点:1.增加被拷贝对象的引用计数 2.并没有发生新的内存分配
浅拷贝就是对内存地址的复制,让目标对象指针和原对象指针指向同一片内存空间。
(b)深拷贝
特点: 1.深拷贝不会增加被拷贝对象的引用计数 2.深拷贝产生了内存分配
深拷贝让目标对象指针和源对象指针指向两片内容相同的内存空间。
(c)深拷贝vs浅拷贝- 是否开辟了新的内存空间
- 是否影响了引用计数
copy关键字总结
:可变对象的copy和mutableCopy都是深拷贝,不可变对象的copy是浅拷贝,mutableCopy是深拷贝,copy方法返回的都是不可变对象,
(d)注意点@property(copy)NSMutableArray*array ?
不论赋值过来的是NSMutableArray 还是NSArray,copy之后都是NSArray,这样对array进行添加元素或者是删除元素会导致程序异常。
9.经典题目
MRC下如何重写retain修饰变量的setter方法?
@property(nonatomic,retain) id obj;
- (void)setObjc:(id)obj{
if(_obj != obj){
[_obj release];
[_obj retain];
}
}
请简述分类的实现原理?
分类的实现是由运行时决议的, 不同分类中含有同名分类方法,谁最终生效取决于谁最后参与编译,最后参与编译的分类当中的同名分类方法会最终生效,如果分类中添加的方法与宿主类方法名相同,分类方法会覆盖同名的宿主方法,这里说的覆盖是指,由于消息传递过程中,优先查找数组靠前的方法,如果找到了一个同名方法就进行调用,实际上宿主类的同名方法是仍然存在?
KVO实现原理?
能否为分类添加成员变量?