18 - SDWebImage SDK 底层01

73 阅读6分钟
SDWebImage SDK 底层
UML 类图
  1. 泛化(generalization):表示is-a的关系,是对象之间偶合度最大的一种关系,子类继承父类的所有细节。直接使用语言中的继承表达。在类图中使用带三角箭头的实线表示,箭头从子类指向父类。
  2. 实现(Realization):在类图中就是接口和实现的关系。在类图中使用带三角箭头的虚线表示,箭头从实现类指向接口。
  3. 聚合(Aggregation):表示has-a的关系,是一种不稳定的包含关系。较强于一般关联,有整体与局部的关系,并且没有了整体,局部也可单独存在。如公司和员工的关系,公司包含员工,但如果公司倒闭,员工依然可以换公司。在类图使用空心的菱形表示,菱形从局部指向整体。
  4. 组合(Composition):表示contins-a的关系,是一种强烈的包含关系。组合类负责被组合类的生命周期。是一种更强的聚合关系。部分不能脱离整体存在。如公司和部门的关系,没有了公司,部门也不能存在了;调查问卷中问题和选项的关系;订单和订单选项的关系。在类图使用。
category 和 extension
  • 区别:
    1. extension 只有.h 文件,可以声明私有属性,方法,变量,实现在当前类.m 之中。
    • 通过@property 声明的属性,编译器会自动添加 setter 和 getter 方法和 生成 _变量名 的变量
    • @synthesize
      • 让编译器自动生成属性的存取方法,并将存取方法作用于系统根据属性名创建的(_+属性名)变量。
      • 当我们自定义存取方法时可以覆盖系统自动生成的存取方法。(注意当我们重写set和get方法时系统不会自动创建1中提到的变量,这时需要我们自己声明实例变量)
    • @dynamic
      • 告诉编译器不自动生成存取方法,由开发者自行实现存取方法。

    1. category 是一对.h 和.m 文件,可以为本类添加方法
    • 不能直接添加成员变量,
      • 因为 category 编译之后是一个结构体,分类中可以存放实例方法,类方法,协议,属性,但是没有存放成员变量的地方。所以不能添加,没有存储成员变量的地方
    • 虽然在分类中可以写@property添加属性,
      • 但是不会自动生成私有属性,也不会生成set,get方法的实现,只会生成set,get的声明,需要我们自己去实现。
    • setter 和 getter 方法通过后面函数来进行关联到对象上。
      • objc_setAssociatedObject objc_getAssociatedObject
      struct _category_t {
        const char *name;
        struct _class_t *cls;
        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;
      };
    
      /var/folders/9c/kdp2y0g17nlgstxd7zyzv8sh0000gn/T/LGPerson+Test-645ee6.mi:86830:17: warning: property 'nccName' requires method 'nccName' to be defined - use @dynamic or provide a method implementation in this category [-Wobjc-property-implementation]
      @implementation LGPerson (Test)
                      ^
      /var/folders/9c/kdp2y0g17nlgstxd7zyzv8sh0000gn/T/LGPerson+Test-645ee6.mi:86303:41: note: property declared here
      @property (nonatomic, strong) NSString *nccName;
                                              ^
      /var/folders/9c/kdp2y0g17nlgstxd7zyzv8sh0000gn/T/LGPerson+Test-645ee6.mi:86830:17: warning: property 'nccName' requires method 'setNccName:' to be defined - use @dynamic or provide a method implementation in this category [-Wobjc-property-implementation]
      @implementation LGPerson (Test)
                      ^
      /var/folders/9c/kdp2y0g17nlgstxd7zyzv8sh0000gn/T/LGPerson+Test-645ee6.mi:86303:41: note: property declared here
      @property (nonatomic, strong) NSString *nccName;
                                              ^
      3 warnings generated
    
    1. category 运行时期确定,不能添加变量,extension 编译时期确定,内存布局,隐藏类的私有属性/添加属性
association_object 实现原理
  • _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)

    object 给哪个对象添加属性, key 是给关联对象要添加属性的 key, value 是给关联对象要添加属性的 value, policy 属性的内存策略(assign copy)

  • AssociationsManager 全局的管理关联对象 AssociationsHashMap, AssociationsHashMap,以 key : value 的形式存储关联对象

    • AssociationsHashMap {disguised, ObjectAssociationMap}, disguised是object转化之后的地址
      • ObjectAssociationMap {key, ObjcAssociation} 存储的是关联属性的 key 以及值
        • ObjcAssociation {policy, value} 存储值以及内存策略
  AssociationsManager 整个 APP 全局管理关联对像的 manager,AssociationsHashMap 是以 key:value 形式
  AssociationsHashMap *_map;

  AssociationsHashMap  里面存储了许多关联对象, disguised_ptr_t 是外部传过来的关联对象转化之后的地址
  disguised_ptr_t  ObjectAssociationMap
  disguised_ptr_t  ObjectAssociationMap
  disguised_ptr_t  ObjectAssociationMap
  ......

  ObjectAssociationMap 管理存储了许多外部传入的要关联对象添加的属性,value 
  void*  ObjcAssociation  void*  是外部传入的关联对象想要添加属性 key
  void*  ObjcAssociation
  void*  ObjcAssociation
  ......

  ObjcAssociation 管理了外部传入的关联对象想要添加属性对应的 value 以及内存存储方式
  uniptr_t _policy;  _policy 代表 assign 或者 copy 
  id _value; 外部传入的值
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    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));

    // 对关联对象处理,生成一个disguised 地址作为 key 来保存 AssociationsHashMap
    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;
        AssociationsHashMap &associations(manager.get());

        if (value) {
          // refs_result 是一个迭代器,他有两个值,一个 first 是 key,一个 second 是 value 
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                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 {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(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();
}

category 加载
  1. 多个分类/类也有同名方法,方法怎么查找?
  • runtime 遍历方法列表,通过二分查找法,找到之后不会返回 imp 指针,往前移动,找同名方法
  1. 调用规则
    • 一个类的load方法在所有父类load方法调用之后调用
    • 分类的load方法在当前类的load方法调用之后调用
    • 分类的load方法调用顺序和编译顺序有关
  2. load方法调用
  • load_images
  • prepare_load_methods()
    • 调整当前的调用顺序
    • 类 递归遍历 loadable_classes<cls, method>
    • 分类 loadable_categorys 跟编译顺序有关
  • call_load_methods()
    • 先调用loadable_classes
    • 后调用loadable_categorys
  void _objc_init(void)
  {
      static bool initialized = false;
      if (initialized) return;
      initialized = true;
      
      // fixme defer initialization until an objc-using image is found?
      environ_init();
      tls_init();
      static_init();
      runtime_init();
      exception_init();
  #if __OBJC2__
      cache_t::init();
  #endif
      _imp_implementationWithBlock_init();

      _dyld_objc_notify_register(&map_images, load_images, unmap_image);

  #if __OBJC2__
      didCallDyldNotifyRegister = true;
  #endif
  }

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

懒加载类不能添加 load 方法,因为在_objc_init 的时候,加载镜像会调用所有的 load 方法。