首先感谢祖国,可以无忧无虑的码代码 ~
If I have seen further, it is by standing on the shoudlers of giants.
想这样一个场景
某天亲爱的产品突然有个需求,统计app
中所有UIButton
的点击次数,由于项目比较庞大,并且使用了超多的UIButton
来交互,那么怎么实现呐?
当然我们可以寻找代码里面所有的UIButton
的点击事件,然后创建一个全局的计数器来进行统计,这个做法可以解决这个问题,但是之后的维护呐?如果寻找的时候发生了遗漏怎么办?新加入的同事不知道这种做法怎么办?显然这不能算是一个好的做法。
当然我们也可以创建一个UIButton
的子类,然后重写它的点击事件,这种策略虽然好一点,但是我们需要寻找代码中的所有UIButton
,且改变其父类。但是如果寻找的时候发生遗漏怎么办?如果新同事不知道这样的要求怎么办?如果在代码中使用了UIButton
的其他子类的话,我们还要再去为这些子类创建新的子类。显然这也不能算是一个好的做法
其实这个时候我们的runtime
就派上了用处
如果各位看官没有想到怎么使用runtime
的话,且容笔者留个悬念,后续揭晓 ~
o(* ̄3 ̄)o
Objective-C
是一门动态语言,这意味着Objective-C
不仅仅需要一个编译器,还需要一个运行时系统来执行编译后的代码,运行时系统让所有的工作正常运行,runtime其实是一个runtime库,绝大部分使用C和汇编码的,正是由于这个库,使得Objective-C具备了面向对象的能力。
1. 类与基础数据结构
1.1 Class
OC
类是由Class
类型表示的,实际上是一个指向objc_class
结构体(objc/objc.h
)的指针
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
而 结构体objc_class
(objc/runtime.h
)的定义为
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
-
isa
:在OC
中,所有的类本身也是一个对象,这个对象的Class
里面有一个指针,指向该类metaClass
-
super_class
:指向该类的父类,如果该类已经是顶层根类(NSObject
或NSProxy
)的话,则为NULL
-
name
:类的名字 -
version
:使用这个字段提供类的版本信息,默认为0,这对于对象的序列化非常有用,可以识别出 不同 版本类 定义中 实例变量布局 的改变 -
info
:提供运行时使用的一些标识,有如下一些位掩码:
CLS_CLASS (0x1L)
表示该类为普通class
,其中包含实例方法和变量
CLS_META (0x2L)
表示该类为metaclass
,其中包含类方法
CLS_INITIALIZED (0x4L)
表示该类已经被运行时初始化了,这个标识位只被objc_addClass
所设置
CLS_POSING (0x8L)
表示该类被pose
成其他的类;(poseclass
在ObjC 2.0
中被废弃了)
CLS_MAPPED (0x10L)
为运行时所使用
CLS_FLUSH_CACHE (0x20L)
为运行时所使用
CLS_GROW_CACHE (0x40L)
为运行时所使用
CLS_NEED_BIND (0x80L)
为运行时所使用
CLS_METHOD_ARRAY (0x100L)
该标志位指示methodLists
是指向一个objc_method_list
还是一个包含objc_method_list
指针的数组; -
instance_size
:该实例变量的大小,包括从父类继承下来的实例变量 -
ivars
:成员变量数组 -
methodList
:方法的数组,与info
的一些标志位有关,CLS_METHOD_ARRAY
标识位决定其指向的是,单个objc_method_list
,还是一个objc_method_list
指针数组,如果info
设置了CLS_CLASS
则objc_method_list
存储实例方法,如果设置的是CLS_META
则存储类方法 -
cache
:用于缓存最近使用到的方法,一个接收者对象接收到消息时候,会依据isa
指针去查找可以响应该消息的对象。在实际使用的时候,对象只有部分方法是常用的,这种情况下,如果每次来消息时候都去methodLists
中遍历一遍的话,会影响性能,这个时候就用上了cache
,每次调用一个方法后,这个方法就会被缓存到cache
列表中,下次调用的时候runtime
就会优先到cache
中查找,如果没有查到的话,才去methodList
中查找方法,这样对于经常用到的方法可以提高效率 -
protocols
:协议的数组
OC
为每个类生成两个objc_class
,一个为普通的class
,另一个为metaClass
。我们可以在运行时创建这两个objc_class
的数据结构,然后使用objc_addClass
动态地创建新的类
1.2 objc_object 和 id
objc_object
(objc/objc.h
)表示一个类的实例结构体
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
objc_object
只有一个指向其类的指针,当像一个对象发送消息的时候,运行时会依据实例对象的isa
指针找到这个实例对象所属的类,运行时会在类方法列表及父类的方法列表中去寻找与消息对应的selector
指向的方法,然后执行这个方法
当创建一个特定类的实例变量的时候,分配的内存会包含一个objc_object
数据结构,然后是类的实例变量的数据。NSObject
的alloc
和allocWithZone:
使用函数class_createInstance
来创建objc_object
数据结构
id
类型,是一个objc_object
结构体类型的指针,id
的存在可以实现类似于C++
中泛型的一些操作,有点类似C
中void *
指针类型的作用
1.3 objc_cache
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method _Nullable buckets[1] OBJC2_UNAVAILABLE;
};
objc_class
中的cache
,用于缓存用过的方法,这个字段是一个指向objc_cache
结构体的指针
-
mask
:一个整数,指定分配的buckets
的数量,在运行时 查找方法的过程中,使用这个字段来确定 开始线性查找 数组的索引的位置,指向 方法selector
的指针 与 该字段做AND
位操作(index = mask & selector
),可以作为一个简单的hash散列算法
-
occupied
:一个整数,指定实际占用的缓存bucket
的总数 -
buckets
:指向Method
数据结构指针的数组,这个数组长度不能超过mask + 1
。这个指针也可能是NULL
,表示这个buckets
没有被占用,被占用的bucket
可能是不连续的,这个数组内元素也可能随着时间而增加
1.4 元类 MetaClass
metaClass
存储着一个类的所有的类方法,每个类都会有一个单独的metaClass
所有类的自身也是一个对象,可以向这个对象发送消息 即调用类方法
eg:
NSArray *array = [NSArray array];
+ (instancetype)array;
NSArray
调用类方法,即发送消息给NSArray
NSArray
是一个对象,因此也会有一个objc_object
类型的指针,包含一个指向其类的isa
指针(参考 1.2),为了调用+ array
方法,这个类的isa
指针,必须指向一个包含这些类方法的objc_class
(参考1.1)的结构体
即 metaClass
是一个类对象的类
当运行时向一个对象发送消息的时候,运行时会在这个对象所属类的方法列表中查找方法
当运行时向一个类对象发送消息的时候,运行时会在这个类的metaClass
的方法列表中查找
关于类与metaClass
的继承体系如下:
我们知道metaClass
也是一个类,也可以向其发送消息,那么metaClass
的isa
指针又指向了哪里,为了不让这种结构无限延伸下去,所有的metaClass
指向其基类的metaClass
作为其所属类,而基类的metaClass
的isa
指针指向其自身,形成闭环。即任何NSObject
继承体系下的metaClass
都使用NSObject
的metaClass
来作为期所属类,基类的metaclass
指向其metaclass
自身,如上图
对于nsobject
的继承体系来讲,其实例方法对于体系中的所有实例、类、metaclass
都是有效的;而类方法对与体系内的所有类和metaclass
都是有效的
eg:
Class newClass = objc_allocateClassPair([NSString class], "NewClass", 0);
class_addMethod(newClass, @selector(showMetaClass), (IMP)ShowMetaClass, "v@:");
objc_registerClassPair(newClass);
id instance = [[newClass alloc] init];
[instance performSelector:@selector(showMetaClass)];
void ShowMetaClass(id self, SEL _cmd) {
NSLog(@"this object is --> %p, class is --> %@, superclass is --> %@", self, [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 0; i < 5; i++) {
// i == 0 实例对象 currentClass 的地址和类,实例对象的 isa 指向 当前对象的所属类
NSLog(@"次数 --> %d, 当前类或者实例所属类 --> %@, isa指针 指向 --> %@ == %p", i, currentClass, object_getClass(currentClass), object_getClass(currentClass));
currentClass = objc_getMetaClass(object_getClassName(currentClass));
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's metaclass is %p", object_getClass(CFBridgingRelease((__bridge void *)[NSObject class])));
NSLog(@"NSObject's metaclass is %@", objc_getMetaClass(object_getClassName([NSObject class])));
}
// 输出:
this object is --> 0x600000013c80, class is --> NewClass, superclass is --> NSString
次数 --> 0, 当前类或者实例所属类 --> NewClass, isa指针 指向 --> NewClass == 0x60000005ea20
次数 --> 1, 当前类或者实例所属类 --> NewClass, isa指针 指向 --> NSObject == 0x10ac96e38
次数 --> 2, 当前类或者实例所属类 --> NSObject, isa指针 指向 --> NSObject == 0x10ac96e38
次数 --> 3, 当前类或者实例所属类 --> NSObject, isa指针 指向 --> NSObject == 0x10ac96e38
次数 --> 4, 当前类或者实例所属类 --> NSObject, isa指针 指向 --> NSObject == 0x10ac96e38
NSObject's class is 0x10ac96e88
NSObject's metaclass is 0x10ac96e38
NSObject's metaclass is NSObject
这个例子是在运行时,动态的创建一个NSString
的子类NewClass
,然后为这个类添加一个方法showMetaClass
,方法的实现是ShowMetaClass
这里有两个方法:
objc_getClass
参数是类名的字符串,返回的是这个类的实例对象
object_getClass
参数是id
类型,返回的是这个id
类型的isa
指针所指向的class
我们查看输出:
第一次for
循环,currentClass
是实例对象,所以其isa
指向当前类NewClass
第二次for
循环,currentClass
变成了类NewClass
,所以其isa
指向了当前类的元类NewClass
第三次for
循环,currentClass
变成了元类NewClass
,所以其isa
指向了元类的父类,即NSObject
之后的循环就是 元类NSObject
的isa
指向当前元类自身了(可以结合 图1.4)~
2. 类与对象的操作函数
类的操作函数大多以class_
为前缀,对象的操作函数大多以objc_
或者object_
为前缀
2.1 类的操作函数
-
class_getName
获取类名
/* Working with Classes */
/**
* 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 * _Nonnull
class_getName(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
class_getSuperclass
获取父类
/**
* 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 _Nullable
class_getSuperclass(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
class_isMetaClass
判断一个类是否是元类
/**
* Returns a Boolean value that indicates whether a class object is a metaclass.
*
* @param cls A class object.
*
* @return \c YES if \e cls is a metaclass, \c NO if \e cls is a non-meta class,
* \c NO if \e cls is \c Nil.
*/
OBJC_EXPORT BOOL
class_isMetaClass(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
class_getInstanceSize
获取实例变量的大小
/**
* 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 _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
// ---
NSString *str = @"123";
NSLog(@"%zu", class_getInstanceSize([str class]));
// 8
2.2 成员变量和属性
在object_class
中,所有的成员变量、属性是放在链表ivars
中(参考1.1),ivars
是一个数组,数组中的每个元素是指向Ivar
变量信息的指针
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
2.2.1 成员变量操作函数
-
ivar_getName
获取成员变量的名字
/**
* Returns the name of an instance variable.
*
* @param v The instance variable you want to enquire about.
*
* @return A C string containing the instance variable's name.
*/
OBJC_EXPORT const char * _Nullable
ivar_getName(Ivar _Nonnull v)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
ivar_getTypeEncoding
获取成员变量的类型编码
/**
* Returns the type string of an instance variable.
*
* @param v The instance variable you want to enquire about.
*
* @return A C string containing the instance variable's type encoding.
*
* @note For possible values, see Objective-C Runtime Programming Guide > Type Encodings.
*/
OBJC_EXPORT const char * _Nullable
ivar_getTypeEncoding(Ivar _Nonnull v)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
ivar_getOffset
获取成员变量的偏移量
对于id
类型 或 其他对象类型的变量,可以使用object_getIvar
和object_setIvar
来直接访问成员变量,而不使用偏移量
/**
* Returns the offset of an instance variable.
*
* @param v The instance variable you want to enquire about.
*
* @return The offset of \e v.
*
* @note For instance variables of type \c id or other object types, call \c object_getIvar
* and \c object_setIvar instead of using this offset to access the instance variable data directly.
*/
OBJC_EXPORT ptrdiff_t
ivar_getOffset(Ivar _Nonnull v)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
class_getInstanceVariable
获取类中指定名称的属性的信息,也可以是私有属性,也可以获取父类的
返回的是一个指向包含指定name
的成员变量信息的objc_ivar
结构体的指针Ivar
/**
* Returns the \c Ivar for a specified instance variable of a given class.
*
* @param cls The class whose instance variable you wish to obtain.
* @param name The name of the instance variable definition to obtain.
*
* @return A pointer to an \c Ivar data structure containing information about
* the instance variable specified by \e name.
*/
OBJC_EXPORT Ivar _Nullable
class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
eg:
想要获取ffSpringInfo
父类中属性@property (nonatomic, copy) NSString *ff_seasonStr;
的值
@interface FFSeasonModel ()
@property (nonatomic, copy) NSString *ff_seasonStr; // 初始化为 @"这是父类的私有属性"
@end
// ---
Ivar strSuperIvar = class_getInstanceVariable([FFSpringModel class], "_ff_seasonStr");
const char * strSuperName = ivar_getName(strSuperIvar);
const char * strSuperEncoding = ivar_getTypeEncoding(strSuperIvar);
ptrdiff_t strSuperOffset = ivar_getOffset(strSuperIvar);
NSLog(@"%s, %s, %td", strSuperName, strSuperEncoding, strSuperOffset);
// _ff_seasonStr, @"NSString", 8
id strSuperValue = object_getIvar(ffSpringInfo, strSuperIvar);
NSLog(@"%@", strSuperValue);
// 这是父类的私有属性
想要获取ffSpringInfo
中属性@property (nonatomic, copy) NSString *springStr;
的值
FFSpringModel *ffSpringInfo = [FFSpringModel new];
ffSpringInfo.springStr = @"ff_spring";
ffSpringInfo.springBool = false;
Ivar strSpringIvar = class_getInstanceVariable([ffSpringInfo class], "_springStr");
const char * strName = ivar_getName(strSpringIvar);
const char * strEncoding = ivar_getTypeEncoding(strSpringIvar);
ptrdiff_t strOffset = ivar_getOffset(strSpringIvar);
NSLog(@"%s, %s, %td", strName, strEncoding, strOffset);
// _springStr, @"NSString", 16
id strValue = object_getIvar(ffSpringInfo, strSpringIvar);
NSLog(@"%@", strValue);
// ff_spring
Ivar boolSpringIvar = class_getInstanceVariable([ffSpringInfo class], "_springBool");
const char * boolName = ivar_getName(boolSpringIvar);
const char * boolEncoding = ivar_getTypeEncoding(boolSpringIvar);
ptrdiff_t boolOffset = ivar_getOffset(boolSpringIvar);
NSLog(@"%s, %s, %td", boolName, boolEncoding, boolOffset);
// _springBool, B, 8
id boolValue = object_getIvar(ffSpringInfo, boolSpringIvar);
NSLog(@"%@", boolValue);
// (null)
疑问:
这里存在一个问题,object_getIvar
方法如果传入值的最终类型是bool的,如果bool的值为true
的话 程序会crash
,如果为false
返回null
,这里没想明白
插入知识点:
iOS6
之后LLVM
编译器引入了property autosynthesis
(属性自动合成),编译器会为每个@property
添加@synthesize
@synthesize propertyName = _propertyName;
这行代码会创建一个带下划线前缀的变量名,同时使用这个属性生成setter
和getter
属性
-
class_getClassVariable
获取指定类 的 指定名字的 类变量 的详细信息,一般认为OC
不支持类变量,笔者认为这里应该指的是类的元类,而类的元类只有一个变量为isa
/**
* Returns the Ivar for a specified class variable of a given class.
*
* @param cls The class definition whose class variable you wish to obtain.
* @param name The name of the class variable definition to obtain.
*
* @return A pointer to an \c Ivar data structure containing information about the class variable specified by \e name.
*/
OBJC_EXPORT Ivar _Nullable
class_getClassVariable(Class _Nullable cls, const char * _Nonnull name)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:
Ivar springMetaClassIvar = class_getClassVariable([FFSpringModel class], "isa");
const char * mcName = ivar_getName(springMetaClassIvar);
const char * mcEncoding = ivar_getTypeEncoding(springMetaClassIvar);
ptrdiff_t mcOffset = ivar_getOffset(springMetaClassIvar);
NSLog(@"%s, %s, %td", mcName, mcEncoding, mcOffset);
// isa, #, 0
-
class_addIvar
OC
不支持向已经存在类中添加实例变量,因此无论是系统提供的类还是自定义的类都无法动态的添加实例变量
class_addIvar
函数用于在运行时创建一个类,并且向类中添加实例变量
这个函数只能在objc_allocateClassPair
和objc_registerClassPair
之间调用
向一个元类中添加变量也不允许
/**
* 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.
* 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 _Nullable cls, const char * _Nonnull name, size_t size,
uint8_t alignment, const char * _Nullable types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:
Class FFRuntimeClass = objc_allocateClassPair([FFSeasonModel class], "FFRuntimeModel", 0);
BOOL isSucc = class_addIvar(FFRuntimeClass, "ff_runtime_str", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
if (isSucc) {
NSLog(@"添加成功");
}
objc_registerClassPair(FFRuntimeClass);
id runtimeClass = [[FFRuntimeClass alloc] init];
[runtimeClass setValue:@"这是 ff_runtime_str 的值" forKey:@"ff_runtime_str"];
id value = object_getIvar(runtimeClass, class_getInstanceVariable([runtimeClass class], "ff_runtime_str"));
NSLog(@"%@", value);
// 添加成功
// 这是 ff_runtime_str 的值
使用KVO
给创建的变量赋值,使用object_getIvar
来取值
-
class_copyIvarList
返回一个纸箱成员变量信息的数组,数组中的每个元素是指向该成员变量信息的objc_ivar
结构体的指针,outCount
指针返回数组的大小
/**
* 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 _Nonnull * _Nullable
class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);ins
这里有个细节,先看下面栗子:
// 这是定义在 FFSpringModel.h 中的属性
@interface FFSpringModel : FFSeasonModel
@property (nonatomic, copy) NSString *h_p_str;
@property (nonatomic, assign) NSString *h_p_bool;
@property (nonatomic, strong) NSMutableArray *h_p_arr;
@end
// 这是定义在 FFSpringModel.m 中的变量和属性
@interface FFSpringModel() {
NSArray *m_array;
NSString *m_str;
BOOL *m_bool;
}
@property (nonatomic, copy) NSString *m_p_str;
@property (nonatomic, assign) NSString *m_p_bool;
@property (nonatomic, strong) NSMutableArray *m_p_arr;
@end
我们对FFSpringModel
分别使用class_copyIvarList
和class_copyPropertyList
来对比结果
unsigned int i_outCount = 0;
Ivar *ivars = class_copyIvarList([FFSpringModel class], &i_outCount);
for (int i = 0; i < i_outCount; i ++) {
NSLog(@"class_copyIvarList ---> %s", ivar_getName(ivars[i]));
}
free(ivars);
// 2018-03-24 15:32:30.132 runtime[2906:197694] class_copyIvarList ---> m_array
// 2018-03-24 15:32:30.133 runtime[2906:197694] class_copyIvarList ---> m_str
// 2018-03-24 15:32:30.133 runtime[2906:197694] class_copyIvarList ---> m_bool
// 2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _h_p_str
// 2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _h_p_bool
// 2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _h_p_arr
// 2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _m_p_str
// 2018-03-24 15:32:30.134 runtime[2906:197694] class_copyIvarList ---> _m_p_bool
// 2018-03-24 15:32:30.135 runtime[2906:197694] class_copyIvarList ---> _m_p_arr
unsigned int p_outCount = 0;
objc_property_t *propertys = class_copyPropertyList([FFSpringModel class], &p_outCount);
for (int i = 0; i < p_outCount; i ++) {
NSLog(@"class_copyPropertyList ---> %s", property_getName(propertys[i]));
}
free(propertys);
// 2018-03-24 15:32:30.135 runtime[2906:197694] class_copyPropertyList ---> m_p_str
// 2018-03-24 15:32:30.135 runtime[2906:197694] class_copyPropertyList ---> m_p_bool
// 2018-03-24 15:32:30.135 runtime[2906:197694] class_copyPropertyList ---> m_p_arr
// 2018-03-24 15:32:30.136 runtime[2906:197694] class_copyPropertyList ---> h_p_str
// 2018-03-24 15:32:30.136 runtime[2906:197694] class_copyPropertyList ---> h_p_bool
// 2018-03-24 15:32:30.136 runtime[2906:197694] class_copyPropertyList ---> h_p_arr
对比运行结果,会发现,class_copyIvarList
会获取属性和变量,而class_copyPropertyList
只能获取属性,毕竟从方法名就可以推测
2.2.2 属性操作函数
-
property_getName
获取属性名
/**
* Returns the name of a property.
*
* @param property The property you want to inquire about.
*
* @return A C string containing the property's name.
*/
OBJC_EXPORT const char * _Nonnull
property_getName(objc_property_t _Nonnull property)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
property_getAttributes
获取属性特性描述字符串
/**
* Returns the attribute string of a property.
*
* @param property A property.
*
* @return A C string containing the property's attributes.
*
* @note The format of the attribute string is described in Declared Properties in Objective-C Runtime Programming Guide.
*/
OBJC_EXPORT const char * _Nullable
property_getAttributes(objc_property_t _Nonnull property)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
property_copyAttributeList
获取属性的特性列表,使用后应该free()
/**
* Returns an array of property attributes for a property.
*
* @param property The property whose attributes you want copied.
* @param outCount The number of attributes returned in the array.
*
* @return An array of property attributes; must be free'd() by the caller.
*/
OBJC_EXPORT objc_property_attribute_t * _Nullable
property_copyAttributeList(objc_property_t _Nonnull property,
unsigned int * _Nullable outCount)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
-
property_copyAttributeValue
获取属性中指定的特性
/**
* Returns the value of a property attribute given the attribute name.
*
* @param property The property whose attribute value you are interested in.
* @param attributeName C string representing the attribute name.
*
* @return The value string of the attribute \e attributeName if it exists in
* \e property, \c nil otherwise.
*/
OBJC_EXPORT char * _Nullable
property_copyAttributeValue(objc_property_t _Nonnull property,
const char * _Nonnull attributeName)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
-
class_getProperty
获取指定属性
/**
* Returns a property with a given name of a given class.
*
* @param cls The class you want to inspect.
* @param name The name of the property you want to inspect.
*
* @return A pointer of type \c objc_property_t describing the property, or
* \c NULL if the class does not declare a property with that name,
* or \c NULL if \e cls is \c Nil.
*/
OBJC_EXPORT objc_property_t _Nullable
class_getProperty(Class _Nullable cls, const char * _Nonnull name)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:
objc_property_t s_property = class_getProperty([FFSpringModel class], "m_p_str");
if (s_property) {
NSLog(@"%s", property_getName(s_property));
} else {
NSLog(@"没找到哟~");
}
// m_p_str
-
class_copyPropertyList
获取属性列表(参考 2.2.1的例子)
/**
* Describes the properties declared by a class.
*
* @param cls The class you want to inspect.
* @param outCount On return, contains the length of the returned array.
* If \e outCount is \c NULL, the length is not returned.
*
* @return An array of pointers of type \c objc_property_t describing the properties
* declared by the class. Any properties declared by superclasses are not included.
* The array contains \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free().
*
* If \e cls declares no properties, or \e cls is \c Nil, returns \c NULL and \c *outCount is \c 0.
*/
OBJC_EXPORT objc_property_t _Nonnull * _Nullable
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
class_addProperty
/**
* 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 _Nullable cls, const char * _Nonnull name,
const objc_property_attribute_t * _Nullable attributes,
unsigned int attributeCount)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
objc_property_attribute_t
/// Defines a property attribute
typedef struct {
const char * _Nonnull name; /**< The name of the attribute */
const char * _Nonnull value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
???
-
class_replaceProperty
替换类的属性
/**
* Replace a property of 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.
*/
OBJC_EXPORT void
class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,
const objc_property_attribute_t * _Nullable attributes,
unsigned int attributeCount)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
2.2.3 方法
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
-
class_addMethod
添加方法,会覆盖父类的同名方法,但是不会替换本类中的实现,如果本类中存在一个同名的方法返回NO
,如果要修改一个本类中已经存在的方法的话,需要使用method_setImplementation
imp
参数指向的函数至少要包括两个参数,self
和_cmd
/**
* 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 _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
方法实现示例:
void myFunction(id self, SEL _cmd) {
// do something
}
关于types
,是一个描述传递给方法的参数类型的字符数组,这就涉及到类型编码,见👇
eg:
void SummerRuntimeMethod(id self, SEL _cmd) {
NSLog(@"这里是 runtime 添加的方法");
}
FFSummerModel *summerInfo = [FFSummerModel new];
class_addMethod([FFSummerModel class], @selector(runtimeMethod), (IMP)SummerRuntimeMethod, "v@:");
[summerInfo performSelector:@selector(runtimeMethod)];
// 这里是 runtime 添加的方法
-
class_getInstanceMethod
获取实例方法,会搜索父类
/**
* Returns a specified instance method for a given class.
*
* @param cls The class you want to inspect.
* @param name The selector of the method you want to retrieve.
*
* @return The method that corresponds to the implementation of the selector specified by
* \e name for the class specified by \e cls, or \c NULL if the specified class or its
* superclasses do not contain an instance method with the specified selector.
*
* @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
*/
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
eg:看下面栗子
这个是获取FFSummerModel
类本身的实例方法
Method summerMethod2 = class_getInstanceMethod([FFSummerModel class], @selector(shouldGoSwimming));
if (summerMethod2) {
method_getImplementation(summerMethod2)();
} else {
NSLog(@"shouldGoSwimming 没找到");
}
// let us go swimming!
这个是获取FFSummerModel
父类的实例方法showSomething
Method summerMethod0 = class_getInstanceMethod([FFSummerModel class], @selector(showSomething));
if (summerMethod0) {
IMP summerImp0 = method_getImplementation(summerMethod0);
summerImp0();
} else {
NSLog(@"showSomething 没找到");
}
// 这是父类 FFSeasonModel 的 showSomething 方法
这里有个小细节,是如何使用IMP
来调用带有参数的方法,简单的如下的调用会在运行时crash
Method summerMethod1 = class_getInstanceMethod([FFSummerModel class], @selector(doSomething:));
if (summerMethod1) {
IMP summerImp1 = method_getImplementation(summerMethod1);
// 这里是传递参数的样式
summerImp1(summerInfo, @selector(doSomething:), @"runtime");
} else {
NSLog(@"showSomething 没找到");
}
// 成功配置后 即可 得到正确的执行结果,👇的是
// 这是父类 FFSeasonModel 的 doSomething 方法,传入的值为:runtime
在这里需要了解什么是IMP
IMP
就是implementation
的缩写,是一个指向方法实现的指针,每个方法都会对应一个IMP
指针,所以可以直接调用方法的IMP
指针,来执行方法,而调用IMP
的方式和调用普通的C
函数相同,比如这样
id returnObjc = someIMP(id self, SEL, parameters);
但前提是项目需要配置一些选项编译器才能识别,为什么呢
首先我们先查看下IMP
的定义(objc/objc.h
),会发现默认IMP
被认定为无参数无返回的函数
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
如果需要识别参数的话,需要将如下配置设置为NO
(TARGETS -> Build Settings -> Enable Strict Checking of objc_msgSend Calls
),即可得到正确的执行结果
但是,此时又会出现一个比较烦的问题,就是每个IMP
的调用都要至少传入两个参数,即someIMP(id self, SEL)
这时,可以使用另外一种解决办法,就是重新定义一个自己的FF_IMP
,和有参数的IMP
的指针类型相同,在获取到IMP
时候强转成我们自己的FF_IMP
,这样在不需要传递参数的时候使用默认的IMP
,在需要传递参数的时候使用FF_IMP
实际代码中,如果方法 没有返回值 或 有返回值但是不用接收返回值 的话,可以使用类似如下FF_IMP
或FF_VOID_IMP
都可以;但是如果需要接收返回值的话,必须使用FF_IMP
看如下栗子
typedef id (* FF_IMP) (id, SEL, ...); // 有返回值
typedef void (* FF_VOID_IMP) (id, SEL, ...); // 无返回值
Method summerMethod1 = class_getInstanceMethod([FFSummerModel class], @selector(doSomething:));
if (summerMethod1) {
FF_IMP summerImp1 = (FF_IMP)method_getImplementation(summerMethod1);
summerImp1(summerInfo, @selector(doSomething:), @"runtime");
} else {
NSLog(@"doSomething 没找到");
}
// 这是父类 FFSeasonModel 的 doSomething 方法,传入的值为:runtime
-
class_getClassMethod
获取类方法,会搜索父类
/**
* Returns a pointer to the data structure describing a given class method for a given class.
*
* @param cls A pointer to a class definition. Pass the class that contains the method you want to retrieve.
* @param name A pointer of type \c SEL. Pass the selector of the method you want to retrieve.
*
* @return A pointer to the \c Method data structure that corresponds to the implementation of the
* selector specified by aSelector for the class specified by aClass, or NULL if the specified
* class or its superclasses do not contain an instance method with the specified selector.
*
* @note Note that this function searches superclasses for implementations,
* whereas \c class_copyMethodList does not.
*/
OBJC_EXPORT Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
eg:就一个栗子啦,整体和class_getInstanceMethod
差不多,可以参考👆
Method summerClassMethod = class_getClassMethod([FFSummerModel class], @selector(seasonInfoClassMethod));
if (summerClassMethod) {
method_getImplementation(summerClassMethod)();
} else {
NSLog(@"seasonInfoClassMethod 没找到");
}
// 这是父类的类方法 seasonInfoClassMethod
-
class_copyMethodList
获取所有方法的数组,不会搜索父类的,使用后需要使用free()
返回包含所有实例方法的数组,无论是否在OC
的.h
中定义 都可以获取到,但是父类的不会包含
如果需要获取类方法,是在元类里面,需class_copMethodList(object_getClass(cls), &outCount)
/**
* 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 _Nonnull * _Nullable
class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:
unsigned int outCount = 0;
Method *methodArr = class_copyMethodList([FFSummerModel class], &outCount);
for (int i = 0; i < outCount; i ++) {
Method method = methodArr[i];
SEL sel = method_getName(method);
NSLog(@"class_copyMethodList ---> %s", sel_getName(sel));
}
free(methodArr);
// 2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> runtimeMethod
// 2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> shouldGoSwimming
// 2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> isSummer
// 2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> gogogo
// 2018-03-25 15:44:47.820 runtime[1586:81902] class_copyMethodList ---> init
-
class_replaceMethod
替换方法的实现
如果类中不存在指定名字的方法,这个函数功能相当于class_addMethod
如果类中存在指定名字的方法,这个函数功能相当于method_setImplementation
/**
* Replaces the implementation of a method for a given class.
*
* @param cls The class you want to modify.
* @param name A selector that identifies the method whose implementation you want to replace.
* @param imp The new implementation for the method identified by name for the class identified by cls.
* @param types An array of characters that describe the types of the arguments to the method.
* Since the function must take at least two arguments—self and _cmd, the second and third characters
* must be “@:” (the first character is the return type).
*
* @return The previous implementation of the method identified by \e name for the class identified by \e cls.
*
* @note This function behaves in two different ways:
* - If the method identified by \e name does not yet exist, it is added as if \c class_addMethod were called.
* The type encoding specified by \e types is used as given.
* - If the method identified by \e name does exist, its \c IMP is replaced as if \c method_setImplementation were called.
* The type encoding specified by \e types is ignored.
*/
OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:交换下面两个方法的实现
- (void)testReplaceMethod01 {
NSLog(@"testReplaceMethod01");
}
- (void)testReplaceMethod02 {
NSLog(@"testReplaceMethod02");
}
// ---
FFSummerModel *summerInfo = [FFSummerModel new];
[summerInfo performSelector:@selector(testReplaceMethod01)];
IMP imp = class_getMethodImplementation([FFSummerModel class], @selector(testReplaceMethod02));
// IMP imp = method_getImplementation(class_getInstanceMethod([FFSummerModel class], @selector(testReplaceMethod02)));
class_replaceMethod([FFSummerModel class], @selector(testReplaceMethod01), imp, "v@:");
[summerInfo performSelector:@selector(testReplaceMethod01)];
// 2018-03-25 16:01:19.444 runtime[1762:100824] testReplaceMethod01
// 2018-03-25 16:01:19.444 runtime[1762:100824] testReplaceMethod02
-
class_getMethodImplementation
该函数会在向类实例发送消息时候调用,返回指向方法的实现函数的指针,也有可能是运行时内部的函数,因为类实例无法响应方法的时候,返回的函数指针将是运行时消息转发机制的一部分
class_getMethodImplementation
比method_getImplementation
快
/**
* Returns the function pointer that would be called if a
* particular message were sent to an instance of a class.
*
* @param cls The class you want to inspect.
* @param name A selector.
*
* @return The function pointer that would be called if \c [object name] were called
* with an instance of the class, or \c NULL if \e cls is \c Nil.
*
* @note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)).
* @note The function pointer returned may be a function internal to the runtime instead of
* an actual method implementation. For example, if instances of the class do not respond to
* the selector, the function pointer returned will be part of the runtime's message forwarding machinery.
*/
OBJC_EXPORT IMP _Nullable
class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
栗子可以参考上面的
-
class_getMethodImplementation_stret
返回方法的实现,这个方法与class_getMethodImplementation
的区别是,如果类实例无法响应这个方法时候 不会返回运行时消息转发机制中的实现(待验证)
/**
* Returns the function pointer that would be called if a particular
* message were sent to an instance of a class.
*
* @param cls The class you want to inspect.
* @param name A selector.
*
* @return The function pointer that would be called if \c [object name] were called
* with an instance of the class, or \c NULL if \e cls is \c Nil.
*/
OBJC_EXPORT IMP _Nullable
class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0)
OBJC_ARM64_UNAVAILABLE;
-
class_respondsToSelector
类的实例对象是否实现了指定的方法
可以使用NSObject
的- (BOOL)respondsToSelector:(SEL)aSelector
或+ (BOOL)instancesRespondToSelector:(SEL)aSelector
替代
/**
* Returns a Boolean value that indicates whether instances of a class respond to a particular selector.
*
* @param cls The class you want to inspect.
* @param sel A selector.
*
* @return \c YES if instances of the class respond to the selector, otherwise \c NO.
*
* @note You should usually use \c NSObject's \c respondsToSelector: or \c instancesRespondToSelector:
* methods instead of this function.
*/
OBJC_EXPORT BOOL
class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:
BOOL isResponds = class_respondsToSelector([FFSummerModel class], @selector(shouldGoSwimmingg));
NSString *str = isResponds ? @"实现了" : @"没有实现";
NSLog(@"%@", str);
// 没有实现
2.2.4 协议
协议相关的操作函数
-
class_copyProtocolList
返回类实现的协议的数组,使用后应该free()
,只会返回当前类实现的协议,不会查找父类的
/**
* 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 _Nonnull * _Nullable
class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg
创建一个简单的协,并让目标vc
实现
@protocol FFAutumnProtocol <NSObject>
@optional
- (void)autumnProtocolMethod;
@end
在目标vc
中如下
unsigned int outCount2 = 0;
Protocol * __unsafe_unretained *protocol_arr = class_copyProtocolList([self class], &outCount2);
for (int i = 0; i < outCount2; i ++) {
Protocol *protocol = protocol_arr[i];
NSLog(@"class_copyProtocolList --> %s", protocol_getName(protocol));
}
free(protocol_arr);
// class_copyProtocolList --> FFAutumnProtocol
-
objc_allocateProtocol
创建新的协议
/**
* Creates a new protocol instance that cannot be used until registered with
* \c objc_registerProtocol()
*
* @param name The name of the protocol to create.
*
* @return The Protocol instance on success, \c nil if a protocol
* with the same name already exists.
* @note There is no dispose method for this.
*/
OBJC_EXPORT Protocol * _Nullable
objc_allocateProtocol(const char * _Nonnull name)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
-
objc_registerProtocol
注册动态创建的协议
/**
* Registers a newly constructed protocol with the runtime. The protocol
* will be ready for use and is immutable after this.
*
* @param proto The protocol you want to register.
*/
OBJC_EXPORT void
objc_registerProtocol(Protocol * _Nonnull proto)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
-
protocol_addProperty
为协议添加属性,只能向未注册的协议添加属性
参数proto
:协议
参数name
:属性名
参数attributes
:属性类型字符串
参数attributeCount
:属性类型数量
参数isRequiredProperty
:是否为必须属性
参数isInstanceProperty
:是否为实例属性
/**
* Adds a property to a protocol. The protocol must be under construction.
*
* @param proto The protocol to add a property to.
* @param name The name of the property.
* @param attributes An array of property attributes.
* @param attributeCount The number of attributes in \e attributes.
* @param isRequiredProperty YES if the property (accessor methods) is not optional.
* @param isInstanceProperty YES if the property (accessor methods) are instance methods.
* This is the only case allowed fo a property, as a result, setting this to NO will
* not add the property to the protocol at all.
*/
OBJC_EXPORT void
protocol_addProperty(Protocol * _Nonnull proto, const char * _Nonnull name,
const objc_property_attribute_t * _Nullable attributes,
unsigned int attributeCount,
BOOL isRequiredProperty, BOOL isInstanceProperty)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
-
protocol_addMethodDescription
为协议添加方法,只能向未注册的协议添加方法
参数types
:方法类型
/**
* Adds a method to a protocol. The protocol must be under construction.
*
* @param proto The protocol to add a method to.
* @param name The name of the method to add.
* @param types A C string that represents the method signature.
* @param isRequiredMethod YES if the method is not an optional method.
* @param isInstanceMethod YES if the method is an instance method.
*/
OBJC_EXPORT void
protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name,
const char * _Nullable types,
BOOL isRequiredMethod, BOOL isInstanceMethod)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
-
protocol_addProtocol
添加一个已经注册的协议到一个未注册的协议,只能向未注册的协议中添加
/**
* Adds an incorporated protocol to another protocol. The protocol being
* added to must still be under construction, while the additional protocol
* must be already constructed.
*
* @param proto The protocol you want to add to, it must be under construction.
* @param addition The protocol you want to incorporate into \e proto, it must be registered.
*/
OBJC_EXPORT void
protocol_addProtocol(Protocol * _Nonnull proto, Protocol * _Nonnull addition)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
-
class_addProtocol
为类添加协议
/**
* 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 _Nullable cls, Protocol * _Nonnull protocol)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
class_conformsToProtocol
判断类是否实现了指定的协议,通常是使用NSObject
的+ (BOOL)conformsToProtocol:(Protocol *)protocol;
/**
* Returns a Boolean value that indicates whether a class conforms to a given protocol.
*
* @param cls The class you want to inspect.
* @param protocol A protocol.
*
* @return YES if cls conforms to protocol, otherwise NO.
*
* @note You should usually use NSObject's conformsToProtocol: method instead of this function.
*/
OBJC_EXPORT BOOL
class_conformsToProtocol(Class _Nullable cls, Protocol * _Nullable protocol)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:用到上面的几个函数
if (!class_conformsToProtocol([self class], NSProtocolFromString(@"FFRuntimeProtocol"))) {
Protocol *runtimeProtocol = objc_allocateProtocol("FFRuntimeProtocol");
objc_property_attribute_t type = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "" };
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
protocol_addProperty(runtimeProtocol, "name", attrs, 3, true, true);
protocol_addProtocol(runtimeProtocol, NSProtocolFromString(@"NSObject"));
protocol_addMethodDescription(runtimeProtocol, @selector(FFRuntimeProtocolMethod), "v@:", false, true);
objc_registerProtocol(runtimeProtocol);
class_addProtocol([self class], runtimeProtocol);
} else {
NSLog(@"动态创建协议失败");
}
2.2.5 版本
-
class_getVersion
获取版本号
可以使用类的版本号,作为该类为其他类提供的接口的版本控制,这对于对象的序列化存储非常有用。在不同类定义版本中,重要的是要识别出实例变量布局的改变。
/**
* 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 _Nullable cls)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
eg:
int version = class_getVersion([FFSummerModel class]);
NSLog(@"%d", version);
// 0
-
class_setVersion
设置版本号
/**
* Sets the version number of a class definition.
*
* @param cls A pointer to an Class data structure.
* Pass the class definition for which you wish to set the version.
* @param version An integer. Pass the new version number of the class definition.
*
* @note You can use the version number of the class definition to provide versioning of the
* interface that your class represents to other classes. This is especially useful for object
* serialization (that is, archiving of the object in a flattened form), where it is important to
* recognize changes to the layout of the instance variables in different class-definition versions.
* @note Classes derived from the Foundation framework \c NSObject class can set the class-definition
* version number using the \c setVersion: class method, which is implemented using the \c class_setVersion function.
*/
OBJC_EXPORT void
class_setVersion(Class _Nullable cls, int version)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
eg:
class_setVersion([FFSummerModel class], 3);
NSLog(@"%d", class_getVersion([FFSummerModel class]));
// 3
3. 动态创建类和对象
runtime
的强大之处,在于可以在运行时创建类和对象
3.1 动态创建类
为创建一个新的类,首先需要调用objc_allocateClassPair
,然后为新的类添加方法class_addMethod
、实例变量class_addIvar
、属性class_addProperty
、协议class_addProtocol
等,最后需要使用objc_registerClassPair
来注册类,之后这个类就可以使用啦
-
objc_allocateClassPair
如果创建一个根类的话,父类设置为nil
,extraBytes
通常设为0
,该参数是分配给类和元类对象尾部的索引ivars
的字节数
实例变量和实例方法应该添加在类自身,类方法应该添加在类的元类上
/**
* Creates a new class and metaclass.
*
* @param superclass The class to use as the new class's superclass, or \c Nil to create a new root class.
* @param name The string to use as the new class's name. The string will be copied.
* @param extraBytes The number of bytes to allocate for indexed ivars at the end of
* the class and metaclass objects. This should usually be \c 0.
*
* @return The new class, or Nil if the class could not be created (for example, the desired name is already in use).
*
* @note You can get a pointer to the new metaclass by calling \c object_getClass(newClass).
* @note To create a new class, start by calling \c objc_allocateClassPair.
* Then set the class's attributes with functions like \c class_addMethod and \c class_addIvar.
* When you are done building the class, call \c objc_registerClassPair. The new class is now ready for use.
* @note Instance methods and instance variables should be added to the class itself.
* Class methods should be added to the metaclass.
*/
OBJC_EXPORT Class _Nullable
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name,
size_t extraBytes)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
objc_registerClassPair
注册由objc_allocateClassPair
创建的类,
/**
* Registers a class that was allocated using \c objc_allocateClassPair.
*
* @param cls The class you want to register.
*/
OBJC_EXPORT void
objc_registerClassPair(Class _Nonnull cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
objc_disposeClassPair
函数用于销毁一个类,如果程序中运行的还有这个类或者其子类的实例,则不能对该类调用该方法
/**
* Destroy a class and its associated metaclass.
*
* @param cls The class to be destroyed. It must have been allocated with
* \c objc_allocateClassPair
*
* @warning Do not call if instances of this class or a subclass exist.
*/
OBJC_EXPORT void
objc_disposeClassPair(Class _Nonnull cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
3.2 动态创建对象
-
class_createInstance
创建实例对象
创建实例时候,会在默认的内存区域分配内存。extraBytes
参数表示分配的额外字节数,这些额外的字节可以用于存储在类定义中所定义的实例变量之外的实例变量。
调用class_createInstance
与+ (instancetype)alloc
方法类似
/**
* Creates an instance of a class, allocating memory for the class in the
* default malloc memory zone.
*
* @param cls The class that you wish to allocate an instance of.
* @param extraBytes An integer indicating the number of extra bytes to allocate.
* The additional bytes can be used to store additional instance variables beyond
* those defined in the class definition.
*
* @return An instance of the class \e cls.
*/
OBJC_EXPORT id _Nullable
class_createInstance(Class _Nullable cls, size_t extraBytes)
OBJC_RETURNS_RETAINED
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
eg:
id obj = class_createInstance([FFSummerModel class], 0);
NSLog(@"%@", object_getClassName(obj));
[obj performSelector:@selector(shouldGoSwimming)];
// FFSummerModel
// let us go swimming!
-
objc_constructInstance
在指定位置创建类实例
/**
* Creates an instance of a class at the specific location provided.
*
* @param cls The class that you wish to allocate an instance of.
* @param bytes The location at which to allocate an instance of \e cls.
* Must point to at least \c class_getInstanceSize(cls) bytes of well-aligned,
* zero-filled memory.
*
* @return \e bytes on success, \c nil otherwise. (For example, \e cls or \e bytes
* might be \c nil)
*
* @see class_createInstance
*/
OBJC_EXPORT id _Nullable
objc_constructInstance(Class _Nullable cls, void * _Nullable bytes)
OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0)
OBJC_ARC_UNAVAILABLE;
-
objc_destructInstance
销毁类实例,但不会移除与实例相关的引用
/**
* Destroys an instance of a class without freeing memory and removes any
* associated references this instance might have had.
*
* @param obj The class instance to destroy.
*
* @return \e obj. Does nothing if \e obj is nil.
*
* @note CF and other clients do call this under GC.
*/
OBJC_EXPORT void * _Nullable objc_destructInstance(id _Nullable obj)
OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0)
OBJC_ARC_UNAVAILABLE;
3.3 实例操作函数
3.3.1 针对整个对象进行操作的函数
-
object_copy
返回指定对象的一份copy
,ARC
下无效
/**
* Returns a copy of a given object.
*
* @param obj An Objective-C object.
* @param size The size of the object \e obj.
*
* @return A copy of \e obj.
*/
OBJC_EXPORT id _Nullable object_copy(id _Nullable obj, size_t size)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ARC_UNAVAILABLE;
-
object_dispose
释放指定对象占用的内存,ARC
下无效
/**
* Frees the memory occupied by a given object.
*
* @param obj An Objective-C object.
*
* @return nil
*/
OBJC_EXPORT id _Nullable
object_dispose(id _Nullable obj)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ARC_UNAVAILABLE;
如何关闭ARC
(automatic reference counting
),使用MRC
(manual reference counting
)?
TARGETS
-> Build Setting
-> Objective-C Automatic Reference Counting
-> YES
eg:
类FFSeasonModel
是FFSummerModel
的父类,子类添加了一些属性和方法来扩展了父类,我们有一个父类的对象,在运行的时候希望将父类对象转换为子类对象,这种情况下是没法直接转换的,因为子类实例对象比父类实例对象更大,没有更多的空间来放置对象
此时,可以使用上面的函数来处理这种情况,参考如下栗子
FFSeasonModel *seasonInfo = [FFSeasonModel new];
id newInfo = object_copy(seasonInfo, class_getInstanceSize([FFSummerModel class]));
object_setClass(newInfo, [FFSummerModel class]);
object_dispose(seasonInfo);
[newInfo performSelector:@selector(shouldGoSwimming)];
// let us go swimming!
3.3.2 针对对象实例变量操作的函数
-
object_setInstanceVariable
修改类对象实例变量的值,ARC
下无效,可以使用object_setIvar
/**
* Changes the value of an instance variable of a class instance.
*
* @param obj A pointer to an instance of a class. Pass the object containing
* the instance variable whose value you wish to modify.
* @param name A C string. Pass the name of the instance variable whose value you wish to modify.
* @param value The new value for the instance variable.
*
* @return A pointer to the \c Ivar data structure that defines the type and
* name of the instance variable specified by \e name.
*
* @note Instance variables with known memory management (such as ARC strong and weak)
* use that memory management. Instance variables with unknown memory management
* are assigned as if they were unsafe_unretained.
*/
OBJC_EXPORT Ivar _Nullable
object_setInstanceVariable(id _Nullable obj, const char * _Nonnull name,
void * _Nullable value)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ARC_UNAVAILABLE;
-
object_getInstanceVariable
获取对象实例变量的值,ARC
下无效,可以使用object_getIvar
/**
* Obtains the value of an instance variable of a class instance.
*
* @param obj A pointer to an instance of a class. Pass the object containing
* the instance variable whose value you wish to obtain.
* @param name A C string. Pass the name of the instance variable whose value you wish to obtain.
* @param outValue On return, contains a pointer to the value of the instance variable.
*
* @return A pointer to the \c Ivar data structure that defines the type and name of
* the instance variable specified by \e name.
*/
OBJC_EXPORT Ivar _Nullable
object_getInstanceVariable(id _Nullable obj, const char * _Nonnull name,
void * _Nullable * _Nullable outValue)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ARC_UNAVAILABLE;
-
object_getIndexedIvars
返回 指向给定对象分配的任何额外字节的指针,ARC
下无效
/**
* Returns a pointer to any extra bytes allocated with an instance given object.
*
* @param obj An Objective-C object.
*
* @return A pointer to any extra bytes allocated with \e obj. If \e obj was
* not allocated with any extra bytes, then dereferencing the returned pointer is undefined.
*
* @note This function returns a pointer to any extra bytes allocated with the instance
* (as specified by \c class_createInstance with extraBytes>0). This memory follows the
* object's ordinary ivars, but may not be adjacent to the last ivar.
* @note The returned pointer is guaranteed to be pointer-size aligned, even if the area following
* the object's last ivar is less aligned than that. Alignment greater than pointer-size is never
* guaranteed, even if the area following the object's last ivar is more aligned than that.
* @note In a garbage-collected environment, the memory is scanned conservatively.
*/
OBJC_EXPORT void * _Nullable object_getIndexedIvars(id _Nullable obj)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ARC_UNAVAILABLE;
-
object_setIvar
设置对象实例变量的值
/**
* Sets the value of an instance variable in an object.
*
* @param obj The object containing the instance variable whose value you want to set.
* @param ivar The Ivar describing the instance variable whose value you want to set.
* @param value The new value for the instance variable.
*
* @note Instance variables with known memory management (such as ARC strong and weak)
* use that memory management. Instance variables with unknown memory management
* are assigned as if they were unsafe_unretained.
* @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar
* for the instance variable is already known.
*/
OBJC_EXPORT void
object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
-
object_getIvar
返回 对象中实例变量的值
/**
* Reads the value of an instance variable in an object.
*
* @param obj The object containing the instance variable whose value you want to read.
* @param ivar The Ivar describing the instance variable whose value you want to read.
*
* @return The value of the instance variable specified by \e ivar, or \c nil if \e object is \c nil.
*
* @note \c object_getIvar is faster than \c object_getInstanceVariable if the Ivar
* for the instance variable is already known.
*/
OBJC_EXPORT id _Nullable
object_getIvar(id _Nullable obj, Ivar _Nonnull ivar)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:
FFAutumnModel *autumnInfo = [FFAutumnModel new];
autumnInfo.autumnStr = @"秋天真是太好了~";
Ivar ivar = class_getInstanceVariable([autumnInfo class], "_autumnStr");
id autumn_value = object_getIvar(autumnInfo, ivar);
NSLog(@"%@", autumn_value);
// 秋天真是太好了~
object_setIvar(autumnInfo, ivar, @"秋天真是真是真是太好啦~");
NSLog(@"%@", object_getIvar(autumnInfo, ivar));
// 秋天真是真是真是太好啦~
3.3.3 针对对象所属的类的操作
-
object_getClassName
返回给定对象类的名字
/**
* Returns the class name of a given object.
*
* @param obj An Objective-C object.
*
* @return The name of the class of which \e obj is an instance.
*/
OBJC_EXPORT const char * _Nonnull object_getClassName(id _Nullable obj)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
eg:
const char * name = object_getClassName([FFAutumnModel new]);
NSLog(@"%s", name);
// FFAutumnModel
-
object_getClass
返回对象的类
/**
* Returns the class of an object.
*
* @param obj The object you want to inspect.
*
* @return The class object of which \e object is an instance,
* or \c Nil if \e object is \c nil.
*/
OBJC_EXPORT Class _Nullable
object_getClass(id _Nullable obj)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:
Class autumnClass = object_getClass([FFAutumnModel new]);
[autumnClass performSelector:@selector(shouldGoTraveling)];
// let's go traveling
-
object_setClass
设置对象的类
/**
* Sets the class of an object.
*
* @param obj The object to modify.
* @param cls A class object.
*
* @return The previous value of \e object's class, or \c Nil if \e object is \c nil.
*/
OBJC_EXPORT Class _Nullable
object_setClass(id _Nullable obj, Class _Nonnull cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
eg:见3.3.1
3.3.4 获取类定义相关的函数
OC
运行时会自动注册代码中的类,也可以在运行时创建类的定义并使用objc_addClass
添加
运行时也提供了一系列函数来获取类定义的相关信息
-
objc_getClassList
获取已注册的类的定义列表
不能假设从该函数获取的类对象是继承自NSObject
体系的,在这些类上调用方法时候应该先检测下需要调用的方法是否存在
参数buffer
:已经分配好的内存空间的数组,如果传NULL
会获得当前已经注册的所有的类
参数bufferCount
:数组中可以存放的元素的个数,返回的是注册的类的总数,当该值小于已经注册类的总数的时候,获取到的是已经注册类的集合的任意指定数量的集合
/**
* Obtains the list of registered class definitions.
*
* @param buffer An array of \c Class values. On output, each \c Class value points to
* one class definition, up to either \e bufferCount or the total number of registered classes,
* whichever is less. You can pass \c NULL to obtain the total number of registered class
* definitions without actually retrieving any class definitions.
* @param bufferCount An integer value. Pass the number of pointers for which you have allocated space
* in \e buffer. On return, this function fills in only this number of elements. If this number is less
* than the number of registered classes, this function returns an arbitrary subset of the registered classes.
*
* @return An integer value indicating the total number of registered classes.
*
* @note The Objective-C runtime library automatically registers all the classes defined in your source code.
* You can create class definitions at runtime and register them with the \c objc_addClass function.
*
* @warning You cannot assume that class objects you get from this function are classes that inherit from \c NSObject,
* so you cannot safely call any methods on such classes without detecting that the method is implemented first.
*/
OBJC_EXPORT int
objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
???
eg:
int bufferCount = 0;
Class *classes = NULL;
bufferCount = objc_getClassList(NULL, 0);
NSLog(@"objc_getClassList --- %d", bufferCount);
if (bufferCount > 0) {
classes = (Class *)malloc(sizeof(Class) * bufferCount);
bufferCount = objc_getClassList(classes, bufferCount);
NSLog(@"class count --- %d", bufferCount);
for (int i = 0; i < bufferCount; i ++) {
Class class = classes[i];
NSLog(@"class name ---> %s", class_getName(class));
if (class_getSuperclass(class) == [UITableView class]) {
NSLog(@"subclass of UITableView ---> %s", class_getName(class));
}
}
free(classes);
}
-
objc_copyClassList
创建并返回一个指向所有所有已经注册类的指针列表
/**
* Creates and returns a list of pointers to all registered class definitions.
*
* @param outCount An integer pointer used to store the number of classes returned by
* this function in the list. It can be \c nil.
*
* @return A nil terminated array of classes. It must be freed with \c free().
*
* @see objc_getClassList
*/
OBJC_EXPORT Class _Nonnull * _Nullable
objc_copyClassList(unsigned int * _Nullable outCount)
OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0, 2.0);
eg:
unsigned int outCount3 = 0;
Class *classList = objc_copyClassList(&outCount3);
NSLog(@"objc_copyClassList count --- %d", outCount3);
for (int i = 0; i < outCount3; i ++) {
Class class = classList[i];
NSLog(@"%s", class_getName(class));
}
-
objc_lookupClass
返回指定类,如果类在运行时没有定义,返回nil
/**
* Returns the class definition of a specified class.
*
* @param name The name of the class to look up.
*
* @return The Class object for the named class, or \c nil if the class
* is not registered with the Objective-C runtime.
*
* @note \c objc_getClass is different from this function in that if the class is not
* registered, \c objc_getClass calls the class handler callback and then checks a second
* time to see whether the class is registered. This function does not call the class handler callback.
*/
OBJC_EXPORT Class _Nullable
objc_lookUpClass(const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
-
objc_getClass
返回指定类,如果类在运行时未注册,会调用类处理的回调,并再次确认是否注册,如果依然没有注册,返回nil
/**
* Returns the class definition of a specified class.
*
* @param name The name of the class to look up.
*
* @return The Class object for the named class, or \c nil
* if the class is not registered with the Objective-C runtime.
*
* @note \c objc_getClass is different from \c objc_lookUpClass in that if the class
* is not registered, \c objc_getClass calls the class handler callback and then checks
* a second time to see whether the class is registered. \c objc_lookUpClass does
* not call the class handler callback.
*
* @warning Earlier implementations of this function (prior to OS X v10.0)
* terminate the program if the class does not exist.
*/
OBJC_EXPORT Class _Nullable
objc_getClass(const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
-
objc_getRequiredClass
返回指定类,如果类在运行时未注册,该函数会调用类处理的回调,并再次确认是否注册,如果依然没有注册,将杀死进程
/**
* Returns the class definition of a specified class.
*
* @param name The name of the class to look up.
*
* @return The Class object for the named class.
*
* @note This function is the same as \c objc_getClass, but kills the process if the class is not found.
* @note This function is used by ZeroLink, where failing to find a class would be a compile-time link error without ZeroLink.
*/
OBJC_EXPORT Class _Nonnull
objc_getRequiredClass(const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
-
objc_getMetaClass
返回指定类的元类,如果指定的类没有在运行时注册,该函数会调用类处理回调,并再次确认是否注册。但是每个类定义的时候必要有一个有效的元类,所以该函数总会返回一个有效的元类,无论是否有效
/**
* Returns the metaclass definition of a specified class.
*
* @param name The name of the class to look up.
*
* @return The \c Class object for the metaclass of the named class, or \c nil if the class
* is not registered with the Objective-C runtime.
*
* @note If the definition for the named class is not registered, this function calls the class handler
* callback and then checks a second time to see if the class is registered. However, every class
* definition must have a valid metaclass definition, and so the metaclass definition is always returned,
* whether it’s valid or not.
*/
OBJC_EXPORT Class _Nullable
objc_getMetaClass(const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
4. 类型编码(Type Encoding)
编译器将每个方法的返回值和参数类型编码成一个字符串,并与其方法的selector
关联在一起。可以使用@encode()
编译器指定获取值。当给定一个类型时候,@encode()
返回这个类型的字符串编码,这里的类型既可以是基本数据类型,也可以是结构体、类等类型。其实,任何可以使用sizeof()
操作的参数的类型,都可以使用@encode
。
Objective-C Runtime Programming Guide > Type Encodings
eg:
long double abc = 1000;
NSLog(@"%Lf", abc);
NSLog(@"%s", @encode(typeof (abc)));
// 1000.000000
// D
float arr[] = {1.0, 2.0, 3.0};
NSLog(@"%s", @encode(typeof(arr)));
// [3f]
NSString *string = @"string";
NSLog(@"%s", @encode(typeof(string)));
// @
typedef struct example {
int num1;
double num2;
char string;
} example;
NSLog(@"%s", @encode(typeof(example)));
// {example=idc}
NSLog(@"%s", @encode(typeof(FFSummerModel)));
// {FFSummerModel=#}
NSLog(@"int : %s", @encode(int));
// int : i
NSLog(@"float : %s", @encode(float));
// float : f
NSLog(@"float * : %s", @encode(float*));
// float * : ^f
NSLog(@"char : %s", @encode(char));
// char : c
NSLog(@"char * : %s", @encode(char *));
// char * : *
NSLog(@"BOOL : %s", @encode(BOOL));
// BOOL : B
NSLog(@"void : %s", @encode(void));
// void : v
NSLog(@"void * : %s", @encode(void *));
// void * : ^v
NSLog(@"NSObject * : %s", @encode(NSObject *));
// NSObject * : @
NSLog(@"NSObject : %s", @encode(NSObject));
// NSObject : {NSObject=#}
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
// [NSObject] : #
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));
// NSError ** : ^@
int intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[] : %s", @encode(typeof(intArray)));
// int[] : [5i]
float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[] : %s", @encode(typeof(floatArray)));
// float[] : [3f]
typedef struct _struct {
short a;
long long b;
unsigned long long c;
} Struct;
NSLog(@"struct : %s", @encode(typeof(Struct)));
// struct : {_struct=sqQ}
- 指针的标准编码是加一个前置的
^
,而char *
拥有自己的编码,因为C
的字符串被认为是一个实体,而不是指针
5. 关联对象
通常会把成员变量Ivar
放在类声明的头文件中,或者放在类实现的@implementation
后面。并且不能在分类中添加成员变量,编译器会报错。
可以使用 关联对象(associated object
)
主要有下面几个函数
objc_setAssociatedObject
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
objc_getAssociatedObject
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
objc_removeAssociatedObjects
/**
* Removes all associations for a given object.
*
* @param object An object that maintains associated objects.
*
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use \c objc_setAssociatedObject
* with a nil value to clear an association.
*
* @see objc_setAssociatedObject
* @see objc_getAssociatedObject
*/
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
- 内存管理策略
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
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. */
};
我们可以把关联对象的想象成字典存取的过程,字典类似于目标对象,我们通过指定的key
使用objc_setAssociatedObject
,将需要添加的成员变量关联到对象中;如果需要取出添加的成员变量,需要用存的时候指定的key
通过objc_getAssociatedObject
来取出
可以参考👇的栗子
eg:
在vc
中有一个view
,想要动态地添加手势到这个view
,并且指定点击后的操作 ~
可以动态的创建一个tap
关联到view
上,动态地创建一个block
关联到view
上 ~
// 传入指定的view,和后续需要执行的操作
- (void)setTapActionFFView:(FFView *)view WithBlock:(void(^)(void))block {
// 根据指定key,获取手势,如果没有的话,就创建手势,并关联到view
UITapGestureRecognizer *tapGR = objc_getAssociatedObject(view, "FFUITapGestureRecognizerKey");
if (!tapGR) {
// 这里的 target 需要传 self,因为触发动作后 是当前的 vc 来响应
tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionOfTapGestureRecognizer:)];
[view addGestureRecognizer:tapGR];
objc_setAssociatedObject(view, "FFUITapGestureRecognizerKey", tapGR, OBJC_ASSOCIATION_RETAIN);
}
// 关联 block
objc_setAssociatedObject(view, "FFActionBlockKey", block, OBJC_ASSOCIATION_COPY);
}
- (void)actionOfTapGestureRecognizer:(UITapGestureRecognizer *)tapGR {
if (tapGR.state == UIGestureRecognizerStateRecognized) {
// 从 view 中取出关联的 block,如果存在的话就执行
void (^action)(void) = objc_getAssociatedObject(tapGR.view, "FFActionBlockKey");
if (action) {
action();
}
}
}
// -----
FFView *ff_view = [[FFView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
ff_view.backgroundColor = [UIColor cyanColor];
[self.view addSubview:ff_view];
[self setTapActionFFView:ff_view WithBlock:^{
// do something ~
}];
不定期更新 不合适的地方 还请指点~ 感激不尽
愿祖国繁荣昌盛~
o(* ̄3 ̄)o