iOS-关联对象(Objective-C)

news2024/11/15 17:58:05

关联对象

  • 关联对象的应用
    • 分类中的 @property
    • 使用关联对象
  • 关联对象的实现
    • objc_setAssociatedObject
      • AssociationsManager
      • 如何存储 ObjcAssociation
        • new_value != nil
        • new_value == nil
    • setHasAssociatedObjects()
    • objc_getAssociatedObject
    • objc_removeAssociatedObjects
  • 写到最后
    • 关于应用
    • 关于实现

我们在 iOS 开发中经常需要使用分类(Category),为已经存在的类添加属性的需求,但是使用 @property 并不能在分类中正确创建实例变量和存取方法。

不过,通过 Objective-C 运行时中的关联对象,也就是 Associated Object,我们可以实现上述需求。

关联对象的应用

分类中的 @property

@property 可以说是一个 Objective-C 编程中的“宏”,它有元编程的思想。

@interface TGObject : NSObject

@property (nonatomic, strong) NSString *property;

@end

在使用上述代码时会做三件事:

  • 生成实例变量 _property
  • 生成 getter 方法 - property
  • 生成 setter 方法 - setProperty:
@implementation DKObject {
    NSString *_property;
}

- (NSString *)property {
    return _property;
}

- (void)setProperty:(NSString *)property {
    _property = property;
}

@end

这些代码都是编译器为我们生成的,虽然你看不到它,但是它确实在这里,我们既然可以在类中使用 @property 生成一个属性,那么为什么在分类中不可以呢?

我们来做一个小实验:创建一个 DKObject 的分类 Category,并添加一个属性 categoryProperty:

@interface TGObject (Category)

@property (nonatomic, strong) NSString *categoryProperty;

@end

看起来还是很不错的,不过 Build 一下这个 Demo,会发现有这么一个警告:

在这里插入图片描述

在这里的警告告诉我们 categoryProperty 属性的存取方法需要自己手动去实现,或者使用 @dynamic 在运行时实现这些方法。

换句话说,分类中的 @property 并没有为我们生成实例变量以及存取方法,而需要我们手动实现。

使用关联对象

Q:我们为什么要使用关联对象?

A:因为在分类中 @property 并不会自动生成实例变量以及存取方法,所以一般使用关联对象为已经存在的类添加『属性』。

上一小节的内容已经给了我们需要使用关联对象的理由。在这里,我们会介绍 ObjC 运行时为我们提供的与关联对象有关的 API,并在分类中实现一个伪属性:

#import "ThreeGObject+Category.h"
#import <objc/runtime.h>

@implementation ThreeGObject (Category)

- (NSString *)categoryProperty {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setCategoryProperty:(NSString *)categoryProperty {
    objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

这里的 _cmd 代指当前方法的选择子,也就是 @selector(categoryProperty)

我们使用了两个方法 objc_getAssociatedObject 以及 objc_setAssociatedObject 来模拟『属性』的存取方法,而使用关联对象模拟实例变量。

在这里有必要解释两个问题:

  1. 为什么向方法中传入 @selector(categoryProperty)
  2. OBJC_ASSOCIATION_RETAIN_NONATOMIC 是干什么的?
    关于第一个问题,我们需要看一下这两个方法的原型:
d objc_getAssociatedObject(id object, const void *key);
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

@selector(categoryProperty) 也就是参数中的 key,其实可以使用静态指针 static void * 类型的参数来代替,不过在这里,笔者强烈推荐使用 @selector(categoryProperty) 作为 key 传入。因为这种方法省略了声明参数的代码,并且能很好地保证 key 的唯一性。

OBJC_ASSOCIATION_RETAIN_NONATOMIC 又是什么呢?如果我们使用 Command 加左键查看它的定义:

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_AssociationPolicy 对应了不同的属性修饰符:

objc_AssociationPolicymodifier
OBJC_ASSOCIATION_ASSIGNassign
OBJC_ASSOCIATION_RETAIN_NONATOMICnonatomic, strong
OBJC_ASSOCIATION_COPY_NONATOMICnonatomic, copy
OBJC_ASSOCIATION_RETAINatomic, strong
OBJC_ASSOCIATION_COPYatomic, copy

而我们在代码中实现的属性 categoryProperty 就相当于使用了 nonatomic strong 修饰符。

关于属性修饰符的区别,并不是这篇文章的主要内容,如果你需要了解它们的区别,Google 是一个很好的选择。

到这里,我们已经完成了对关联对象应用的介绍,再来回顾一下小节的内容。

@property 其实有元编程的思想,它能够为我们自动生成实例变量以及存取方法,而这三者构成了属性这个类似于语法糖的概念,为我们提供了更便利的点语法来访问属性:

self.property <=> [self property]
self.property = value <=> [self setProperty:value]

在分类中,因为类的实例变量的布局已经固定,使用@property 已经无法向固定的布局中添加新的实例变量(这样做可能会覆盖子类的实例变量),所以我们需要使用关联对象以及两个方法来模拟构成属性的三个要素。

关联对象的实现

这一部分会从三个 objc 运行时的方法为入口来对关联对象的实现一探究竟,其中两个方法是上一部分使用到的方法:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);

三个方法的作用分别是:

  • 以键值对形式添加关联对象
  • 根据 key 获取关联对象
  • 移除所有关联对象

而接下来的内容自然就是围绕这三个方法进行的,我们会对它们的实现进行分析。

objc_setAssociatedObject

首先是 objc_setAssociatedObject 方法,这个方法的调用栈并不复杂:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
└── void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy)
    └── void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy)

调用栈中的_object_set_associative_reference方法实际完成了设置关联对象的任务:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        ObjectAssociationMap *refs = i->second;
        ...
    }
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

在这里的实现省略了大多的实现代码,而且忽略了很多逻辑上的顺序,不过不要在意这里的代码能否执行。

我们需要注意其中的几个类和数据结构,因为在具体分析这个方法的实现之前,我们需要了解其中它们的作用:

  • AssociationsManager
  • AssociationsHashMap
  • ObjcAssociationMap
  • ObjcAssociation

AssociationsManager

AssociationsManager 在源代码中的定义是这样的:

class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }

    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

spinlock_t AssociationsManager::_lock;
AssociationsHashMap *AssociationsManager::_map = NULL;

它维护了 spinlock_tAssociationsHashMap的单例,初始化它的时候会调用 lock.lock() 方法,在析构时会调用 lock.unlock(),而 associations 方法用于取得一个全局的AssociationsHashMap单例。

也就是说 AssociationsManager 通过持有一个自旋锁 spinlock_t 保证对 AssociationsHashMap 的操作是线程安全的,即每次只会有一个线程对 AssociationsHashMap 进行操作。

如何存储 ObjcAssociation

ObjcAssociation 就是真正的关联对象的类,上面的所有数据结构只是为了更好的存储它。

首先,AssociationsHashMap 用与保存从对象的disguised_ptr_tObjectAssociationMap 的映射:

class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
    void *operator new(size_t n) { return ::malloc(n); }
    void operator delete(void *ptr) { ::free(ptr); }
};

ObjectAssociationMap 则保存了从key 到关联对象ObjcAssociation的映射,这个数据结构保存了当前对象对应的所有关联对象:

class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
   void *operator new(size_t n) { return ::malloc(n); }
   void operator delete(void *ptr) { ::free(ptr); }
};

最关键的ObjcAssociation包含了 policy 以及 value

class ObjcAssociation {
    uintptr_t _policy;
    id _value;
public:
    ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
    ObjcAssociation() : _policy(0), _value(nil) {}

    uintptr_t policy() const { return _policy; }
    id value() const { return _value; }

    bool hasValue() { return _value != nil; }
};

举一个简单的例子来说明关联对象在内存中以什么形式存储的,以下面的代码为例:

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        NSObject *obj = [NSObject new];
        objc_setAssociatedObject(obj, @selector(hello), @"Hello", OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }
    return 0;
}

这里的关联对象 ObjcAssociation(OBJC_ASSOCIATION_RETAIN_NONATOMIC, @"Hello") 在内存中是这么存储的:

在这里插入图片描述

接下来我们可以重新回到对 objc_setAssociatedObject 方法的分析了。

在这里会将方法的执行分为两种情况:

  • new_value != nil 设置/更新关联对象的值
  • new_value == nil 删除一个关联对象

new_value != nil

先来分析在new_value != nil的情况下,该方法的执行是什么样的:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    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);

        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;
                j->second = ObjcAssociation(policy, new_value);
            } else {
                (*refs)[key] = ObjcAssociation(policy, new_value);
            }
        } else {
            ObjectAssociationMap *refs = new ObjectAssociationMap;
            associations[disguised_object] = refs;
            (*refs)[key] = ObjcAssociation(policy, new_value);
            object->setHasAssociatedObjects();
        }
    }
    if (old_association.hasValue()) ReleaseValue()(old_association);
}
  1. 使用old_association(0, nil)创建一个临时的 ObjcAssociation 对象(用于持有原有的关联对象,方便在方法调用的最后释放值)

  2. 调用 acquireValuenew_value 进行 retain 或者 copy

static id acquireValue(id value, uintptr_t policy) {
     switch (policy & 0xFF) {
     case OBJC_ASSOCIATION_SETTER_RETAIN:
         return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
     case OBJC_ASSOCIATION_SETTER_COPY:
         return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
     }
     return value;
 }
  1. 初始化一个 AssociationsManager,并获取唯一的保存关联对象的哈希表 AssociationsHashMap AssociationsManager manager;
    AssociationsHashMap &associations(manager.associations());
  2. 先使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap
  3. 如果没有找到,初始化一个 ObjectAssociationMap,再实例化 ObjcAssociation 对象添加到 Map 中,并调用 setHasAssociatedObjects 方法,表明当前对象含有关联对象
ObjectAssociationMap *refs = new ObjectAssociationMap;
 associations[disguised_object] = refs;
 (*refs)[key] = ObjcAssociation(policy, new_value);
 object->setHasAssociatedObjects();
 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);
 }
  1. 如果找到了对应的 ObjectAssociationMap,就要看key是否存在了,由此来决定是更新原有的关联对象,还是增加一个
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);
 }
  1. 最后的最后,如果原来的关联对象有值的话,会调用 ReleaseValue() 释放关联对象的值
struct ReleaseValue {
     void operator() (ObjcAssociation &association) {
         releaseValue(association.value(), association.policy());
     }
 };

 static void releaseValue(id value, uintptr_t policy) {
     if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
         ((id(*)(id, SEL))objc_msgSend)(value, SEL_release);
     }
 }

到这里,该条件下的方法实现就结束了。

new_value == nil

如果 new_value == nil,就说明我们要删除对应 key 的关联对象,实现如下:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    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);

        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);
            }
        }
    }
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

这种情况下方法的实现与前面的唯一区别就是,我们会调用 erase 方法,擦除 ObjectAssociationMapkey对应的节点。

setHasAssociatedObjects()

其实上面的两种情况已经将 objc_setAssociatedObject 方法的实现分析得很透彻了,不过,这里还有一个小问题来等待我们解决,setHasAssociatedObjects() 方法的作用是什么?

inline void objc_object::setHasAssociatedObjects() {
    if (isTaggedPointer()) return;

 retry:
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    if (!newisa.indexed) return;
    if (newisa.has_assoc) return;
    newisa.has_assoc = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}

它会将isa结构体中的标记位 has_assoc 标记为 true,也就是表示当前对象有关联对象,在这里我还想祭出这张图来介绍 isa 中的各个标记位都是干什么的。

在这里插入图片描述

如果想要了解关于 isa 的知识,可以阅读从 NSObject 的初始化了解 isa

objc_getAssociatedObject

我们既然已经对 objc_setAssociatedObject 的实现已经比较熟悉了,相信对于objc_getAssociatedObject的理解也会更加容易。

方法的调用栈和objc_setAssociatedObject非常相似:

id objc_getAssociatedObject(id object, const void *key)
└── id objc_getAssociatedObject_non_gc(id object, const void *key);
    └── id _object_get_associative_reference(id object, void *key)

而 _object_get_associative_reference 相比于前面方法的实现更加简单。

id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        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()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
    return value;
}

代码中寻找关联对象的逻辑和 objc_setAssociatedObject 差不多:

  1. 获取静态变量 AssociationsHashMap

  2. DISGUISE(object) key 查找 AssociationsHashMap

  3. void *keykey查找 ObjcAssociation

  4. 根据 policy 调用相应的方法

 if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);

 if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
     ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
 }

  1. 返回关联对象 ObjcAssociation 的值

objc_removeAssociatedObjects

关于最后的 objc_removeAssociatedObjects 方法,其实现也相对简单,这是方法的调用栈:

void objc_removeAssociatedObjects(id object)
└── void _object_remove_assocations(id object)

这是简化版本的 objc_removeAssociatedObjects 方法实现:

void objc_removeAssociatedObjects(id object) {
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}

为了加速移除对象的关联对象的速度,我们会通过标记位 has_assoc 来避免不必要的方法调用,在确认了对象和关联对象的存在之后,才会调用 _object_remove_assocations 方法移除对象上所有的关联对象:

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            delete refs;
            associations.erase(i);
        }
    }
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

方法会将对象包含的所有关联对象加入到一个 vector 中,然后对所有的 ObjcAssociation 对象调用ReleaseValue()方法,释放不再被需要的值。

写到最后

关于应用

本来在这个系列的文章中并不会涉及关联对象这个话题,不过,有人问过我这么一个问题:在分类中到底能否实现属性?其实在回答这个问题之前,首先要知道到底属性是什么?而属性的概念决定了这个问题的答案。

  • 如果你把属性理解为通过方法访问的实例变量,我相信这个问题的答案是不能,因为分类不能为类增加额外的实例变量。
  • 不过如果属性只是一个存取方法以及存储值的容器的集合,那么分类是可以实现属性的。

分类中对属性的实现其实只是实现了一个看起来像属性的接口而已。

关于实现

关联对象又是如何实现并且管理的呢:

  • 关联对象其实就是 ObjcAssociation 对象
  • 关联对象由AssociationsManager管理并在 AssociationsHashMap 存储
  • 对象的指针以及其对应 ObjectAssociationMap 以键值对的形式存储在 AssociationsHashMap
  • ObjectAssociationMap 则是用于存储关联对象的数据结构
  • 每一个对象都有一个标记位has_assoc指示对象是否含有关联对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/420364.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP32设备驱动-VEML6040颜色传感器驱动

VEML6040颜色传感器驱动 文章目录 VEML6040颜色传感器驱动1、VEML6040介绍2、硬件准备3、软件准备4、驱动实现1、VEML6040介绍 VEML6040 颜色传感器可感应红光、绿光、蓝光和白光,并使用 CMOS 工艺将光电二极管、放大器和模拟/数字电路集成到单个芯片中。 通过应用颜色传感器…

【Spring】— Spring基础学习

Spring基础学习一、Spring概述1.什么是Spring2.Spring的下载及目录结构2.1 Spring框架包2.2 第三方依赖包二、控制反转&#xff08;IoC&#xff09;与依赖注入&#xff08;DI&#xff09;1.什么是控制反转&#xff08;IoC&#xff09;2.什么是依赖注入&#xff08;DI&#xff0…

u-view2.0 引入iconfont(阿里字体图标库)详细步骤!

uView已通过大量的实践中&#xff0c;收集了用户最有可能需要用到的图标&#xff0c;见Icon 图标&#xff0c;但我们也相信&#xff0c;它肯定无法覆盖所有的场景和需求。 用户也可以使用标签的方式&#xff0c;自行引入字体图标&#xff0c;为何要通过扩展的方式集成呢&#…

树莓派 ROS 学习(二)小乌龟 turtlesim

添加链接描述 目录 创建catkin工作空间 小乌龟turtlesim节点 启动节点 turtlesim节点 turtlesim 话题与消息 查看话题的消息类型 查看消息类型的具体内容 查看话题的值 改变背景颜色 获取参数列表 获取参数值 修改颜色 控制乌龟运动 通过发布话题控制乌龟运动 通过键盘控制乌龟…

力扣sql中等篇练习(一)

力扣sql中等篇练习(一) 1 第二高的薪水 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 SELECT IFNULL((SELECT distinct salary SecondHighestSalaryFROM EmployeeORDER BY salary desclimit 1,1),null ) SecondHighestSalary1.3 运行截图 2 分数排…

【C++】命名空间,缺省参数,函数重载,引用,内联函数,auto 关键字

目录1. 命名空间2. 输入输出3. 缺省参数4. 函数重载为什么C支持函数重载&#xff1f;5. 引用5.1 引用作函数参数&#xff08;输出型参数&#xff09;5.2 作函数的返回值关于函数的返回值&#xff1a;5.3 引用权限关于类型转换&#xff1a;5.4 引用和指针6. 内联函数6.1 C推荐的…

2、在vscode上创建第一个C++多文件编译工程(即如何添加task.json和launch.json文件到工程里面)

文章目录1、新建一个工程目录2、在vscode打开刚建立的工程目录&#xff0c;建立cpp文件3、设置C/C编译的选项:c_cpp_properties.json&#xff08;1&#xff09;鼠标点在.c的源文件内部&#xff0c;按 CtrlShiftp 快捷键&#xff0c;在弹出的界面中选择: [C/C:编配置(UI)]&#…

RK3568平台开发系列讲解(Linux系统篇)Linux 内部的全景图

🚀返回专栏总目录 文章目录 一、Linus 是谁二、Linux 内核全景图三、漫画:Linux 内核都有啥沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们一起认识这样一个计算机黑客,看看他是怎样创造出影响世界的 Linux,然后进一步了解一下 Linux 的内部结构。 一、…

从0学习stm32第二天

1.存储器结构 程序存存储器&#xff0c;数据存储器&#xff0c;寄存器和输入输出端口&#xff0c;被组织在同一个4G的线性地址空间中&#xff1b; 可以通过地址的方法访问对应的存储器或寄存器&#xff1b; 比如 0X12 34 56 78在内存中存储 低地址----------------------…

vue3组合式API和vite+ts

创建项目 npm create vitelatest . 选择ts版本 直接安装依赖项目启动 vite项目配置路径 cnpm i --save-dev types/node 让ts项目支持node环境 vite构建工具中配置了路径加载组件 import HelloWorld from "/components/HelloWorld.vue"; 界面显示正常 引入文件路径报错…

STM32嵌入式面试知识点总结

一、STM32F1和F4的区别&#xff1f; 解答&#xff1a; 参看&#xff1a;STM32开发 – STM32初识 内核不同&#xff1a;F1是Cortex-M3内核&#xff0c;F4是Cortex-M4内核&#xff1b; 主频不同&#xff1a;F1主频72MHz&#xff0c;F4主频168MHz&#xff1b; 浮点运算&#xff…

midjourney入口是什么?怎么使用midjourney

最近有很多小伙伴在咨询我midjourney的事情&#xff0c;因为他们看过midjourney的神奇能力&#xff0c;忍不住想玩一下&#xff0c;都在问midjourney要去哪里玩&#xff1f;midjourney入口是什么&#xff1f;小编觉得今天有必要来给大家详细的说说。 一.midjourney是什么 Midj…

Kafka消息发送流程

消息发送高阶用法 自定义拦截器 自定义序列化 自定义分区器 核心参数 https://kafka.apache.org/0110/documentation.html 参数名描述默认值bootstrap.servers格式为host1:port1,host2:port2,…key.serializervalue.serializerretries0retry.backoff.ms上次发送失败&…

【数据结构与算法】快速排序的非递归实现方法

目录 一.前言 二.非递归实现 一.前言 如果数据量过大的话&#xff0c;不断递归就会出现栈溢出的现象&#xff0c;这个时候你的代码是没问题的&#xff0c;但就是跑不起来&#xff0c;这个时候就要把递归改成非递归。 一般有两种改法&#xff1a; 1.直接改&#xff0c;利用循环…

Kafka---Kafka安装(单机版)

Kafka安装&#xff08;单机版&#xff09; 文章目录Kafka安装&#xff08;单机版&#xff09;上传压缩包解压更名配置文件修改myid启动zookeeper启动kafka创建topic查看消息队列查看消息队列详情生产消息消费消息查询指定对列消息数量上传压缩包 将压缩包上传到/opt/install …

Spring —— Spring Boot 日志文件

JavaEE传送门JavaEE Spring —— Spring Boot 创建和使用 Spring —— Spring Boot 配置文件 目录Spring Boot 日志文件Spring Boot 使用日志得到日志对象使用日志对象打印日志日志级别日志级别作用日志级别的分类日志级别设置日志持久化更简单的日志输出 (lombok)Spring Boo…

全网最详细,Jmeter性能测试-性能进阶, 数据驱动将融入性能测试(五)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 本节或者后面都可能…

【汇总版】计算机组成原理思维导图

目录导读与总结汇总思维导图导读与总结 这是一份涵盖计算机组成原理基础知识的思维导图&#xff0c;它包括计算机系统的层次结构、指令和指令执行、CPU的组成和工作原理、存储器的层次结构和管理、输入输出设备的原理和接口、以及汇编语言的基础知识。通过这份思维导图&#x…

Zookeeper源码分析——ZK服务端初始化源码解析

持久化源码 快照 public interface SnapShot {/*** deserialize a data tree from the last valid snapshot and * return the last zxid that was deserialized* 反序列化方法*/long deserialize(DataTree dt, Map<Long, Integer> sessions) throws IOException;/*** …

在Linux中搭建Apache和多个版本PHP源码的集群

ApachePHP安装在公网IP为x.x.x.x的服务器上 需要下载安装的软件版本&#xff1a;httpd-2.4php-5.6php-7.4php-8.0 安装httpd 第一步&#xff0c;查看Linux系统中是否安装了apache。 命令&#xff1a;rpm -qa | grep httpd 若已经安装了&#xff0c;则需要使用命令“yum -y…