上章我们了解:
realizeClassWithoutSwift对非懒加载类进行加载,并给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化。最后设置⽗类,isa指针的初始化。我们继续分析分类加载和关联对象。
一. 懒加载类的加载
在源码objc4-838.1中添加测试代码:
@interface NYPerson : NSObject
@property (nonatomic ,copy) NSString *hobby;
@end
//懒加载类 未实现load方法
@implementation NYPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
NYPerson *p = [NYPerson alloc];
}
return 0;
}
断点调试发现懒加载类也会调用realizeClassWithoutSwift方法。
bt看下堆栈信息:
执行顺序是,预编译->编译->汇编->连接->dyld->通知_objc_init->执行mian函数(ps:_objc_init初始化过程,前几章的知识,需要加深理解)。从堆栈信息可以看出,懒加载类是在main函数之后被调用->
objc_alloc->callAlloc->快速查找未命中->_objc_msgSend_uncached->lookUpImpOrForward(到了这我们熟悉的慢速查找流程)->realizeAndInitializeIfNeeded_locked->initializeAndLeaveLocked->initializeAndMaybeRelock->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift
如图所示:
结论:所以在
懒加载类在第一次接受到消息的时候才会被加载。
二.关联对象
在代码中添加NY分类:
@interface NYPerson (NY)
@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,assign) int age;
-(void)test;
-(void)category_instanceMethod;
+(void)categoty_classMethod;
@end
@implementation NYPerson (NY)
-(void)test {
NSLog(@"%s",__func__);
}
-(void)category_instanceMethod {
NSLog(@"%s",__func__);
}
+(void)categoty_classMethod {
NSLog(@"%s",__func__);
}
static const char *NYNameKey = "NYNameKey";
-(void)setName:(NSString *)name {
objc_setAssociatedObject(self, NYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name {
return objc_getAssociatedObject(self, NYNameKey);
}
@end
在终端使用clang -rewrite-objc mian.mm 生成cpp文件进行分析。
找到对应分类数据结构代码:
//分类数据结构
struct _category_t {
const char *name; //NY 分类名称
struct _class_t *cls;//类 NYPerson
const struct _method_list_t *instance_methods; //方法列表
const struct _method_list_t *class_methods; //类方法列表
const struct _protocol_list_t *protocols; //协议列表
const struct _prop_list_t *properties; //属性列表
};
分类没有元类,所以使用类方法列表。再者分类没有ivar说明分类没有成员变量。那如果要给分类添加属性,要怎么办呢?
类的扩展必须写在
类的声明与类的实现之间。类扩展与分类有什么区别?
分类:在原则上只能添加方法,有单独的实现。
类扩展:能添加属性,添加方法,成员变量,没有单独的实现。
继续上面的问题,如何给分类添加属性呢?
通过关联对象方法objc_setAssociatedObject,objc_getAssociatedObject类设置方法关联。
如下有5种关联策略:
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. */
};
我们来看看打印:
来看看
objc_setAssociatedObject底层代码:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
if (!object && !value) return;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
DisguisedPtr<objc_object> disguised{(objc_object *)object};//关联对象
ObjcAssociation association{policy, value};//关联策略和值
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false;
{
AssociationsManager manager;//C++ 构造函数 初始化一个对象
AssociationsHashMap &associations(manager.get());//获取全局的HasMap
if (value) {
//去关联对象表中找对象对应的HashMap表,如果没有内部会重新生成一个
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
//说明是第一次设置关联对象,把是否关联对象设置为YES
isFirstAssociation = true;
}
/* establish or replace the association */
//更新对应关联对象
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {//如果value为空
auto refs_it = associations.find(disguised);//通过object找对应的HashMap表
if (refs_it != associations.end()) {// 存在
auto &refs = refs_it->second;
auto it = refs.find(key);//通过key再在二级表里面找对应的内容
if (it != refs.end()) {
//删除掉
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
我们来看看try_emplace做了什么:
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
//在TheBucket找关联的key值 已存则返回
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
//不存在就插入一个新的对象
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
分类和类扩展的区别
-
分类原则上只能增加⽅法(
可以通过rutime关联对象实现添加属性)。 -
类扩展不仅可以增加⽅法,还可以增加实例变量(或者属性)。 -
类扩展是在编译阶段被添加到类中,⽽分类是在运⾏时添加到类中。
-
类扩展不能像分类那样拥有独⽴的实现部分(
@implementation部分),也就是说,类扩展所 -
声明的⽅法必须
依托对应类的实现部分来实现。 -
定义在 .m ⽂件中的类扩展⽅法为私有的,定义在 .h ⽂件(头⽂件)中的类扩展⽅法为公有的。类扩展是在 .m ⽂件中声明私有⽅法的⾮常好的⽅式。
三.分类加载
我们来看看分类的加载是在objc中实现的。 在源码attachCategories的实现中:
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,int flags)
{
......忽略......
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data()->extAllocIfNeeded();//新建rwe
const char *mangledName = cls->nonlazyMangledName();
const char *personName = "NYPerson";
auto lg_ro = (const class_ro_t *)cls->data();
auto lg_isMeta = lg_ro->flags & RO_META;
if (strcmp(mangledName, personName) == 0 && !lg_isMeta) {
}
for (uint32_t i = 0; i < cats_count; i++) {//遍历每个分类
auto& entry = cats_list[i];
//获取分类里面的方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
//获取属性列表
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
//获取协议列表
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
/添加分类的方法到rwe中
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
我们进行调试打印:
注意:这里面,分类和本类都需要实现+load方法才可以。
1.类(非懒),分类(非懒)
默认一个.cxx_destruct析构函数
2.类(非懒),分类(懒)
3.类(懒),分类(非懒)
4.类(懒),分类(懒)
小结:
类为⾮懒加载类/分类为⾮懒加载类
编译时ro⾥⾯只有类的数据没有分类的数据,分类的数据在运⾏是被加载到rwe⾥⾯。
类为⾮懒加载类/分类为懒加载类
编译时类和分类的数据都被加载ro⾥⾯了。
类为懒加载类/分类为⾮懒加载类
编译时类和分类的数据都被加载ro⾥⾯了。
类为懒加载类/分类懒加载类
在类第⼀次接收到消息时加载数据,类和分类的数据都被加载在ro⾥⾯。
四.总结
-
懒加载类在第一次接受到消息的时候才会被加载。 -
分类:在原则上只能添加方法,有单独的实现。类扩展:能添加属性,添加方法,成员变量,没有单独的实现。通过关联对象方法objc_setAssociatedObject,objc_getAssociatedObject类设置方法关联对象。 -
类与分类的加载:
类(非懒),分类(非懒):编译时ro⾥⾯只有类的数据没有分类的数据,分类的数据在运⾏是被加载到rwe⾥⾯。类(非懒),分类(懒):编译时类和分类的数据都被加载ro⾥⾯了。类(懒),分类(非懒):编译时类和分类的数据都被加载ro⾥⾯了。类(懒),分类(懒):在类第⼀次接收到消息时加载数据,类和分类的数据都被加载在ro⾥⾯。