当前位置: 首页 > news >正文

【iOS】类与对象底层探索

类与对象底层探索

  • Clang
  • 探索对象本质
  • `objc_setProperty`源码探索
  • cls与类的关联原理
    • isa的类型isa_t
    • 原理探索
  • 类&类的结构
    • 什么是元类
    • NSObject到底有几个
    • isa走位&继承关系图
    • objc_class&objc_object
  • 类结构分析
    • 计算cache类中的内存大小
    • 获取bits
      • 属性列表(property_list)&& 方法列表(methods_list)
    • rw、ro、rwe之间的关系
  • 参考文章

Clang

我们在探索OC对象的本质之前,先来了解一个编译器:Clang。我们通过终端将main.m文件输出为main.cpp,这样的目的是为了更好的观察底层的一些结构和实现逻辑,方便了解底层。

//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp//2、将 ViewController.m 编译成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp //4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

探索对象本质

打开编译好的main.cpp,找到LGPerson的定义,可以发现其在底层被编译成了struct结构体。

  • LGPerson_IMPL中的第一个属性 其实就是 isa,是继承自NSObject,属于伪继承,伪继承的方式是直接将NSObject结构体定义为LGPerson中的第一个属性,意味着LGPerson 拥有 NSObject中的所有成员变量

  • LGPerson中的第一个属性NSObject_IVARS等效于NSObject中的isa

struct LGPerson_IMPL {struct NSObject_IMPL NSObject_IVARS;//相当于Class isaNSString *_name;
};

这里提出一个问题:为什么isa的类型是class

这是为了适配 Objective - C 的对象模型、消息传递机制以及类层次结构的管理,让对象能够正确地找到所属的类并调用相应的方法。

总结:

  • OC对象的本质其实就是结构体
  • LGPerson中的isa是继承自NSObject中的isa

objc_setProperty源码探索

除了LGPerson的底层定义,我们可以发现属性name对应的setget方法,其中set方法的实现依赖的就是runtime中的objc_setProperty

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }//gei方法
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
//set方法
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); }

下面我们来一步步了解其底层实现:

objc4_838源码中objc_setProperty底层实现

进入reallySetProperty源码实现,这里的原理为:新值retain旧值release

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{if (offset == 0) {object_setClass(self, newValue);//设置isa指向return;}//通过检查偏移量,说明要设置的是对象的类信息,设置完成后直接返回,无需执行后续代码id oldValue;//用于存储属性的旧值id *slot = (id*) ((char*)self + offset);//计算属性在对象内存中的地址,使用指针slot指向属性的存储位置、if (copy) {newValue = [newValue copyWithZone:nil];//如果copy为true,执行不可变赋值操作} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];//如果mutableCopy为true,执行可变赋值操作} else {if (*slot == newValue) return;newValue = objc_retain(newValue);//新增retain}//当不复制时调用,检查指针是否等于newValue,相等直接返回;否则,调用objc_retain增加newValue的引用计数,保证设置新值的时候不会被提前释放// 如果不是原子操作,直接更新内存位置的值if (!atomic) {oldValue = *slot;*slot = newValue;//设置新值} else {// 如果是原子操作,使用锁来保证线程安全spinlock_t& slotlock = PropertyLocks[slot];//获取自旋锁slotlockslotlock.lock();//锁定自旋锁,更新属性值时不会有其他线程同时访问该属性oldValue = *slot;//将*slot复制给oldValue*slot = newValue;//设置新值        slotlock.unlock();//解锁自旋锁}objc_release(oldValue);//release旧值
}

自旋锁:一种用于多线程编程的同步机制,用于保护共享资源,防止多个线程同时访问和修改这些资源而导致的数据不一致或其他并发问题。

总结:
在这段底层探索中,这里需要进行几点说明:

  • objc_setProperty方法的目的适用于关联 上层的set方法 以及 底层的set方法,其本质就是一个接口
  • 这么设计的原因是,上层的set方法有很多,如果直接调用底层set方法中,会产生很多的临时变量,当你想查找一个sel时,会非常麻烦
  • 基于上述原因,苹果采用了适配器设计模式(即将底层接口适配为客户端需要的接口),对外提供一个接口,供上层的set方法使用,对内调用底层的set方法,使其相互不受影响,即无论上层怎么变,下层都是不变的,或者下层的变化也无法影响上层,主要是达到上下层接口隔离的目的

在这里插入图片描述

cls与类的关联原理

这里我们来了解一下initInstanceIsa如何将cls和isa关联在一起的。

isa的类型isa_t

这里先给出isa指针的类型isa_t的定义,这里我们可以明确这是一个联合体定义。

union isa_t {isa_t() { }isa_t(uintptr_t value) : bits(value) { }uintptr_t bits;private:// Accessing the class requires custom ptrauth operations, so// force clients to go through setClass/getClass by making this// private.Class cls;public:
#if defined(ISA_BITFIELD)struct {ISA_BITFIELD;  // defined in isa.h};bool isDeallocating() {return extra_rc == 0 && has_sidetable_rc == 0;}void setDeallocating() {extra_rc = 0;has_sidetable_rc = 0;}
#endif

通常来说,isa指针占用的内存大小为8字节,即64位

由于这里使用的是联合体,由于联合体的定义我们可以得知,这里的cls和bits这两个成员是互斥的,所以当初始化isa指针的时候,有两种初始化方法:

  • 通过cls初始化,bits无默认值
  • 通过bits初始化,cls有默认值

这里还提供了一个结构体定义的位域,用于存储类信息以及其他信息,结构体成员ISA_BITFIELD这是一个宏定义,有两个版本分别对应ios移动端以及maxOS,如下所示:

# if __arm64__//对应ios移动端
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \uintptr_t nonpointer        : 1;                                       \uintptr_t has_assoc         : 1;                                       \uintptr_t weakly_referenced : 1;                                       \uintptr_t shiftcls_and_sig  : 52;                                      \uintptr_t has_sidetable_rc  : 1;                                       \uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \uintptr_t nonpointer        : 1;                                       \uintptr_t has_assoc         : 1;                                       \uintptr_t has_cxx_dtor      : 1;                                       \uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \uintptr_t magic             : 6;                                       \uintptr_t weakly_referenced : 1;                                       \uintptr_t unused            : 1;                                       \uintptr_t has_sidetable_rc  : 1;                                       \uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif# elif __x86_64__//对应macOS
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   define ISA_BITFIELD                                                        \uintptr_t nonpointer        : 1;                                         \uintptr_t has_assoc         : 1;                                         \uintptr_t has_cxx_dtor      : 1;                                         \uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \uintptr_t magic             : 6;                                         \uintptr_t weakly_referenced : 1;                                         \uintptr_t unused            : 1;                                         \uintptr_t has_sidetable_rc  : 1;                                         \uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)# else
#   error unknown architecture for packed isa
# endif

下面讲解一下其中的内容表示:

  • nonpointer`表示自定义类,占1位:
    • 0:纯isa指针。
    • 1:不只是类对象地址,isa包含了类信息,对象的引用计数。
  • has_assoc表示关联对象标志,占1位:
    • 0:没有关联对象。
    • 1:存在关联对象
  • has_cxx_dtor表示该对象是否有C++/OC的析构器,占1位:
    • 如果有析构函数,就需要做析构逻辑
    • 如果没有,就可以更快的释放对象
  • shiftcls表示存储类的指针类型,即类对象
    • arm64中占33位,开启指针优化的情况下,在arm64架构中有33位用来存储类指针
    • x86_64中占44位
  • magic用于调试器判断当前对象是真的对象还是没有初始化的空间,占6位
  • weakly_refrenced是指对象是否被指向或者曾经指向一个ARC的弱变量(没有弱引用的对象可以更快释放)
  • deallocating标志对象是是否正在释放内存
  • has_sidetable_rc表示当对象引用计数大于10时,则需要借用该变量存储进位
  • extra_rc表示该对象的引用计数值,实际上是引用计数值减1。
    • 如果对象的引用计数为10,那么extra_rc为9(这个仅为举例说明),实际上iPhone 真机上的 extra_rc 是使用 19位来存储引用计数的

下面展示两种不同的平台中,isa存储情况:
在这里插入图片描述

原理探索

通过alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone方法路径,查找到initInstanceIsa,并进入其原理实现:

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{ASSERT(!cls->instancesRequireRawIsa());ASSERT(hasCxxDtor == cls->hasCxxDtor());initIsa(cls, true, hasCxxDtor);
}

而后进入initIsa方法源码实现:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ ASSERT(!isTaggedPointer()); isa_t newisa(0);if (!nonpointer) {newisa.setClass(cls, this);} else {ASSERT(!DisableNonpointerIsa);ASSERT(!cls->instancesRequireRawIsa());#if SUPPORT_INDEXED_ISAASSERT(cls->classArrayIndex() > 0);newisa.bits = ISA_INDEX_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#elsenewisa.bits = ISA_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BITnewisa.has_cxx_dtor = hasCxxDtor;
#   endifnewisa.setClass(cls, this);
#endifnewisa.extra_rc = 1;}// This write must be performed in a single store in some cases// (for example when realizing a class because other threads// may simultaneously try to use the class).// fixme use atomics here to guarantee single-store and to// guarantee memory order w.r.t. the class index table// ...but not too atomic because we don't want to hurt instantiationisa = newisa;
}

这里的逻辑主要为:

  • 通过isa初始化isa
  • 通过bits初始化isa

验证isa与类的关联

  • 通过initIsa方法中的通过initIsa方法中的newisa.shiftcls = (uintptr_t)cls >> 3;验证
  • 通过isa指针地址与ISA_MSAK 的值 & 来验证
  • 通过runtime的方法object_getClass验证
  • 通过位运算验证

类&类的结构

什么是元类

这里先展示一张调试的图片:
在这里插入图片描述
这里我们需要注意一个问题:为何图中的p/x 0x00000001000081e8 & 0x00007ffffffffff8ULL以及p/x 0x00000001000081c0 & 0x00007ffffffffff8ULL中的类信息打印的结果都是CJLPerson?

  • 0x00000001000081e8是perosn对象的isa指针地址,他&后得到的结果是创建person的类CJLPerson。
  • 而0x00000001000081c0是isa中获取的类信息所指的类的isa的指针地址,就是CJLPerson类的类的isa指针地址,在Apple中我们将其简称为元类
  • 故而,我们可以了解到两个打印都是CJLPerson的根本原因是元类导致的。

在 Objective - C 的运行时环境里,0x00007ffffffffff8ULL 常作为一个掩码用于提取对象的 isa 指针中的类信息。

元类的说明:

  • 对象的isa是指向类,但是其实类也是一个对象叫做类对象,他的isa的位域只想Apple定义的元类
  • 元类是系统给的,他的定义和创建都是由编译器完成,在该过程中,类的归属来自元类。
  • 元类是类对象的类,每个类都有独一无二的元类来存储同类名的相关信息
  • 元类本身自己是没有名称的,但是由于与类相关联,故而使用同类名一样的名称

这里我们继续通过lldb命令来探索元类的走向,即isa的走位,这里我们可以得出一个关系链:对象 – 类 – 元类 – NSObject – NSObject
在这里插入图片描述
总结:

  • 对象的isa指向类
  • 类的isa指向元类
  • 元类的isa指向根元类,即NSObject
  • 根元类的isa指向其自己

NSObject到底有几个

我们从上面的图片中可以看出,NSObject类的元类也是NSObject,与上面的CJLPerson中的根元类的元类是同一个,这里我们得出结论为:内存中只存在一份根元类NSObject,根元类的元类是指向它自己的,在内存中永远只存在一份

isa走位&继承关系图

对象、类、元类、根元类的关系示意图:
在这里插入图片描述
isa走位:

  • 实例对象的isa指向类
  • 类对象的isa指向元类
  • 元类的isa指向根元类
  • 根元类的isa指向他自己本身,形成闭环,根元类即为NSObject

superclass走位

  • 类继承自父类
  • 父类继承自根类,此时根类指NSObject
  • 根类继承与nil,即根类(NSObject)可以理解为无中生有。

元类继承关系基本如上,在最后一条中,根元类继承与根类,这时的根类指NSObject。

实例对象之间没与继承关系,类之间有继承关系

objc_class&objc_object

这里我们先来理解一下对象和类为什么都有isa属性呢?这里我们需要注意的两个结构体类型为:objc_class以及objc_object

NSObject 的底层编译是NSObject_IMPL结构体:其中Class是isa指针的类型,是由objc_class定义的类型;而objc_class是一个结构体,在iOS中,所有的Class都是以objc_class为模版创建的

struct NSObject_IMPL {Class isa;
};typedef struct objc_class* Class;

下面展示一下objc_class以及objc_object在源码中的代码:

struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
struct objc_class : objc_object {objc_class(const objc_class&) = delete;objc_class(objc_class&&) = delete;void operator=(const objc_class&) = delete;void operator=(objc_class&&) = delete;// Class ISA;Class superclass;cache_t cache;             // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

***objc_class以及objc_object有什么关系?***:

  • 结构体类型objc_class继承自objc_object类型,其中objc_object也同样是一个结构体,并且有一个isa属性,所以objc_class也拥有了isa属性。
  • 在main.cpp底层编译文件中,NSObject中的isa属性在底层是由Class定义的,其中class的底层编码来自objc_class类型,所以NSObect拥有isa属性。
  • NSObject是一个类,用它初始化一个实例对象objcobjc满足objc_object 的特性(即有isa属性),主要是因为isa是由 NSObjectobjc_class继承过来的,而objc_class继承自objc_objectobjc_objectisa属性。所以对象都有一个isaisa表示指向,来自于当前的objc_object
  • objc_object是当前的类对象,所有的对象都有这样一个特性,故拥有isa属性。

总结:

  • 所有的对象、类、元类都有isa属性
  • 所有的对象都是由objc_object继承来的
  • 所以我们可以概括为所有以objc_object为模版创建的对象都有isa属性,所有以objc_class为模版创建的对象都有isa属性。万物皆对象
  • 在结构体层面来说,我们可以理解为是上层OC和底层的对接:
    • 下层是通过结构体定义的模版,例如objc_classobjc_object
    • 上层是通过底层的模版创建的一些类型,例如CJLPerson

在这里插入图片描述

类结构分析

在这个部分中,我们来探索一下类信息中都有哪些内容,来帮助我们更好的理解该部分的内容。

这里我们先来认识一下objc_class这个结构体:

struct objc_class : objc_object {objc_class(const objc_class&) = delete;objc_class(objc_class&&) = delete;void operator=(const objc_class&) = delete;void operator=(objc_class&&) = delete;// Class ISA;Class superclass;cache_t cache;             // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
//后面这里不做展示

isa: 主要指向类对象或是元类对象
superclass:指向当前类的父类
cache:方法缓存,提高调用方法的性能
bits:封装了类的其他信息,例如成员变量、方法列表、协议、属性。

元类对象结构亦是如此,只不过元类对象中存放的是类方法。

计算cache类中的内存大小

struct cache_t {
private:explicit_atomic<uintptr_t> _bucketsAndMaybeMask;union {struct {explicit_atomic<mask_t>    _maybeMask;
#if __LP64__uint16_t                   _flags;
#endifuint16_t                   _occupied;};explicit_atomic<preopt_cache_t *> _originalPreoptCache;};

上面我们先展示cache_t的定义,这段代码的核心功能是管理方法缓存,通过联合体实现了两种缓存模式的切换,节省内存并提高了灵活性。

从上面代码中我们可以看出,在LP64位的一个情况下是只有16个字节,这里我们展示一下cache_t的结构:
在这里插入图片描述

获取bits

我们可以现在objc4的源码中获取到CJLperson类对象的首地址,由于bits举例objc_class的首地址还需要偏移32字节,将这个bits强转成class_data_bits_t
在这里插入图片描述

这里我们faxing去处了bits中的值,我们可以通过看class_data_bits_t的声明,发现里面确实有bits成员:

struct class_data_bits_t {friend objc_class;// Values are the FAST_ flags above.uintptr_t bits;

在这里插入图片描述

但是这里的bits并不是我们想要的内容,我们继续向后看class_data_bits_t的源码,可以看到两个方法:data()safe_ro()方法,这两个方法一个返回class_rw_t,另一个返回class_ro_t;在64位架构CPU下,bits 的第3到第46字节存储 class_rw_tclass_rw_t 中存储flagswitnessfirstSubclassnextSiblingClass 以及class_rw_ext_t

下面我们直接探索属性列表和方法列表

属性列表(property_list)&& 方法列表(methods_list)

我们查看class_rw_t定义的源码可以发现,结构体中有提供相应的方法去获取属性列表、方法列表:

	const method_array_t methods() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;} else {return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};}}const property_array_t properties() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;} else {return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};}}const protocol_array_t protocols() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;} else {return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};}}

这里注意一个内容,这里的存储在class_rw_t这个类是存储一个属性的,并不存储一个成员变量成员变量是存储在另一个类中:class_ro_t的这里我们对比看一下这两个类的区别:

struct class_ro_t {uint32_t flags;uint32_t instanceStart;uint32_t instanceSize;
#ifdef __LP64__uint32_t reserved;
#endifunion {const uint8_t * ivarLayout;Class nonMetaclass;};explicit_atomic<const char *> name;WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;protocol_list_t * baseProtocols;const ivar_list_t * ivars;const uint8_t * weakIvarLayout;property_list_t *baseProperties;// This field exists only when RO_HAS_SWIFT_INITIALIZER is set._objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];_objc_swiftMetadataInitializer swiftMetadataInitializer() const {if (flags & RO_HAS_SWIFT_INITIALIZER) {return _swiftMetadataInitializer_NEVER_USE[0];} else {return nil;}}const char *getName() const {return name.load(std::memory_order_acquire);}class_ro_t *duplicate() const {

这里有一个成员变量列表也就是我们这里的ivars,这个列表包含的内容不仅仅包含一个成员变量列表,除了包括在{}中定义的一个成员变量,还包括通过属性定义的成员变量.bits --> data() -->ro() --> ivars通过这个流程来获取成员变量表.

通过@property定义的属性,也会存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性

rw、ro、rwe之间的关系

rw里面有methods、properties、protocols和ro;
ro里面有ivars、baseMethods、baseProtocols、baseProperties

ro:在类第一次从磁盘被加载到内存中产生,它是一块纯净的内存clear memory(只读的),它保存了类最纯净的成员变量、实例方法、协议、属性等等。当进程内存不够时候,ro可以被移除,在需要的时候再去磁盘中加载,从而节省更多内存。
在这里插入图片描述
rw:程序运行就必须一直存在,在进程运行时类一经使用后,runtime就会把ro写入新的数据结构dirty memory(读写的),这个数据结构存储了只有在运行时才会生成的新信息。(例如创建一个新的方法缓存并从类中指向它)。故而所有类都会链接成一个树状结构,这是通过First Subclass和Next Sibling Class指针实现的,这就决定了runtime能够遍历当前使用的所有类。
在这里插入图片描述

类的方法和属性保存在ro中了,为什么还要在rw中有呢?
因为它们可以在运行时被修改,当category被加载时,它可以向类中添加新的方法,也可以通过Runtime API去动态添加和修改,因为ro是只读的,所以需要再rw去跟踪这些东西。

rwe:是在category被加载或者通过 Runtime API 对类的方法属性协议等等进行修改后产生的,它保存了原本rw中类可能会被修改的东西(Methods / Properties / Protocols / Demangled Name),它的作用是给rw瘦身。(rwe是类被修改才会产生,没有被修改的类不会产生。注意:category被加载时候产生的rwe的条件是:分类和本类是必须是非懒加载类(重写+load))

在这里插入图片描述

参考文章

Objective-C 类的底层探索
iOS-底层原理 08:类 & 类结构分析
iOS-底层原理 07:isa与类关联的原理
OC底层探索(五) 类的结构分析
类结构中的class_rw_t与class_ro_t
OC底层探索(四) ISA的结构与类的关联、ISA走位分析

相关文章:

【iOS】类与对象底层探索

类与对象底层探索 Clang探索对象本质objc_setProperty源码探索cls与类的关联原理isa的类型isa_t原理探索 类&类的结构什么是元类NSObject到底有几个isa走位&继承关系图objc_class&objc_object 类结构分析计算cache类中的内存大小获取bits属性列表&#xff08;prope…...

2025年- H18-Lc126-54.螺旋矩阵(矩阵)---java版

1.题目描述 2.思路* 思路1&#xff1a; 补充2&#xff1a; directions[1][0] // 表示“下”这个方向的行增量&#xff08;1&#xff09; directions[1][1] // 表示“下”这个方向的列增量&#xff08;0&#xff09; int[][] directions {{0, 1}, {1, 0}, {0, -1}, {-…...

Paddle Serving|部署一个自己的OCR识别服务器

前言 之前使用C部署了自己的OCR识别服务器&#xff0c;Socket网络传输部分是自己写的&#xff0c;回过头来一看&#xff0c;自己犯傻了&#xff0c;PaddleOCR本来就有自己的OCR服务器项目&#xff0c;叫PaddleServing&#xff0c;这里记录一下部署过程。 1 下载依赖环境 1.1 …...

yolov5 本地训练

YOLOv5 | Kaggle 直接gitclone他的源码用Vscode看&#xff08;也可以直接把jupyter下下来&#xff09; 他要1.8&#xff0c;我的是2.7&#xff0c;他这个代码可能有点年头了 两年前了 他的环境 我的环境 我就是不懂为什么清华源的torch windows默认下出来是cpu版本 . 在终端…...

同城跑腿小程序帮取帮送接单抢单预约取件智能派单同城配送全开源运营版源码优创

一、源码描述 这是一套同城跑腿小程序&#xff0c;基于FastadminUniapp框架&#xff0c;全开源无加密&#xff0c;可私有化部署&#xff0c;包含用户端、骑手端和运营端&#xff08;后端&#xff09;&#xff0c;支持帮取/帮送模式&#xff0c;支持一键接单/抢单&#xff0c;主…...

基于SpringBoot的药房药品销售管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

机器学习中的学习率及其衰减方法全面解析

摘要&#xff1a; 本文深入解析机器学习中的学习率及其衰减方法&#xff0c;涵盖学习率的作用、常用衰减参数及七种主流衰减策略&#xff08;分段常数、指数、自然指数、多项式、余弦、线性余弦、噪声线性余弦&#xff09;。通过公式推导与图示对比&#xff0c;揭示不同衰减方式…...

硬件性能与能效比竞赛:解码 PC 硬件的 “速度与激情”

引言&#xff1a;当性能遇见能效&#xff0c;一场永不停歇的算力革命 在数字内容爆炸式增长的时代&#xff0c;无论是 4K/8K 游戏的极致画质追求&#xff0c;还是 AI 大模型的本地化部署需求&#xff0c;亦或是内容创作者对实时渲染的效率渴求&#xff0c;都在推动 PC 硬件走向…...

大模型在终末期肾脏病风险预测与临床方案制定中的应用研究

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与创新点 1.3 研究方法与数据来源 二、终末期肾脏病概述 2.1 定义与诊断标准 2.2 发病机制与影响因素 2.3 现状与危害 三、大模型技术原理及应用现状 3.1 大模型基本原理 3.2 在医疗领域应用案例 3.3 在终末期肾脏病…...

【C++11】智能指针

&#x1f4dd;前言&#xff1a; 这篇文章我们来讲讲C11——智能指针&#xff1a; &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;C学习笔记 &#x1f380;CSDN主页 愚润求学 &#x1f304;其他专栏&#xff1a;C语言入门基础&#xff0c;pyt…...

华为云Astro轻应用利用自定义连接器调用第三方接口实际操作

样图 说明 华为云Astro轻应用通过自定义连接器调用第三方接口具有多方面的作用,主要体现在以下几点: 扩展功能与集成能力 调用第三方服务:通过配置自定义连接器,Astro轻应用可以调用第三方提供的Rest协议接口,实现第三方提供的业务功能,扩展应用的能力。 集成外部系统:…...

【中间件】brpc_基础_butex.h

butex.h 学习笔记 源码 1 概述 butex.h 提供了一种用户态同步原语 butex&#xff08;类似 Linux 的 futex&#xff09;&#xff0c;专为 bthread 设计&#xff0c;用于高效协调线程的阻塞与唤醒。其核心是通过原子操作结合等待队列管理&#xff0c;减少内核态切换开销&#…...

数字智慧方案5876丨智慧交通枢纽智能化系统建设方案(56页PPT)(文末有下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2301_78256053/89575493 资料解读&#xff1a;智慧交通枢纽智能化系统建设方案 详细资料请看本解读文章的最后内容。 随着城市化进程的加速&#xff0c;交…...

深度学习笔记40_中文文本分类-Pytorch实现

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、我的环境 1.语言环境&#xff1a;Python 3.8 2.编译器&#xff1a;Pycharm 3.深度学习环境&#xff1a; torch1.12.1cu113torchvision…...

python设置word字体的方法

在Python中&#xff0c;可以使用python-docx库来设置Word文档的字体样式&#xff0c;以下为具体方法和示例代码&#xff1a; 一、设置段落中字体样式 使用python-docx库时&#xff0c;Word文档中的文本通常被组织成段落&#xff08;Paragraph对象&#xff09;&#xff0c;而一…...

golang常用库之-标准库text/template

文章目录 golang常用库之-标准库text/template背景什么是text/templatetext/template库的使用 golang常用库之-标准库text/template 背景 在许多编程场景中&#xff0c;我们经常需要把数据按照某种格式进行输出&#xff0c;比如生成HTML页面&#xff0c;或者生成配置文件。这…...

【JAVA】如何快速阅读一个基于maven构建的springboot项目

一、摘要 在JAVA项目开发过程中&#xff0c;现在比较流行的是springboot机构&#xff0c;特别是在后端开发的项目中&#xff0c;springboot应用的非常普遍。springboot很好将大型的、复杂的项目进行分解&#xff0c;以模块或者服务的表现形式组成项目。那么当我们接手一个陌生的…...

Fedora升级Google Chrome出现GPG check FAILED问题解决办法

https://dl.google.com/linux/linux_signing_key.pub 的 GPG 公钥(0x7FAC5991)已安装 https://dl.google.com/linux/linux_signing_key.pub 的 GPG 公钥(0xD38B4796)已安装 仓库 "google-chrome" 的 GPG 公钥已安装&#xff0c;但是不适用于此软件包。 请检查此仓库的…...

深入解析MapReduce:大数据处理的经典范式

引言 在大数据时代&#xff0c;如何高效处理海量数据成为技术核心挑战之一。Hadoop生态中的MapReduce框架应运而生&#xff0c;以其“分而治之”的思想解决了大规模数据的并行计算问题。本文将从原理、核心组件到实战案例&#xff0c;带你全面理解这一经典计算模型。 一、MapR…...

JVM性能调优的基础知识 | JVM内部优化与运行时优化

目录 JVM内部的优化逻辑 JVM的执行引擎 解释执行器 即时编译器 JVM采用哪种方式&#xff1f; 即时编译器类型 JVM的分层编译5大级别&#xff1a; 分层编译级别&#xff1a; 热点代码&#xff1a; 如何找到热点代码&#xff1f; java两大计数器&#xff1a; OSR 编译…...

云计算-容器云-部署jumpserver 版本2

应用部署&#xff1a;堡垒机部署 # 使用提供的软件包配置Yum源&#xff0c;通过地址将jumpserver.tar.gz软件包下载至Jumpserver节点的/root目录下 [rootjumpserver ~]# tar -zxvf jumpserver.tar.gz -C /opt/ [rootjumpserver ~]# cp /opt/local.repo /etc/yum.repos.d/ [roo…...

MSP430G2553驱动0.96英寸OLED(硬件iic)

1.前言 最近需要用MSP430单片机做一个大作业,需要用到OLED模块&#xff0c;在这里记录一下 本篇文章主要讲解MSP430硬件iic的配置和OLED函数的调用&#xff0c;不会详细讲解OLED显示原理(其实就是江科大的OLED模块如何移植到msp430上).OLED显示原理以及底层函数讲解请参考其他…...

同质化的旅游内核

湘西凤凰古城、北京非常有文艺氛围的方家胡同都在被改造翻新为现代的其他城市范式式的样式。 什么意思呢&#xff1f;很多古城的老房子&#xff0c;从外面看&#xff0c;很古老、很漂亮&#xff0c;但是进去以后&#xff0c;完全不是那么回事&#xff0c;整座房子已经被完全掏…...

2025年五一数学建模A题【支路车流量推测】原创论文讲解(含完整python代码)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2025年五一数学建模A题【支路车流量推测】完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 A题论文共104页&a…...

文章六:《循环神经网络(RNN)与自然语言处理》

文章6&#xff1a;循环神经网络&#xff08;RNN&#xff09;与自然语言处理——让AI学会"说人话" 引言&#xff1a;你的手机为什么能秒懂你&#xff1f; 当你说"我想看科幻片"时&#xff0c;AI助手能立刻推荐《星际穿越》&#xff0c;这背后是RNN在"…...

Redis总结及设置营业状态案例

Redis简介: rRedis服务开启与停止: 服务开启: 在Redis配置文件中输入cmd进入命令行输入redis-server redis-cli.exe -h -p&#xff1a;连接到redis服务 设置密码:在redis.windows.conf中找到requirepass 密码 服务停止&#xff1a; 在服务开启的界面按ctrlc Redis数据类…...

中科大:LLM几何推理数据生成

&#x1f4d6;标题&#xff1a;Enhancing the Geometric Problem-Solving Ability of Multimodal LLMs via Symbolic-Neural Integration &#x1f310;来源&#xff1a;arXiv, 2504.12773 &#x1f31f;摘要 &#x1f538;多模态大语言模型&#xff08;MLLM&#xff09;的最…...

AimRT从入门到精通 - 04RPC客户端和服务器

一、ROS中的service通信机制 服务通信也是ROS中一种极其常用的通信模式&#xff0c;服务通信是基于请求响应模式的&#xff0c;是一种应答机制。也即&#xff1a;一个节点A向另一个节点B发送请求&#xff0c;B接收处理请求并产生响应结果返回给A。比如如下场景&#xff1a; 机器…...

【Android】Intent

目录 一、什么是Intent 二、显式Intent 三、隐式Intent 四、复杂数据传递 五、跨应用权限管理 六、常见问题 一、什么是Intent 1. 跨组件通信桥梁 实现组件间通信&#xff08;Activity/Service/BroadcastReceiver&#xff09;封装操作指令与数据传输逻辑 目标组件启动…...

从0开始建立Github个人博客(hugoPaperMod)

从0开始建立Github个人博客(hugo&PaperMod) github提供给每个用户一个网址&#xff0c;用户可以建立自己的静态网站。 一、Hugo hugo是一个快速搭建网站的工具&#xff0c;由go语言编写。 1.安装hugo 到hugo的github标签页Tags gohugoio/hugo选择一个版本&#xff0c…...

Python集合全解析:从基础到高阶应用实战

一、集合核心特性与创建方法 1.1 集合的本质特征 Python集合&#xff08;Set&#xff09;是一种​​无序且元素唯一​​的容器类型&#xff0c;基于哈希表实现&#xff0c;具有以下核心特性&#xff1a; ​​唯一性​​&#xff1a;自动过滤重复元素​​无序性​​&#xff…...

Matlab自学笔记

一、我下载的是Matlab R2016a软件&#xff0c;打开界面如下&#xff1a; 二、如何调整字体大小&#xff0c;路径为&#xff1a;“主页”->“预设”->“字体”。 三、命令行窗口是直接进行交互式的&#xff0c;如下输入“3 5”&#xff0c;回车&#xff0c;就得到结果“…...

Python爬虫实战:获取好大夫在线各专业全国医院排行榜数据并分析,为患者就医做参考

一、引言 在当今医疗资源丰富但分布不均的背景下,患者在选择合适的心血管内科医院时面临诸多困难。好大夫在线提供的医院排行榜数据包含了医院排名、线上服务得分、患者评价得分等重要信息,对患者选择医院具有重要的参考价值。本研究通过爬取该排行榜数据,并进行深入分析,…...

多模态人工智能研究:视觉语言模型的过去、现在与未来

多模态人工智能研究&#xff1a;视觉语言模型的过去、现在与未来 1. 引言&#xff1a;定义多模态图景 多模态人工智能指的是旨在处理和整合来自多种数据类型或“模态”信息的人工智能系统&#xff0c;这些模态包括文本、图像、音频和视频等。与通常侧重于单一模态&#xff08;…...

DeepSeek+Excel:解锁办公效率新高度

目录 一、引言&#xff1a;Excel 遇上 DeepSeek二、认识 DeepSeek&#xff1a;大模型中的得力助手2.1 DeepSeek 的技术架构与原理2.2 DeepSeek 在办公场景中的独特优势 三、DeepSeek 与 Excel 结合的准备工作3.1 获取 DeepSeek API Key3.2 配置 Excel 环境 四、DeepSeekExcel 实…...

3033. 修改矩阵

​题目来源&#xff1a; leetcode题目&#xff1a;3033. 修改矩阵 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 获取每列的最大值后将-1替换即可。 解题代码&#xff1a; #python3 class Solution:def getMaxRow(matrix:List[List[int]])->List[int]:r…...

Android面试总结之jet pack模块化组件篇

一、ViewModel 深入问题 1. ViewModel 如何实现跨 Fragment 共享数据&#xff1f;其作用域是基于 Activity 还是 Fragment&#xff1f; 问题解析&#xff1a; ViewModel 的作用域由 ViewModelStoreOwner 决定。当 Activity 和其内部 Fragment 共享同一个 ViewModelStoreOwner…...

【无需docker】mac本地部署dify

环境安装准备 #安装 postgresql13 brew install postgresql13 #使用zsh的在全局添加postgresql命令集 echo export PATH"/usr/local/opt/postgresql13/bin:$PATH" >> ~/.zshrc # 使得zsh的配置修改生效 source ~/.zshrc # 启动postgresql brew services star…...

清洗数据集

将label在图片上画出来 按照第一行的属性分类 import os import cv2 import multiprocessing as mp from tqdm import tqdm# ---------- 路径配置 ---------- # IMAGE_DIR = r"C:\Users\31919\Desktop\datasets\13k_100drive_raw_with_hand\images\test" LABEL_DIR =…...

支持向量机(SVM)详解

引言 支持向量机&#xff08;Support Vector Machine, SVM&#xff09;是一种强大的监督学习算法&#xff0c;主要用于分类和回归任务。其核心思想是找到一个最优的决策边界&#xff08;超平面&#xff09;&#xff0c;最大化不同类别之间的间隔&#xff08;Margin&#xff09…...

MIT XV6 - 1.2 Lab: Xv6 and Unix utilities - pingpong

接上文 MIT XV6 - 1.1 Lab: Xv6 and Unix utilities - user/_sleep 是什么&#xff1f;做什么&#xff1f; pingpong 不务正业了那么久(然而并没有&#xff0c;虽然还在探索sleep&#xff0c;但是教材我已经看完了前三章了)&#xff0c;让我们赶紧继续下去 在进行本实验之前请务…...

“淘宝闪购”提前4天全量,意味着什么?

4月30日推出&#xff0c;首日上线50个城市&#xff0c;既定5月6日推广至全国的“淘宝闪购”&#xff0c;突然在5月2日早上官宣&#xff0c;提前4天面向全国消费者全量开放。 这一系列节奏&#xff0c;剑指一个字“快”&#xff01; 是业务发展远超预期的“快”。 4月30日&am…...

Servlet 解决了什么问题?

Servlet 主要解决了以下几个核心问题&#xff1a; 性能问题 (Performance): CGI 的问题&#xff1a; 传统的 CGI 技术为每个Web 请求都启动一个新的进程。进程的创建和销毁涉及大量的系统资源开销&#xff08;内存分配、CPU 时间、进程上下文切换等&#xff09;。在高并发场景下…...

Cherry Studio的MCP协议集成与应用实践:从本地工具到云端服务的智能交互

Cherry Studio的MCP协议集成与应用实践&#xff1a;从本地工具到云端服务的智能交互 一、MCP协议与Cherry Studio的技术融合 MCP&#xff08;Model Context Protocol&#xff09; 是由Anthropic提出的标准化协议&#xff0c;旨在为AI模型提供与外部工具交互的通用接口。通过M…...

CPU:AMD的线程撕裂者(Threadripper)系列

AMD的线程撕裂者&#xff08;Threadripper&#xff09;系列是AMD面向高性能计算&#xff08;HPC&#xff09;、工作站&#xff08;Workstation&#xff09;和高端桌面&#xff08;HEDT&#xff09;市场推出的顶级处理器产品线。该系列以极高的核心数、强大的多线程性能、丰富的…...

(即插即用模块-Attention部分) 六十二、(2022) LKA 大核注意力

文章目录 1、Larger Kernel Attention2、代码实现 paper&#xff1a;Visual Attention Network Code&#xff1a;https://github.com/Visual-Attention-Network 1、Larger Kernel Attention 自注意力机制在 NLP 领域取得了巨大成功&#xff0c;但其应用于计算机视觉任务时存在…...

Spring 分批处理 + 冷热数据分离:历史订单高效迁移与数据清理实战

在实际业务中&#xff0c;随着时间推移&#xff0c;订单量持续增长&#xff0c;若未及时进行数据治理&#xff0c;会造成数据库膨胀、查询缓慢、性能下降等问题。为了实现数据分层管理和系统高性能运行&#xff0c;我们在项目中采用了“冷热数据分离 分批迁移 数据清理”的综…...

Mybatis中的一级二级缓存扫盲

思维导图&#xff1a; MyBatis 提供了一级缓存和二级缓存机制&#xff0c;用于提高数据库查询的性能&#xff0c;减少对数据库的访问次数。&#xff08;本质上是减少IO次数&#xff09;。 一级缓存 1. 概念 一级缓存也称为会话缓存&#xff0c;它是基于 SqlSession 的缓存。在同…...

Elasticsearch 常用的 API 接口

文档类 API Index API &#xff1a;创建并建立索引&#xff0c;向指定索引添加文档。例如&#xff1a;PUT /twitter/tweet/1 &#xff0c;添加一个文档。 Get API &#xff1a;获取文档&#xff0c;通过索引、类型和 ID 获取文档。如GET /twitter/tweet/1。 DELETE API &…...

纯前端专业PDF在线浏览器查看器工具

纯前端专业PDF在线浏览器查看器工具 工具简介 我们最新开发的PDF在线浏览器工具现已发布&#xff01;这是一个基于Web的轻量级PDF阅读器&#xff0c;无需安装任何软件&#xff0c;直接在浏览器中即可查看和操作PDF文档。 主要功能 ✅ PDF文件浏览 支持本地PDF文件上传流畅的…...