理解objc运行时二:类的结构(runtime.h)

类的数据结构(runtime.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

__OBJC2__

If you are running 10.5 or later, or any version of iOS, your computer is running Objective-C 2. If you are writing code which you want to work on systems before this, you can check for the OBJC2 macro, which will be defined only for Objective-C 2 and later systems.

参考

OBJC2_UNAVAILABLE

1
2
3
4
5
6
7
8
9
10
11
12
13
/* OBJC2_UNAVAILABLE: unavailable in objc 2.0, deprecated in Leopard */
/*主要是为了兼容以前的OC版本,目前条件下,可忽略 */
#if !defined(OBJC2_UNAVAILABLE)
# if __OBJC2__
# define OBJC2_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
# else
/* plain C code also falls here, but this is close enough */
# define OBJC2_UNAVAILABLE \
__OSX_DEPRECATED(10.5, 10.5, "not available in __OBJC2__") \
__IOS_DEPRECATED(2.0, 2.0, "not available in __OBJC2__") \
__TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE
# endif
#endif

因此可直接忽略掉OBJC2OBJC2_UNAVAILABLE

objc_class结构体

名称 含义
isa isa指针
super_class 父类
name 类名
version 类的版本号
instance_size 类的大小
ivars 变量列表
methodLists 方法列表
cache 方法缓存列表
protocols 协议列表

Q: isa指针指向哪?

先了解下OC的类的继承体系

  • isa指针与superclass

要知道isa指针的指向,需要先了解元类,

元类(metaClass)

因为类也是对象(struct objc_class : objc_object {…}), 元类可近似认为是类对象的isa指针所指向的类

方法调用

对象封装了变量和方法,变量是数据,每个对象都不尽相同,但方法是操作,每个对象应该都一样,所有的对象共享一份方法的代码,放在类中,而因为类本身也是一个对象,当向类发送消息时,该类的isa指针应该指向保存类方法的地址,由上可得,元类中保存着类方法

对象方法的查找顺序:
当前类 -> 父类 -> … -> 根类

类方法的查找顺序:
当前类的元类 -> 元类父类 -> 根元类

A:总结

  • 对象的isa指针指向类对象
  • 类对象的isa指针指向该类对象对应的元类对象
  • 所有元类对象的isa指针统一指向NSObject元类对象

  • 类对象的superclass指向父类的类对象,回溯而上直到NSObject类对象
  • NSObjectsuperclass指向nil

  • NSObject元类对象的父类为NSObject类对象
  • NSObject类对象的isa指针指向NSObject元类对象

验证

objc-706工程中,在debug-objc targetmain.mm添加测试代码

1
2
3
objc_class *cls_a_s = (objc_class *)[NSString class];
objc_class *cls_b_s = (objc_class *)cls_a_s->getMeta();
NSLog(@"%p,%p",cls_a_s,cls_b_s);

输出结果:

观察 + 计算

0x1dffffb6c19d89 = 0x1d8001004a10f1 - 0x1004a10f0 + 0x7fffb6c19d88

类对象的isa指针指向地址满足下面的等式

addr(class->isa) = addr(metaclass->isa) - addr(metaclass->superclass) + addr(metaclass)

另一个测试用例

1
2
3
objc_class *cls_a = (objc_class *)[NSObject class];
objc_class *cls_b = (objc_class *)cls_a->getMeta();
NSLog(@"%p,%p",cls_a,cls_b);

0x1d8001004a10f1 = 0x1d8001004a10f1 - addr(NSObject) + 0x1004a1140

NSObject类地址是0x1004a1140,上式也成立

Q: 如何获得super_class,version,name,instance_size的值

获取super_class

1
2
3
4
5
6
7
8
9
10
11
12
/** 
* Returns the superclass of a class.
*
* @param cls A class object.
*
* @return The superclass of the class, or \c Nil if
* \e cls is a root class, or \c Nil if \e cls is \c Nil.
*
* @note You should usually use \c NSObject's \c superclass method instead of this function.
*/
OBJC_EXPORT Class class_getSuperclass(Class cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

获取version

1
2
3
4
5
6
7
8
9
10
11
12
/** 
* Returns the version number of a class definition.
*
* @param cls A pointer to a \c Class data structure. Pass
* the class definition for which you wish to obtain the version.
*
* @return An integer indicating the version number of the class definition.
*
* @see class_setVersion
*/
OBJC_EXPORT int class_getVersion(Class cls)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

获取name

1
2
3
4
5
6
7
8
9
/** 
* Returns the name of a class.
*
* @param cls A class object.
*
* @return The name of the class, or the empty string if \e cls is \c Nil.
*/
OBJC_EXPORT const char *class_getName(Class cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

获取instance_size

1
2
3
4
5
6
7
8
9
/** 
* Returns the size of instances of a class.
*
* @param cls A class object.
*
* @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
*/
OBJC_EXPORT size_t class_getInstanceSize(Class cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

测试代码

1
2
3
4
5
6
7
8
9
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"NSString类的super_class是%@",class_getSuperclass([NSString class]));
NSLog(@"NSString类的version是%d",class_getVersion([NSString class]));
NSLog(@"NSString类的name是%s",class_getName([NSString class]));
NSLog(@"NSString类的instance_size是%ld",class_getInstanceSize([NSString class]));
}
return 0;
}

输出结果:
Snip20161221_1.png

Q:如何获得ivars,methodLists,protocols

获得ivars

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** 
* Describes the instance variables declared by a class.
*
* @param cls The class to inspect.
* @param outCount On return, contains the length of the returned array.
* If outCount is NULL, the length is not returned.
*
* @return An array of pointers of type Ivar describing the instance variables declared by the class.
* Any instance variables declared by superclasses are not included. The array contains *outCount
* pointers followed by a NULL terminator. You must free the array with free().
*
* If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0.
*/
OBJC_EXPORT Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

获得methodLists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** 
* Describes the instance methods implemented by a class.
*
* @param cls The class you want to inspect.
* @param outCount On return, contains the length of the returned array.
* If outCount is NULL, the length is not returned.
*
* @return An array of pointers of type Method describing the instance methods
* implemented by the class—any instance methods implemented by superclasses are not included.
* The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
*
* If cls implements no instance methods, or cls is Nil, returns NULL and *outCount is 0.
*
* @note To get the class methods of a class, use \c class_copyMethodList(object_getClass(cls), &count).
* @note To get the implementations of methods that may be implemented by superclasses,
* use \c class_getInstanceMethod or \c class_getClassMethod.
*/
OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

获得protocols

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** 
* Describes the protocols adopted by a class.
*
* @param cls The class you want to inspect.
* @param outCount On return, contains the length of the returned array.
* If outCount is NULL, the length is not returned.
*
* @return An array of pointers of type Protocol* describing the protocols adopted
* by the class. Any protocols adopted by superclasses or other protocols are not included.
* The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
*
* If cls adopts no protocols, or cls is Nil, returns NULL and *outCount is 0.
*/
OBJC_EXPORT Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#import "objc-private.h"
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class cls = [NSObject class];
unsigned int count = 0;
Ivar *list = class_copyIvarList(cls, &count);
for (unsigned int i = 0; i < count; ++i) {
Ivar ity = list[i];
const char *iname = ivar_getName(ity);
NSLog(@"%@\n",[NSString stringWithUTF8String:iname]);
}
free(list);

objc_property_t *list2 = class_copyPropertyList(cls, &count);
for (unsigned int i = 0; i < count; ++i) {
objc_property_t ity = list2[i];
const char *iname = property_getName(ity);
NSLog(@"%@\n",[NSString stringWithUTF8String:iname]);
}
free(list2);

Method *methods = class_copyMethodList(cls, &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methods[i];
printf("\t'%s'|'%s' of encoding '%s'\n",
class_getName(cls),
sel_getName(method_getName(method)),
method_getTypeEncoding(method));

}
free(methods);

Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &count);
Protocol * protocol;
for (unsigned int i = 0; i < count; i++) {
protocol = protocols[i];
NSLog(@"protocol name: %s", protocol_getName(protocol));
}

}
return 0;
}

输出效果

获得cache

为已有类添加ivar,property,method,protocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** 
* Adds a new instance variable to a class.
*
* @return YES if the instance variable was added successfully, otherwise NO
* (for example, the class already contains an instance variable with that name).
*
* @note This function may only be called after objc_allocateClassPair and before objc_registerClassPair. 需要在obj_allocateClassPair与objc_registerClassPair之间调用
* Adding an instance variable to an existing class is not supported. 不支持为已有的类添加实例变量
* @note The class must not be a metaclass. Adding an instance variable to a metaclass is not supported. 不支持为元类添加实例变量
* @note The instance variable's minimum alignment in bytes is 1<<align. The minimum alignment of an instance
* variable depends on the ivar's type and the machine architecture.
* For variables of any pointer type, pass log2(sizeof(pointer_type)).
*/
OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size,
uint8_t alignment, const char *types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

1
2
3
4
5
6
7
8
9
10
11
12
13
/** 
* Adds a property to a class.
*
* @param cls The class to modify.
* @param name The name of the property.
* @param attributes An array of property attributes.
* @param attributeCount The number of attributes in \e attributes.
*
* @return \c YES if the property was added successfully, otherwise \c NO
* (for example, the class already has that property).
*/
OBJC_EXPORT BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** 
* Adds a new method to a class with a given name and implementation.
*
* @param cls The class to which to add a method.
* @param name A selector that specifies the name of the method being added.
* @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
* @param types An array of characters that describe the types of the arguments to the method.
*
* @return YES if the method was added successfully, otherwise NO
* (for example, the class already contains a method implementation with that name).
*
* @note class_addMethod will add an override of a superclass's implementation,
* but will not replace an existing implementation in this class.
* To change an existing implementation, use method_setImplementation.
*/
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char *types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

1
2
3
4
5
6
7
8
9
10
11
/** 
* Adds a protocol to a class.
*
* @param cls The class to modify.
* @param protocol The protocol to add to \e cls.
*
* @return \c YES if the method was added successfully, otherwise \c NO
* (for example, the class already conforms to that protocol).
*/
OBJC_EXPORT BOOL class_addProtocol(Class cls, Protocol *protocol)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

测试代码

添加proerty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class cls = [NSObject class];
unsigned int count = 0;
/*
Defines a property attribute
typedef struct {
const char *name; *< The name of the attribute
const char *value; *< The value of the attribute (usually empty)
} objc_property_attribute_t;
*/
objc_property_attribute_t t = {"T","123"};
class_addProperty(cls, "newProperty",&t, 1);
objc_property_t *list2 = class_copyPropertyList(cls, &count);
for (unsigned int i = 0; i < count; ++i) {
objc_property_t ity = list2[i];
const char *iname = property_getName(ity);
NSLog(@"%@\n",[NSString stringWithUTF8String:iname]);
}
free(list2);
}
return 0;
}

添加method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class cls = [NSObject class];
unsigned int count = 0;
IMP imp = imp_implementationWithBlock(^{ NSLog(@"add a new method"); });
BOOL r = class_addMethod(cls, @selector(newMethod), imp, nil);
Method *methods = class_copyMethodList(cls, &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methods[i];
printf("\t'%s'|'%s' of encoding '%s'\n",
class_getName(cls),
sel_getName(method_getName(method)),
method_getTypeEncoding(method));

}
free(methods);
[(id)cls performSelector:@selector(newMethod)];
}
return 0;
}

添加protocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class cls = [NSObject class];
unsigned int count = 0;
// objc_addProtocol();
Protocol *p = objc_allocateProtocol("newProtocol");
objc_registerProtocol(p);
class_addProtocol(cls,p);

Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &count);
Protocol * protocol;
for (unsigned int i = 0; i < count; i++) {
protocol = protocols[i];
NSLog(@"protocol name: %s", protocol_getName(protocol));
}

}
return 0;
}

:以上函数声明摘自于iOS10.1runtime.h

参考

理解objc运行时三:方法编码,执行,转发,交换 快速生成iOS应用的图标
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×