【面试题】runtime 相关
一个 NSObject 对象占用多少内存?
objc4 源码
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
https://unix.org/version2/whatsnew/lp64_wp.html
LP64 (also known as 4/8/8) denotes long and pointer as 64 bit types
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
void setInstanceSize(uint32_t newSize) {
ASSERT(isRealized());
ASSERT(data()->flags & RW_REALIZING);
auto ro = data()->ro();
if (newSize != ro->instanceSize) {
ASSERT(data()->flags & RW_COPIED_RO);
*const_cast<uint32_t *>(&ro->instanceSize) = newSize;
}
cache.setFastInstanceSize(newSize);
}
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
{
runtimeLock.assertLocked();
class_ro_t *cls_ro_w, *meta_ro_w;
class_rw_t *cls_rw_w, *meta_rw_w;
cls_rw_w = objc::zalloc<class_rw_t>();
meta_rw_w = objc::zalloc<class_rw_t>();
cls_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
meta_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
cls->setData(cls_rw_w);
cls_rw_w->set_ro(cls_ro_w);
meta->setData(meta_rw_w);
meta_rw_w->set_ro(meta_ro_w);
// Set basic info
cls_rw_w->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
meta_rw_w->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING | RW_META;
cls_ro_w->flags = 0;
meta_ro_w->flags = RO_META;
if (superclass) {
uint32_t flagsToCopy = RW_FORBIDS_ASSOCIATED_OBJECTS;
cls_rw_w->flags |= superclass->data()->flags & flagsToCopy;
cls_ro_w->instanceStart = superclass->unalignedInstanceSize();
meta_ro_w->instanceStart = superclass->ISA()->unalignedInstanceSize();
cls->setInstanceSize(cls_ro_w->instanceStart);
meta->setInstanceSize(meta_ro_w->instanceStart);
} else {
cls_ro_w->flags |= RO_ROOT;
meta_ro_w->flags |= RO_ROOT;
cls_ro_w->instanceStart = 0;
meta_ro_w->instanceStart = (uint32_t)sizeof(objc_class);
cls->setInstanceSize((uint32_t)sizeof(id)); // just an isa
meta->setInstanceSize(meta_ro_w->instanceStart);
}
cls_ro_w->name.store(strdupIfMutable(name), std::memory_order_release);
meta_ro_w->name.store(strdupIfMutable(name), std::memory_order_release);
cls_ro_w->ivarLayout = &UnsetLayout;
cls_ro_w->weakIvarLayout = &UnsetLayout;
meta->chooseClassArrayIndex();
cls->chooseClassArrayIndex();
// This absolutely needs to be done before addSubclass
// as initializeToEmpty() clobbers the FAST_CACHE bits
cls->cache.initializeToEmpty();
meta->cache.initializeToEmpty();
#if FAST_CACHE_META
meta->cache.setBit(FAST_CACHE_META);
#endif
meta->setInstancesRequireRawIsa();
// Connect to superclasses and metaclasses
cls->initClassIsa(meta);
if (superclass) {
meta->initClassIsa(superclass->ISA()->ISA());
cls->setSuperclass(superclass);
meta->setSuperclass(superclass->ISA());
addSubclass(superclass, cls);
addSubclass(superclass->ISA(), meta);
} else {
meta->initClassIsa(meta);
cls->setSuperclass(Nil);
meta->setSuperclass(cls);
addRootClass(cls);
addSubclass(cls, meta);
}
addClassTableEntry(cls);
}
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
总结
- NSObject 对象只有一个 isa 指针成员变量
- LP64 指针类型内存中占 8 个字节
- 8 个字节 word_align 后返回 8 个字节
- 而 instanceSize 方法中限制所有对象至少 16 字节(CF requires all objects be at least 16 bytes)
- 所以一个 NSObject 对象占用 16 字节内存(但只使用了 8 字节存储 isa 指针)
验证
代码
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
// 获得 NSObject 类的实例对象的成员变量所占用的大小
size_t instanceSize = class_getInstanceSize([NSObject class]);
// 获得 obj 指针所指向内存大小
size_t mallocSize = malloc_size((__bridge const void *)(obj));
NSLog(@"instance size:%zu", instanceSize); // 8
NSLog(@"malloc size:%zu", mallocSize); // 16
}
return 0;
}
Debug
Debug -> Debug Workflow -> View Memory
其它
将 Objective-C 代码转换为 C\C++ 代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件 -o 输出文件
如果需要链接其它框架,使用 -framework 参数:-framework UIKit
常用 LLDB 指令
# 打印
print
p
# 打印对象
po
# 读取内存
# memory read/数量格式字节数 内存地址
# x/数量格式字节数 内存地址
x/3xw 0x10011001
# 写入内存(修改内存中的值)
# memory write 内存地址 数值
memory write 0x10011001 10
# 格式
# x: 16 进制
# d: 10 进制
# f: 浮点
# s: 字符串
# 字节大小(单位)
# b: byte 1 字节
# h: half word 2 字节
# w: word 4 字节
# g: giant word 8 字节
对象的 isa 指针指向哪里?
OC 中对象分为:
- 实例对象(instance object)
- 类对象(class object)
- 元类对象(meta-class object)
其中:
- 实例对象的 isa 指针指向类对象;
- 类对象的 isa 指针指向元类对象;
- 元类对象的 isa 指针指向基类的元类对象。
证明
执行代码:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
/*
objc4 源码中 isa.h L57-L113 宏定义(省略部分)
https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/isa.h#L57-L113
*/
# if __arm64__
// 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
# else
# define ISA_MASK 0x0000000ffffffff8ULL
# endif
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# else
# error unknown architecture for packed isa
# endif
@interface JQObject : NSObject
@end
@implementation JQObject
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建实例对象
JQObject *obj = [[JQObject alloc] init];
// 获取类对象
Class class = object_getClass(obj);
// 获取元类对象
Class metaClass = objc_getMetaClass("JQObject");
// 获取基类的元类对象
Class rootMetaClass = objc_getMetaClass("NSObject");
// 'obj->isa' 方法已过期,使用 'object_getClass(obj)' 替代获取 isa 指针
// Direct access to Objective-C's isa is deprecated in favor of object_getClass()
// Replace 'obj->isa' with 'object_getClass(obj)'
// long isa = (long)obj->isa;
// 将指针地址转换为长整型供计算
long objIsaAddress = (long)object_getClass(obj);
long classAddress = (long)class;
long classIsaAddress = (long)object_getClass(class);
long metaClassAddress = (long)metaClass;
long metaClassIsaAddress = (long)object_getClass(metaClass);
long rootClassAddress = (long)rootMetaClass;
// OC 运行时会对 isa 指针进行 ISA_MASK 与操作
// https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-object.h#L260
if ((objIsaAddress & ISA_MASK) == classAddress) {
NSLog(@"实例对象的 isa 指针指向类对象");
}
if ((classIsaAddress & ISA_MASK) == metaClassAddress) {
NSLog(@"类对象的 isa 指针指向元类对象");
}
if ((metaClassIsaAddress & ISA_MASK) == rootClassAddress) {
NSLog(@"元类对象的 isa 指针指向基类的元类对象");
}
}
return 0;
}
输出:
示例项目地址:
https://github.com/Coder-ZJQ/demos/tree/master/interview/oc-isa-to
OC 的类信息存放在哪里?
objc4 源码
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *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 vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct class_data_bits_t {
// ......
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// ......
}
struct class_rw_t {
// ......
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
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};
}
}
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
// ......
}
UML
classDiagram class Class class objc_class { Class superclass cache_t cache class_data_bits_t bits } class objc_object { Class isa } class NSObject { Class isa } class class_data_bits_t { class_rw_t* data } class class_rw_t { class_ro_t *ro method_array_t methods property_array_t properties protocol_array_t protocols } class class_ro_t { uint32_t instanceStart uint32_t instanceSize ivar_list_t * ivars } NSObject..>Class objc_object..>Class objc_class..>Class Class-->objc_class : typedef objc_class--|>objc_object objc_class..>class_data_bits_t class_data_bits_t..>class_rw_t class_rw_t..>class_ro_t link NSObject "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/NSObject.h#L53-L58" link Class "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc.h#L37-L38" link objc_class "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-runtime-new.h#L1688-L1696" link objc_object "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc.h#L40-L43" link class_data_bits_t "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-runtime-new.h#L1609-L1611" link class_rw_t "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-runtime-new.h#L1458-L1574" link class_ro_t "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-runtime-new.h#L1037-L1171"
cache_t 方法缓存
cache_t 相关快捷方法
struct cache_t {
struct bucket_t *buckets() const; // 散列表
unsigned capacity() const; // 散列表的长度
mask_t mask() const; // 散列表的长度 - 1
mask_t occupied() const; // 已缓存的方法数量
}
bucket_t 相关数据结构及方法
struct bucket_t {
private:
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
public:
inline SEL sel() const { return _sel.load(memory_order_relaxed); }
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
#define MAYBE_UNUSED_ISA
#else
#define MAYBE_UNUSED_ISA __attribute__((unused))
#endif
inline IMP rawImp(MAYBE_UNUSED_ISA objc_class *cls) const {
uintptr_t imp = _imp.load(memory_order_relaxed);
if (!imp) return nil;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
imp ^= (uintptr_t)cls;
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
#else
#error Unknown method cache IMP encoding.
#endif
return (IMP)imp;
}
inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {
uintptr_t imp = _imp.load(memory_order_relaxed);
if (!imp) return nil;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
SEL sel = _sel.load(memory_order_relaxed);
return (IMP)
ptrauth_auth_and_resign((const void *)imp,
ptrauth_key_process_dependent_code,
modifierForSEL(base, sel, cls),
ptrauth_key_function_pointer, 0);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
return (IMP)(imp ^ (uintptr_t)cls);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
return (IMP)imp;
#else
#error Unknown method cache IMP encoding.
#endif
}
}
向散列表中插入方法
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
runtimeLock.assertLocked();
// Never cache before +initialize is done
if (slowpath(!cls()->isInitialized())) {
return;
}
if (isConstantOptimizedCache()) {
_objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
cls()->nameForLogging());
}
#if DEBUG_TASK_THREADS
return _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCK
mutex_locker_t lock(cacheUpdateLock);
#endif
ASSERT(sel != 0 && cls()->isInitialized());
// Use the cache as-is if until we exceed our expected fill ratio.
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
散列表 hash 算法实现
// Class points to cache. SEL is key. Cache buckets store SEL+IMP.
// Caches are never built in the dyld shared cache.
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
value ^= value >> 7;
#endif
return (mask_t)(value & mask);
}
class_rw_t 与 class_ro_t
结构
通过阅读 obj4 源码可以得出 class_rw_t
与 class_ro_t
结构(部分省略),如下:
(objc4 源码版本:objc4-818.2)
flowchart LR subgraph class_rw_t A["class_ro_t *ro()"] B["method_array_t methods()"] C["property_array_t properties()"] D["protocol_array_t protocols()"] DD["......"] end subgraph class_ro_t E["uint32_t instanceSize;"] F["method_list_t *baseMethods()"] G["property_list_t *baseProperties;"] H["protocol_list_t *baseProtocols;"] I["const ivar_list_t * ivars;"] II["......"] end J["method_array_t"] K["property_array_t"] L["protocol_array_t"] M[method_list_t] N[property_list_t] O[protocol_list_t] P[ivar_list_t] subgraph method_t direction LR U["SEL name;"] V["const char *types;"] W["IMP imp;"] WW["......"] end subgraph property_t direction LR Q["const char *name;"] R["const char *attributes;"] end subgraph protocol_t direction LR X["struct protocol_list_t *protocols;"] Y["method_list_t *instanceMethods;"] Z["method_list_t *classMethods;"] ZZ["property_list_t *instanceProperties;"] ZZZ["......"] end subgraph ivar_t direction LR S["const char *name;"] T["const char *type;"] TT["......"] end A-->class_ro_t B-->J C-->K D-->L F-->M G-->N H-->O I-->P J-->M K-->N L-->O M-->method_t N-->property_t O-->protocol_t P-->ivar_t
class_ro_t
- class_ro_t 里面的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,是只读的,包含了类的初始内容;
- class_ro_t 编译时即确定;
class_rw_t
- class_rw_t 里面的 methods、properties、protocols 是二维数组,是可读可写的,包含了类的初始内容、分类的内容;
- class_rw_t 运行时才确定,且会先拷贝 class_ro_t 中的内容;
- class_rw_t 中已包含 class_ro_t 的相关内容,所以访问类的方法属性协议等主要通过 class_rw_t,不通过 class_ro_t。
拷贝 class_ro_t 中内容至 class_rw_t 源码:
/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name()));
}
ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
}
#endif
}
objc_msgSend 执行流程
flowchart TB A(objc_msgSend) B{消息接收者是否为nil} C([退出]) D{在 cache 中查找} E{在 class_rw_t 中查找} F{在 cache 中查找} G{在 class_rw_t 中查找} H{superclass == nil} I{是否动态方法解析} J([调用方法结束查找]) K([调用方法结束查找并将方法缓存至 cache]) L[+resolveInstanceMethod:] M[+resolveClassMethod:] N{类对象还是实例对象} O{+/- forwardingTargetForSelector:} P{+/- methodSignatureForSelector:} Q[调用 +/- forwardInvocation: 方法] R[调用 doesNotRecognizeSelector: 方法抛出异常] S["调用 objc_msgSend(返回值, selector)"] subgraph 消息发送 subgraph "消息接受者的类对象(isa)" D E end subgraph "消息接受者的父类对象(superclass)" F G end B C H J K end subgraph 动态方法解析 I L M N end subgraph 消息转发 O P Q R S end A-->B B-->|是|C B-->|否|D D-->|未找到|E E-->|未找到|H H-->|否|F H-->|是|I F-->|未找到|G G-->|未找到|H D-->|找到了|J E-->|找到了|K F-->|找到了|J G-->|找到了|K I-->|否|N I-->|是|O N-->|实例对象|L N-->|类对象|M L-->|返回YES|D M-->|返回YES|D O-->|返回值不为nil|S O-->|返回值为nil|P P-->|返回值不为nil|Q P-->|返回值为nil|R
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface JQForwardObject : NSObject
- (void)test;
+ (void)test;
@end
@implementation JQForwardObject
- (void)test {
NSLog(@"\nfunc: %s\nreceiver: %@\nsel: %@", __func__, self, NSStringFromSelector(_cmd));
}
+ (void)test {
NSLog(@"\nfunc: %s\nreceiver: %@\nsel: %@", __func__, self, NSStringFromSelector(_cmd));
}
@end
@interface JQObject : NSObject
- (void)test;
+ (void)test;
@end
@implementation JQObject
/* 1. 动态消息解析
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(test)) {
Method met = class_getInstanceMethod(self, @selector(resolveMethod));
class_addMethod(object_getClass(self), sel, method_getImplementation(met), method_getTypeEncoding(met));
return YES;
}
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
class_addMethod(self, sel, (IMP)resolveMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)resolveMethod {
NSLog(@"\nfunc: %s\nreceiver: %@\nsel: %@", __func__, self, NSStringFromSelector(_cmd));
}
void resolveMethod(id self, SEL _cmd) {
NSLog(@"\nfunc: %s\nreceiver: %@\nsel: %@", __func__, self, NSStringFromSelector(_cmd));
}
*/
/*
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[JQForwardObject alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [JQForwardObject class];
}
return [super forwardingTargetForSelector:aSelector];
}
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// anInvocation getReturnValue:<#(nonnull void *)#>
// anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#>
// anInvocation invoke
// [anInvocation invokeWithTarget:<#(nonnull id)#>];
NSLog(@"%@", anInvocation);
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%@", anInvocation);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[[JQObject alloc] init] test];
[JQObject test];
}
return 0;
}