Category
- 你用分类都做了哪些事情?
- 声明私有方法
- 分解体积庞大的类文件
- 把Framework的私有方法公开
- 特点
- 在运行时决议,也就是在编译时并没有把Category中声明的内容添加到宿主类中,而是在运行的时候通过runtime将添加的方法添加到宿主类上面
- 可以为系统添加分类
- 分类中可以添加哪些内容?
- 实例方法
- 类方法
- 协议
- 属性
- Category结构
struct category_t {
const char *name; //分类名称
classref_t cls; //分类所属的宿主类
struct method_list_t *instanceMethods; //实例方法列表
struct method_list_t *classMethods; //类方法列表
struct protocol_list_t *protocols; //协议
struct property_list_t *instanceProperties; //实例属性列表
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
- Category方法添加源码
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
/*二维数组
*/
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;
}
}
//获取随著类当中的rw数据,其中包含宿主类的方法列表信息
auto rw = cls->data();
//主要针对分类中有关于内存管理想干方法情况下的一些特殊处理
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
/*
rw代表类
methods代表类的方法列表
attachLists 方法的含义是 将含有没count个元素的mlists拼接到rw的methods上
*/
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);
}
关联对象
-
能否给分类添加“成员变量”?
- 能通过关联对象的方式给分类添加成员变量
//设定一个value值,通过key设定和value的映射关系,通过policy策略关联到对象上面 objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) //根据指定的key到object中获取和key相对应的关联值 objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key) //根据指定对象移除它所有的关联对象 objc_removeAssociatedObjects(id _Nonnull object)
- 通过关联对象为分类添加的"成员变量"由AssociationsManager统一管理全部存储在AssociationsHashMap中,所有类的关联对象都存在同一个全局容器中
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { // retain the new value (if any) outside the lock. ObjcAssociation old_association(0, nil); //根据policy的值对value进行copy或者retain操作 id new_value = value ? acquireValue(value, policy) : nil; { //关联对象管理类,C++实现的一个类 AssociationsManager manager; //获取其维护的一个HashMap AssociationsHashMap &associations(manager.associations()); disguised_ptr_t disguised_object = DISGUISE(object); if (new_value) { // break any existing association. //根据对象指针查找对应的一个ObjectAssociationMap结构的map AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // secondary table exists ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; j->second = ObjcAssociation(policy, new_value); } else { (*refs)[key] = ObjcAssociation(policy, new_value); } } else { //没找到关联值的时候进行添加 // create the new association (first time). ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); } } else { //当value传为空时则对该关联进行擦除操作 // setting the association to nil breaks the association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); //擦除操作 } } } } // release the old value (outside of the lock). if (old_association.hasValue()) ReleaseValue()(old_association);
}
```
扩展(Extension)
- 作用
- 声明私有属性,可以不被子类继承
- 声明私有方法
- 声明私有成员变量
- 特点
- 编译时决议
- 只以声明的形式存在,多数情况下寄生于宿主类的.m中来进行声明方法的实现
- 不能为系统类添加扩展
代理(Delegate)
-
准确的说是一种设计模式
-
iOS当中以@protocol形式体现
-
传递方式一对一
-
怎么声明必须实现方法和选择实现方法
- 被标记为@required为必须实现的
- 被标记为@optional为可选实现的
-
容易出现循环引用,所以委托方需要通过weak关键字来弱化引用
通知(NSNotification)
- 通知是使用观察者模式来实现、用于跨层传递消息的机制
- 通知和代理的区别
- 代理是通过代理模式实现的,通知是通过观察者模式实现的
- 代理是一对一,通知是一对多
- 通知的实现流程
- 发送者将发送的消息发送到通知中心
- 通知中心广播给观察者
- 通知的实现机制(代码未开源,猜测)
- 底层应该维护一个Map表来存储注册的通知,同事每个通知下面设有观察者列表,当收到消息后则给观察者列表中的观察者发送消息
KVO
- Key-value observing的缩写
- 是OC对观察者设计模式的又一实现
- Apple使用了isa混写(isa-swizzling)来实现KVO(派生出一个名为NSKVONotifying_(ObjName)的子类并重写其setter方法)
- KVO触发的问题
- 通过外部直接修改属性可能(比如属性名设置isAge,通过key=age进行kvc设置)可以触发KVO监听方法(调动setter方法)
- 通过KVC修改也可以触发KVO的回调
- 通过直接修改成员变量无法触发KVO监听,需手动添加监听
//直接为成员变量赋值
[self willChangeValueForKey:@"value"];
_value += 1;
[self didChangeValueForKey:@"value"];
KVC(键值编码技术)
- Key-value coding的缩写
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKey:(NSString *)key;
- 通过键值编码技术可以修改对象同名成员变量的值,其中key是没有任何限制的,在外界可以通过key进行私有变量的修改,破坏了面向对象的思想方法(把对象行为属性结合为一个整体,隐藏内部实现,吧不想告诉别人的东西隐藏起来)
- KVC调用流程
- 若存set(key)的方法则优先调用该方法
- 如果不存在set(key)方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,如果返回yes则按照会按照_key,_iskey,key,iskey的顺序搜索成员变量,如果返回NO则调用- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法抛出异常
// 如果开发者想让这个类禁用KVC里,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set<Key>:属性名时,会直接用setValue:forUndefinedKey:方法。如果返回YES则按照会按照_key,_iskey,key,iskey的顺序搜索成员变量
+ (BOOL)accessInstanceVariablesDirectly
{
return YES;
}
//setValue:forKey:通过_key,_iskey,key,iskey的顺序搜索成员变量查找仍未找到对应的key时调用该方法
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSLog(@"%@", key);
}
//valueForKey:通过_key,_iskey,key,iskey的顺序搜索成员变量查找仍未找到对应的key时调用该方法
- (id)valueForUndefinedKey:(NSString *)key
{
NSLog(@"%@", key);
return [super valueForUndefinedKey:key];
}
属性关键字
- 原子性
- atomic修饰数组时对数组进行赋值和获取时是安全的,但是对数组进行增删是不在atomic责任之内的
- nonmatic
- 引用计数
- retain/strong
- assign/unsafe_unetained
- weak
- copy
- assign
- assign和weak区别
- assign
- assign可以修饰基本数据类型,如int,BOOL等;
- 修饰对象类型时,不改变其引用计数
- 会产生垂悬指针(assign修饰对象释放后指针会继续指向原地址,如果继续访问可能获取原对象造成异常)
- weak
- 不改变被修饰对象的引用计数
- 所指对象在被释放后会自动置nil
- assign既可以修饰对象又可以修饰基本数据类型,weak只能修饰对象
- assign修饰对象释放后指针会继续指向原地址,而weak是置为nil的
- assign
OC语言笔试题
- MRC下如何重写retain修饰变量的setter方法?
@property (nonatomic, retain) id obj;
- (void)setName:(id)obj
{
if (_obj != obj) {
[_obj release];
_obj = [obj retain];
}
}