在iOS Objective-C底层 part1:start内已经大概讲了分类是如何加载到内存中的,本篇文章展开说说分类的结构与内部功能实现.
1.分类原本面目
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;//分类添加的实例的属性
struct property_list_t *_classProperties;//分类添加的类的属性(结构中有但未真实用到)
};
来看调用流程
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
由于类加方法,属性,协议
与元类加方法,属性,协议
是一样的,我们这只看类加方法,属性,协议
.
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
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);
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;
}
}
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);
}
addUnattachedCategoryForClass //绑定分类与类
remethodizeClass //类的三个列表加入分类的三个列表
attachCategories
到目前为止,加方法,加属性,加协议所走流程是一模一样,代码也很容易理解.但我们知道,分类加属性在类的.h
内加一个@property(nonatomic,copy) NSString *name;
是不够的,还需要我们做关联属性.
1.1 提问
那么为什么加属性不能和加方法,加协议一样只是走完上述流程就好了呢?(因为加方法,加协议道理是一样的,所以下面的只针对加方法进行说明)
1.2 回答
- reason1==>属性是个性,方法是共性
这是一个所有权的问题!实例方法的所有权归属于类.而属性的在类内只是一个样板,属性真实的所有权归属于类的对象实例
,如图.
如果以上的描述你没明白,举个例子:
@interface User : NSObject
-(void)buy;
@end
@implementation User
-(void)buy{
NSLog(@"%@",@"买东西");
}
@end
User * user1 = [[User alloc]init];
user1.name = @"lilei";
[user1 buy];
User * user2 = [[User alloc]init];
user1.name = @"hanmeimei";
[user2 buy];
两个对象调用buy方法会去User类的方法列表内找,所以他们的实现肯定一模一样的.(如果你知道OC方法的调用流程,就更不言自明了)==>共性
而属性两者却截然不同.==>个性
所以类中@property(nonatomic,copy) NSString *name;只是个性的样板.
这也是为什么OC方法在运行时任何时候都可以加的原因,但属性就哈哈了.
- reason2==>类本身属性内存类帮忙申请,分类属性内存类不管
OC内类生成对象的时候会根据类本身
的所带属性的个数为对象申请相应的内存空间.
而分类可以加属性是后期版本的OC才实现的功能.
在不影响类生成对象的流程的基础上,
属性样板
会加入类本身;(分类.h加入属性,.m不做关联属性.可以'使用',你懂的,😄)
但内存类不管,那怎么办,当然是第三方托管;
第三方托管==>关联属性
2.关联属性的具体实现
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self,"name",name,OBJC_ASSOCIATION_COPY);
}
- (NSString*)name
{
NSString *nameObject = objc_getAssociatedObject(self, "name");
return nameObject;
}
先看保存==>objc_setAssociatedObject
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
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);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
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 {
// 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);
}
最终梳理结果:
全局有一个
AssociationsManager
持有一张哈希表(AssociationsHashMap)
.哈希表内:key==>对象地址
value==>对象绑定的属性表
(ObjectAssociationMap)
对象绑定的属性表内:
key==>调用objc_setAssociatedObject方法传入的key
value==>调用objc_setAssociatedObject方法传入的value+传入的policy(存储策略).
关于policy(存储策略),多说两句:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
Behavior | @property Equivalent | Description |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) / @property (unsafe_unretained) | 弱引用关联对象 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | 强引用关联对象,且为非原子操 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 复制关联对象,且为非原子操作 |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 强引用关联对象,且为原子操作 |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 复制关联对象,且为原子操作 |
objc_setAssociatedObject
分析完毕.
objc_getAssociatedObject
上面的get方法也看到了.
还有一个方法
void objc_removeAssociatedObjects(id object);//解除对象的所有绑定的属性
在理解了objc_setAssociatedObject
的基础上,这两个方法也不难懂,就不展开说.