// An opaque type that represents an Objective-C class.
// Objective-C 类是由 Class 类型来表示的,它实际上是一个指向 objc_class 结构体的指针
typedef struct objc_class *Class;
// Represents an instance of a class.
//objc_object 是表示一个类的实例的结构体
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
// A pointer to an instance of a class.
typedef struct objc_object *id;
//部分代码
struct objc_class : objc_object {
// Class ISA;
Class superclass; // 当前类的父类
cache_t cache; // 方法缓存
// 用于获取具体类的信息(属性、方法、协议等)
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
};
// 部分代码
struct objc_object {
private:
isa_t isa; // 可以看到 isa 指针实际上是一个 isa_t 的共用体
};
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
//定义一个结构体,结构体内定义的变量后面的值代表该变量占用多少二进制位(位域技术)
//在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体
struct {
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8;
};
#endif
};
共用体概念
[外链图片转存失败(img-RxyT7TE8-1568884592268)(media/15398503045242/15476213762813.jpg)]
[外链图片转存失败(img-PATV9JAG-1568884592271)(media/15398503045242/15597226584149.jpg)]
class_rw_t
的封装读写
信息、对 class_ro_t 的封装、方法、属性、协议列表等只读
信息//部分源码
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits; // 仅有一个属性 通过 FAST_xxx 获取对应的信息
.......
public:
//用于返回 class_rw_t
//#define FAST_DATA_MASK 0x00007ffffffffff8UL
class_rw_t* data() { // FAST_DATA_MASK & bits 获取 class_rw_t
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
......
}
//部分源码
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; // 方法列表
property_array_t properties; // 属性列表
protocol_array_t protocols; // 协议列表
Class firstSubclass; // 第一个子类
Class nextSiblingClass; // 下一个分类
char *demangledName;
......
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //实例对象大小
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; //成员变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
struct method_t {
SEL name; //方法名 (选择器)
const char *types; //编码 (返回值类型、参数类型)
IMP imp; //指向函数的指针 (函数地址,方法的实现)
};
IMP 代表函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull,SEL _Nonnull,...)
SEL 代表方法\函数名,一般叫做选择器,底层结构给 char *
类似
typedef struct objc_selector *SEL;
types 包含了函数返回值、参数编码的字符串
[外链图片转存失败(img-FYLneNMF-1568884592280)(media/15398503045242/15573051892976.jpg)]
官方文档
[外链图片转存失败(img-XRs0kUdm-1568884592283)(media/15398503045242/15686022301569.jpg)]
[外链图片转存失败(img-SwP815T4-1568884592286)(media/15398503045242/15476219455441.jpg)]
// mask一开始给一个初始值,如果长度不够用就再次申请,然后清空之前的缓存,缓存最新的(因为mask发生了变化)
struct cache_t {
struct bucket_t *_buckets; //散列表 (是一个数组 内部有很多的 bucket_t )
mask_t _mask; //散列表的长度-1 (散列表的长度减去1 & key = 索引)
mask_t _occupied; //已经缓存的方法数量
......
}
struct bucket_t {
private:
#if __arm64__
MethodCacheIMP _imp; // 函数的内存地址 @selector
cache_key_t _key; // SEL 作为 key 函数地址
#else
cache_key_t _key;
MethodCacheIMP _imp;
#endif
public:
inline cache_key_t key() const { return _key; }
inline IMP imp() const { return (IMP)_imp; }
inline void setKey(cache_key_t newKey) { _key = newKey; }
inline void setImp(IMP newImp) { _imp = newImp; }
void set(cache_key_t newKey, IMP newImp);
};
#####cache_t的存入
将_key与_mask相与,因为_mask是数组大小-1,所以得到的结果刚好小于数组的大小。
如果得到的位置已经被占用,则往后寻找,直到找到空的位置,把缓存设置到这个位置
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
// 因为cache_t内部用来储存的结构其实就是个数组
// 所以操作的时候需要先加个锁,保证线程安全。
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
}
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
// 如果缓存中已经缓存过了,不用再缓存,直接return
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel);
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
// Cache is too full. Expand it.
cache->expand();
}
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity * 2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
reallocate(oldCapacity, newCapacity);
}
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
bool freeOld = canBeFreed();
bucket_t *oldBuckets = buckets();
bucket_t *newBuckets = allocateBuckets(newCapacity);
// Cache's old contents are not propagated.
// This is thought to save cache memory at the cost of extra cache fills.
// fixme re-measure this
assert(newCapacity > 0);
assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false);
}
}
//缓存中查找方法的流程 源码在 objc-cache.mm 中
// k : SEL
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m); // k : SEL m:mask
mask_t i = begin;
do { // 如果找到 就直接返回
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
// 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(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
对于 hash 表的碰撞问题(@selector(test) & _mask 和 @selector(test1) & _mask 值相同),苹果的解决方案是发生碰撞的时候,就索引减1 ,查找下一个,直到减到 0 就返回mask 。
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask; // 如果减到 0 就直接返回 mask
}
关于hash 表的原理就是 : f(key) = index ,结构图如下:
[外链图片转存失败(img-xaNbY3yZ-1568884592287)(media/15398503045242/15686304099605.jpg)]
//优化后的 isa
union isa_t {
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
};
};
- nonpointer
- 0, 代表普通的指针,存储着 Class 和 Meta—Class 对象的内存地址
- 1, 代表优化过(不是普通指针),使用位域存储更多的信息
- has_assoc
- 是否有设置关联对象,如果有,释放时会更快
- has_cxx_dtor
- 是否有C++的析构函数(.cxx_destruct),如果有,释放时会更快,类似delloc
- shiftcls
- 存储着Class、Meta-Class对象的内存地址信息
- magic
- 用于在调试时分辨对象是否未完成初始化
- weakly_referenced
- 是否有被弱引用指向过,如果没有,释放时会更快
- deallocating
- 对象是否正在释放
- has_sidetable_rc
- 里面存储的值是引用计数减 1
- extra_rc (retain count 引用计数)
- 引用计数器是否过大无法存储在isa中
- 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
Class、Mate-Class 对象的地址的后三位始终是0
[外链图片转存失败(img-h61jFe6K-1568884592291)(media/15398503045242/15476214977618.jpg)]
isa 指针 有两种 一种是 指针型的isa 另一种是 非指针型的isa。
关于对象,其指向类对象。
[外链图片转存失败(img-jqXld4K4-1568884592295)(media/15398503045242/15476216272498.jpg)]
关于类对象,其指向元类对象。
[外链图片转存失败(img-oEhK39wl-1568884592295)(media/15398503045242/15476216450733.jpg)]
[外链图片转存失败(img-XK7SgxGs-1568884592297)(media/15398503045242/15476235317120.jpg)]
类对象存储实例方法列表等信息
元类对象存储类方法列表等信息
[外链图片转存失败(img-LgwAuWXM-1568884592299)(media/15398503045242/15476239352566.png)]
实例的isa 指向类对象,类对象的isa 指向元类对象。
Objective-C 类是由Class
类型来表示的,它实际上是一个指向objc_class
结构体的指针;
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
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;
需要注意的是在Objective-C 中,所有的类自身也是一个对象,这个对象的Class
里面也有一个isa
指针,它指向metaClass
(元类)。
指向该类的父类,如果父类已经是最顶层的根类(NSObject
或者NSProxy
),则super_class
为NULL
。
可以使用这个字段提供类的版本信息。对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。
用于缓存最近使用的方法。一个接收者接收到一个消息的时候,会根据isa
指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,都是methodLists
中遍历一遍,性能会很差。使用cache在每次调用一个方法后,这个方法会被缓存到cache
列表中,下次调用的时候runtime
就会优先去cache
中找,如果cache
没有,才去methodLists
中查找方法,这样对于经常用到的方法的调用,提高了调用的效率。
是表示一个类的实例的结构体,定义如下:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
isa
指针,即指向其类的isa
指针。当我们向一个Objective-C
对象发送消息时,运行时库会根据实例对象的isa
指针找到这个实例对象所属的类。Runtime
库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector
指向的方法。找到后运行这个方法。objc_object
数据结构,然后是类的实例变量的数据。NSObject类的alloc
和allocWithZone:
方法使用函数class_createInstance
来创建objc_object
数据结构。它是一个objc_object
结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。
NSArray *array = [NSArray array];
+array
消息发送给了NSArray
类,这个NSArray
也是一个对象。既然是对象,那么它也是一个objc_object
指针,它包含一个指向其类的isa
指针。那么这个isa
指针指向什么呢?为了调用 +array
方法,这个类的isa
指针必须指向一个包含这些类方法的一个objc_class
结构体。这就引出了meta-class(类对象的类)
的概念。对象发送消息的时候
,Runtime
会在这个对象所属的这个类的方法列表中
查找方法;而向一个类发送消息的时候
,会在这个类的meta-class的方法列表
中查找。meta-class
之所以重要,因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class
,因为每个类的类方法基本不可能完全相同。meta-class
也是一个类,也可以向它发送一个消息,那么它的isa
又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C
设计者让所有的meta-class
的isa
指向基类的meta-class
,以此作为他们的所属类,即任何NSObject
继承体系下的meta-class
都使用NSObject
的meta-class
作为自己的所属类,而基类的meta-class
的isa
指针是指向它自己
,这样就形成了一个完美的闭环。Runtime 提供了大量的函数来操作类与对象。
class
为前缀objc
或 object_
为前缀。动态创建一个类(参数:父类,类名,额外的内存空间)
objc_allocateClassPair
函数: 如果想要创建一个根类,则superclass
指定为Nil。extraBytes
通常指定为0,该参数是分配给类和元类对象尾部的索引ivars
的字节数。
objc_allocateClassPair
。然后使用诸如class_addMethod
,class_addIvar
等函数来为新创建的类添加方法、实例变量和属性等。完成这些后,我们需要调用objc_registerClassPair
函数来注册类,之后这个新类就可以在程序中使用了。Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
//在运行时动态生成一个类
Class newClass = objc_allocateClassPair([NSObject class], "RuntimeClass", 0);
// 为类添加成员变量
// 给指定的类添加成员变量 ,只能在objc_allocateClassPair() 和 objc_registerClassPair() 之间调用,并且不能为一个已经存在的类添加成员变量
class_addIvar(newClass, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
class_addIvar(newClass, "_age", sizeof(int), sizeof(int), @encode(int));
// 为类添加方法
SEL sel = sel_registerName("RuntimeTestMethod");
class_addMethod(newClass, sel, (IMP)RuntimeTestMethodIMP, "i@:@");
// 注册类到Runtime (必须要注册)
objc_registerClassPair(newClass);
// 使用已经创建的类
id person = [[newClass alloc] init];
// 可以这样设置值
// [person setValue:@"小花" forKey:@"_name"];
// NSLog(@" name :%@",[person valueForKey:@"_name"]);
// 设置成员变量的值
Ivar nameIvar = class_getInstanceVariable(newClass, "_name");
object_setIvar(person, nameIvar, @"xiaohua");
Ivar ageIvar = class_getInstanceVariable(newClass, "_age");
object_setIvar(person, ageIvar, @18);
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"xiaoli",@"name",@"25",@"age", nil];
[person performSelector:@selector(RuntimeTestMethod) withObject:dic];
// 获取成员变量的值
NSLog(@"实例变量Person的值%@ %@",object_getIvar(person, nameIvar),object_getIvar(person, ageIvar));
// 实例变量Person的值xiaohua 18
NSLog(@"实例所属的类: %@ ,实例所属的父类: %@",object_getClass(person),class_getSuperclass(object_getClass(person)));
//实例所属的类: RuntimeClass ,实例所属的父类: NSObject
// 在不需要这个类的时候释放
//objc_disposeClassPair(newClass);
//动态添加的方法必须是已经实现的
void RuntimeTestMethodIMP(id self,SEL _cmd,NSDictionary *dict) {
NSLog(@"传递进来的 dict%@",dict);
NSLog(@"打印成员变量name: %@",object_getIvar(self, class_getInstanceVariable([self class], "_name")));
}
//传递进来的 dict{ age = 25; name = xiaoli; }
注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)
销毁一个类
void objc_disposeClassPair(Class cls)
objc_disposeClassPair
函数用于销毁一个类,不过需要注意的是,如果程序运行中还存在类或子类的实例,则不能针对类调用该方法。
获取isa指向的Class
Class object_getClass(id obj)
设置isa指向的Class
Class object_setClass(id obj, Class cls)
Person *person = [[Person alloc] init];
[person run]; // [Person run]
//object_setClass 设置isa指向的Class
object_setClass(person, [Son class]); // 可以动态修改 isa 指向
[person run]; // [Son run]
判断一个OC对象是否为Class
BOOL object_isClass(id obj)
判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
获取类的父类
Class class_getSuperclass(Class cls)
当 cls 为 Nil 或者 cls 为根类时,返回 Nil。
获取一个实例成员变量的信息
Ivar class_getInstanceVariable(Class cls, const char *name)
Ivar ivar = class_getInstanceVariable([Person class],"_height");
NSLog(@"%s == %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar)); // _height == i
获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
目前没有找到关于Objective-C中类变量的信息,**一般认为Objective-C不支持类变量。**注意,返回的列表不包含父类的成员变量和属性。
拷贝实例变量列表(最后需要调用free释放) 常用
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
unsigned int count = 0;
//获取类中的所有成员变量
Ivar *ivarList = class_copyIvarList([Person class], &count);
//遍历所有成员变量
for (int i = 0; i < count; i ++) {
//根据角标,从数组中取出对应的成员变量
Ivar ivar = ivarList[i];
//获取成员变量的名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
NSLog(@"%@",ivarName);
}
free(ivarList);
设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
// 设置成员变量的值
Ivar nameIvar = class_getInstanceVariable(newClass, "_name");
object_setIvar(person, nameIvar, @"xiaohua");
Ivar ageIvar = class_getInstanceVariable(newClass, "_age");
object_setIvar(person, ageIvar, @18);
// 获取成员变量的值
object_getIvar(person, nameIvar)
object_getIvar(person, ageIvar)
动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
动态为类添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
Class cls = objc_allocateClassPair(MyClass.class, "MySubClass", 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "v@:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");
objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_ivar1"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);
id instance = [[cls alloc] init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];
输出结果:
run sub method 1
run sub method 1
动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
获取 一个实例方法 、类方法,class_getInstanceMethod
、class_getClassMethod
函数,与 class_copyMethodList
不同的是,这两个函数都会去搜索父类的实现
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
class_getMethodImplementation
函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))
更快。返回的函数指针可能是一个指向runtime
内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector
,则返回的函数指针将是运行时消息转发机制的一部分.
拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
class_copyMethodList
函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)
(一个类的实例方法是定义在元类里面)。该列表不包含父类实现的方法。outCount
参数返回方法的个数。在获取到列表后,需要使用free()
方法来释放。
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
class_addMethod
的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation
。一个OC方法是一个简单的C函数,至少包含两个参数self
和_cmd
。所以,实现函数(IMP参数指向的函数)至少需要两个参数void myMethodIMP(id self, SEL _cmd){ // implementation ....}
。与成员变量不同的是,可以为类动态添加方法,不管这个类是否已存在;另外,参数types
是一个描述传递给方法的参数类型的字符数组。
动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
class_replaceMethod
函数,该函数的行为可以分为两种:如果类中不存在name 指定的方法,则类似于class_addMethod
函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation
一样替代原来方法的实现。
用block作为方法实现
// 创建一个指针函数的指针,该函数调用时会调用特定的block
IMP imp_implementationWithBlock(id block)
// 返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id imp_getBlock(IMP anImp)
// 解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL imp_removeBlock(IMP anImp)
@interface MyRuntimeBlock : NSObject
@end
@implementation MyRuntimeBlock
@end
// 测试代码
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
NSLog(@"%@", str);
});
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "v@:@");
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];
// 输出结果 : hello world!
(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );
返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
#import
@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *address;
@property (nonatomic,strong) NSArray *array;
@property (nonatomic,copy) NSString *string;
- (void)method1;
- (void)method2;
+ (void)classMethod;
@end
#import "Person.h"
@interface Person (){
NSInteger _instance1;
NSString *_instance2;
}
@property (nonatomic,assign) NSUInteger age;
@end
@implementation Person
- (void) method1 {
NSLog(@" == method1 == ");
}
- (void)method2 {
NSLog(@" == method2 == ");
}
+ (void)classMethod {
NSLog(@" == classMethod == ");
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
unsigned int count = 0;
Class cls = Person.class;
//类名
NSLog(@"class name : %s",class_getName(cls));
//父类
NSLog(@"super class name : %s",class_getName(class_getSuperclass(cls)));
//是否是元类
NSLog(@"Person is %@ a meta-class",class_isMetaClass(cls) ? @"" : @"not");
//获取元类
Class meta_class = objc_getMetaClass(class_getName(cls));
NSLog(@"%s meta-class is %s",class_getName(cls),class_getName(meta_class));
//实例变量大小
NSLog(@"instance size: %zu",class_getInstanceSize(cls));
//成员变量
Ivar *ivars = class_copyIvarList(cls, &count);
for (int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
NSLog(@"instance variable's name:%s at index %d",ivar_getName(ivar),i);
}
free(ivars);
Ivar string = class_getInstanceVariable(cls, "_string");
if (string != NULL) {
NSLog(@"instance variable %s",ivar_getName(string));
}
//属性操作
objc_property_t *properties = class_copyPropertyList(cls, &count);
for (int i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSLog(@"propertie's name : %s",property_getName(property));
}
free(properties);
objc_property_t array = class_getProperty(cls, "array");
if (array != NULL) {
NSLog(@"property %s",property_getName(array));
}
//方法操作
Method *methods = class_copyMethodList(cls, &count);
for (int i = 0; i < count; i ++) {
Method method = methods[i];
NSLog(@"method's signature: %s",method_getName(method));
}
Method method1 = class_getInstanceMethod(cls, @selector(method1));
if (method1 != NULL) {
NSLog(@"method %s",method_getName(method1));
}
Method classMethod = class_getClassMethod(cls, @selector(classMethod));
if (classMethod != NULL) {
NSLog(@"class method : %s",method_getName(classMethod));
}
IMP imp = class_getMethodImplementation(cls, @selector(method1));
imp();
//协议
Protocol *__unsafe_unretained *protocals = class_copyProtocolList(cls, &count);
Protocol *protocol;
for (int i = 0; i < count; i ++) {
protocol = protocals[i];
NSLog(@"protocol name : %s",protocol_getName(protocol));
}
NSLog(@"Person is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"" : @" not", protocol_getName(protocol));
}
[外链图片转存失败(img-ftKP9LWY-1568884592303)(media/15398503045242/runtime_shili.png)]
实例操作函数主要针对我们创建的实例对象的一系列操作函数,可以使用这组函数从实例对象中获取到想要的信息,比如实例对象中变量的值。
// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );
// 释放指定对象占用的内存
id object_dispose ( id obj );
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);
2、针对对象实例变量进行操作的函数,这类函数包含:
// 修改类实例的实例变量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 获取对象实例变量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 返回指向给定对象分配的任何额外字节的指针
void * object_getIndexedIvars ( id obj );
// 返回对象中实例变量的值
id object_getIvar ( id obj, Ivar ivar );
// 设置对象中实例变量的值
void object_setIvar ( id obj, Ivar ivar, id value );
```
实例变量的Ivar已知的情况下,调用`object_getIvar`会比`object_getInstanceVariable`函数快,相同情况下,`object_setIvar`也比`object_setInstanceVariable`快。
3、针对对象的类进行操作的函数,这类函数包含:
// 返回给定对象的类名
const char * object_getClassName ( id obj );
// 返回对象的类
Class object_getClass ( id obj );
// 设置对象的类
Class object_setClass ( id obj, Class cls );
objc_getClassList函数: 获取已注册的类定义的列表。不能假设从该函数中获取的类对象是继承自NSObject 体系的,所以在这些类上调用方法时,都应该检测一下这个方法是否在这个类中实现。
int numClasses;
Class * classes = NULL;
numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
classes = malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
NSLog(@"number of classes: %d", numClasses);
for (int i = 0; i < numClasses; i++) {
Class cls = classes[i];
NSLog(@"class name: %s", class_getName(cls));
}
free(classes);
}
```
获取类定义的方法由三个:objc_lookUpClass
objc_getClass
和objc_getRequiredClass
: 如果类在运行时未注册,则objc_lookUpClass
会返回nil,而objc_getClass
会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。而objc_getRequiredClass
函数的操作与 objc_getClass
相同,只不过如果没有找到类,则会杀死进程。
objc_getMetaClass函数: 如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类定义都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。
SEL: 又叫选择器,是表示一个方法的selector的指针。
typedef struct objc_selector *SEL;
方法的selector
用于表示运行时方法的名字
。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL
。
SEL sel1 = @selector(method1);
NSLog(@"sel : %p", sel1);
输出:sel : 0x100002d72
两个类之间,不管它们是不是父类与子类的关系,只要方法名相同,那么方法的SEL
就是一样的。每个方法都对应一个SEL
。在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。相同的方法只能对应一个SEL。这也就导致Objective-C在处理相同方法名且参数个数相同但类型不同的方法方面的能力很差。如在某个类中定义以下两个方法:
- (void)setWidth:(int)width;
- (void)setWidth:(double)width;
当然,不同的类可以拥有相同的selector
,这个没有问题。不同类的实例对象执行相同的selector
时,会在各自的方法列表中去根据selector
去寻找自己对应的IMP
。
所有的SEL
组成一个Set
集合,Set的特点就是唯一,因此SEL是唯一的。因此,如果到这个方法集合中查找某个方法时,只需要去找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串
,而对于字符串的比较仅仅需要比较他们的地址就可以了,可以说速度上无语伦比!!但是,有一个问题,就是数量增多会增大hash冲突而导致的性能下降(或是没有冲突,因为也可能用的是perfect hash)。但是不管使用什么样的方法加速,如果能够将总量减少(多个方法可能对应同一个SEL),那将是最犀利的方法。那么,我们就不难理解,为什么 SEL仅仅是函数名了。
本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。
可以在运行时添加新的selector,也可以在运行时获取已存在的selector,我们可以通过下面三种方法来获取SEL:
sel_registerName函数
Objective-C编译器提供的@selector()
NSSelectorFromString()方法
IMP: 实际上是一个函数指针,指向方法实现的首地址:
id (*IMP)(id, SEL, ...)
Method: 用于表示类定义中的方法:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
OC 中的方法调用,其实就是转换为objc_msgSend
函数的调用。
objc_msgSend
的执行流程可以分为3个阶段:
[外链图片转存失败(img-ZCO8qYmc-1568884592306)(media/15398503045242/15663558819004.jpg)]
面试题:runtime 如何通过Selector 找到对应的IMP 地址的?(其实就是上面的寻找过程)
[外链图片转存失败(img-7YqvGb6Q-1568884592307)(media/15398503045242/15476252402496.jpg)]
[外链图片转存失败(img-vxN8t7e6-1568884592310)(media/15398503045242/15476255489105.jpg)]
[外链图片转存失败(img-eJifQAVr-1568884592311)(media/15398503045242/15663740925549.jpg)]
@implementation Person
- (void)otherTest {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
//获取需要动态添加的方法
Method method = class_getInstanceMethod(self, @selector(otherTest));
//动态添加方法实现
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
// 返回 YES 代表有动态添加的方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
@implementation PersonC
// 任何方法都默认的两个参数 self , _cmd (当前方法的方法编号)
void otherRun(id self,SEL _cmd,NSNumber *meter){
NSLog(@"跑了%@米",meter);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == NSSelectorFromString(@"run:")) {
// class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
// cls : 给哪个类动态添加方法
// name : 添加方法的方法名
// imp : 方法实现
// types : 方法类型 (返回值 + 参数类型)
// v: void
// @ : 对象 -> self
// : 表示SEL->_cmd
class_addMethod(self, sel, (IMP)otherRun, "v@:@");
return YES;
}
// 返回父类的默认调用
return [super resolveInstanceMethod:sel];
}
//+ (BOOL)resolveClassMethod:(SEL)sel {
// if (sel == NSSelectorFromString(@"test1")) {
//
// class_addMethod(object_getClass(self), sel, (IMP)otherRun, "v@:@");
//
// return YES;
// }
// return [super resolveClassMethod:sel];
//}
@end
[外链图片转存失败(img-1Vyto9L8-1568884592312)(media/15398503045242/15663951198817.jpg)]
[外链图片转存失败(img-pvB51Z0j-1568884592315)(media/15398503045242/15476259960974.jpg)]
OC 中方法调用的本质就是消息传递,消息有名称(name)或选择子(selector),可以接受参数,而且可能还有返回值。
消息机制的原理:对象根据方法编号SEL去映射表中查找对应的方法的实现。
通常会使用 clang -rewrite-objc
来窥探OC的底层实现:
例如 main.m 文件代码如下:
```
#import
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
```
打开终端,到main.m目录下,执行如下指令命令,最终会生成main.cpp文件:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
相关的代码转化为如下:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
xcrun -sdk iphonesimulator11.4 clang -rewrite-objc -F /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator11.4.sdk/System/Library/Frameworks
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );
// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
// 销毁类实例
void * objc_destructInstance ( id obj );
class_createInstance
函数:创建实例时,会在默认的内存区域为类分配内存。extraBytes
参数表示分配的额外字节数。这些额外的字节可用于存储在类定义中所定义的实例变量之外的实例变量。该函数在ARC 环境下无法使用。class_createInstance
的效果与+alloc
方法类似。不过在使用class_createInstance
时,需要确切的知道用它来做什么。id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
NSLog(@"%@", [str1 class]);
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);
打印结果:
NSString
__NSCFConstantString
class_createInstance
函数获取的是NSString
实例,而不是类簇中的默认占位符类__NSCFConstantString
。objc_constructInstance
函数:在指定的位置(bytes)创建类实例。objc_destructInstance
函数:销毁一个类的实例,但不会释放并移除任何与其相关的引用。字典转模型的方式:
一个一个的给模型属性赋值
字典转模型KVC实现
key
一一对应。[ setValue:forUndefinedKey:]
报找不到key的错。setValue:forUndefinedKey:
报错。setValue:forUndefinedKey:
把系统的方法覆盖掉,就能继续使用KVC字典转模型了。字典转模型Runtime 实现
MJExtension 字典转模型实现 (写一个 NSObject 的分类,字典转模型用分类来处理)
//Runtime 根据模型中属性,去字典中取出对应的value给模型属性赋值
//思路: 遍历模型中的所有属性(使用运行时)
+ (instancetype)modelWithDict:(NSDictionary *)dict {
//1、创建对应的对象
id objc = [[self alloc] init];
//2、利用Runtime 给对象中的属性赋值
//class_copyIvarList : 获取类中的所有成员变量
//Ivar : 成员变量
//第一个参数 : 表示获取哪个类中的成员变量
//第二个参数 : 表示这个类有多少成员变量,传入一个Int 变量地址,会自动给这个变量赋值
//返回 Ivar * : 指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到
//count : 成员变量个数
unsigned int count = 0;
//获取类中的所有成员变量
Ivar *ivarList = class_copyIvarList(self, &count);
//遍历所有成员变量
for (int i = 0; i < count; i ++) {
//根据角标,从数组中取出对应的成员变量
Ivar ivar = ivarList[i];
//获取成员变量的名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//处理成员变量名 - >字典中的key (去掉 _ ,从第一个角标开始截取)
NSString *key = [ivarName substringFromIndex:1];
//根据成员属性名去字典中查找对应的value
id value = dict[key];
//如果模型属性数量大于字典键值对数,模型属性会被赋值nil,报错 could not set nil as the value for the key age
if (value) {
[objc setValue:value forKey:key];
}
}
free(ivarList); //释放
return objc;
}
注:获取模型中的类的所有属性名,是采取
class_copyIvarList
先获取成员变量(以下划线开头) ,然后再处理成员变量名->字典中的key(去掉 _ ,从第一个角标开始截取) 得到属性名。
Ivar : 成员变量,以下划线开头。
Property 属性 获取类里面的属性class_copyPropertyList
,获取类中的所有成员变量class_copyIvarList
{
int _a; //成员变量
}
@property (nonatomic,assign) NSInteger attitudes_count; //属性
这里有成员变量,就不会漏掉属性;如果有属性,可能会漏掉成员变量。
>使用Runtime 字典转模型获取模型属性名的时候,最好获取成员属性名,`Ivar`因为可能会有个属性是没有setter 和 getter 方法的。
2、Runtime 字典转模型 -> 模型中嵌套模型 (模型属性是另外一个模型对象),可以做如下处理:
// 字典转模型-->模型中嵌套模型「模型属性是另外一个模型对象」
+ (instancetype)modelWithDict2:(NSDictionary *)dict {
//1、创建对应的对象
id objc = [[self alloc] init];
//2、利用runtime给对象中的属性赋值
unsigned int count = 0;
//获取类中的所有成员变量
Ivar *ivarList = class_copyIvarList(self, &count);
//遍历所有成员变量
for (int i = 0; i < count; i ++) {
//根据角标,从数组中取出对应的成员变量
Ivar ivar = ivarList[i];
//获取成员变量名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//获取成员变量类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
//替换 @\“User\” - > User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
//处理成员属性名 - > 字典中的key (去掉 _,从第一个角标开始截取)
NSString *key = [ivarName substringFromIndex:1];
//根据成员变量属性名去字典中查找对应的value
id value = dict[key];
// ====== 二级转换 ===== //
//二级转换: 如果字典中还有字典,也需要把对应的字典转换模型
//判断下value 是否是字典,并且是自定义对象才需要转换
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
//字典转化成模型 userDict => User 模型,转换成哪个模型
//根据字符串类名生成类对象
Class modelClass = NSClassFromString(ivarType);
if (modelClass) { //有对应的模型才需要转
//把字典转模型
value = [modelClass modelWithDict2:value];
}
}
//给模型中属性赋值
if (value) {
[objc setValue:value forKey:key];
}
}
free(ivarList); //释放
return objc;
}
3 **Runtime 字典转模型 --> 数组中装着模型 **(模型的属性是一个数组,数组中是字典模型对象),这种情况做如下处理:
//Runtime : 根据模型中的属性,去字典中取出对应的value给模型属性赋值
//思路: 遍历模型中所有属性 -> 使用运行时
+ (instancetype)modelWithDict3:(NSDictionary *)dict {
// 1、创建对应的对象
id objc = [[self alloc] init];
//2、利用Runtime 给对象中的属性赋值
unsigned int count = 0 ;
//获取类中的所有成员变量
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍历所有成员变量
for (int i = 0; i < count; i ++) {
// 根据角标,从数组取出对应的成员变量
Ivar ivar = ivarList[i];
//获取成员变量名
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//处理成员属性名 -> 字典中的key (去掉 _,从第一个角标开始截取)
NSString *key = [ivarName substringFromIndex:1];
// 根据成员属性名去字典中查找对应的value
id value = dict[key];
// ====== 三级转换 ======= //
//三级转换:NSArray 中也是字典,把数组中的字典转换成模型
//判断值是否是数组
if ([value isKindOfClass:[NSArray class]]) {
//判断对应类有没有实现字典数组转模型数组的协议
// arrayContainModelClass 提供一个协议,只要遵守这个协议的类,都能把数组中的字典转模型
if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
//转换成 id 类型,就能调用任何对象的方法
id idSelf = self;
//获取数组中字典对应的模型
NSString *type = [idSelf arrayContainModelClass][key];
//生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
//遍历字典数组,生成模型数组
for (NSDictionary *dict in value) {
//字典转模型
id model = [classModel modelWithDict3:dict];
[arrM addObject:model];
}
// 把模型数组赋值给value
value = arrM;
}
}
//如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil,而报错
if (value) {
//给模型中属性赋值
[objc setValue:value forKey:key];
}
}
free(ivarList); // 释放
return objc;
}
总结:既然能获取到属性类型,就可以拦截到模型的那个数组属性,进而对数组中每个模型遍历并字典转模型,但是不知道数组中的模型都是什么类型,可以声明一个方法,改方法的目的不是让其调用,而是让其实现并返回模型的类型。
- 在OC 中调用一个方法,其实就是向一个对象发送消息,查找消息的唯一依据就是
selector
的名字。利用运行时可以偷换selector
对应的方法实现,达到给方法挂钩的目的。- 每个类都有一个方法列表,存放着方法的名字和方法的实现的映射关系,
selector
的本质其实就是方法名,IMP
有点类似函数指针,指向具体的Method
实现,通过selector
就可以找到对应的IMP
。- 交换方法的几种实现方式
*method_exchangeImplementations
交换两个方法的实现
*class_replaceMethod
替换方法的实现
*method_setImplementation
来设置某个方法的IMP
应用场景: 当第三方框架或者系统原生方法不能满足需求的时候,可以在保持系统方法的基础上,添加额外的功能。
需求: 加载一张图片直接用[UIImage imageNamed:@"icon"];
是无法知道到底有没有加载成功。给系统的 imageNamed
添加额外的功能(是否加载图片成功)。
- 方案一 : 继承系统的类,重写方法(弊端:每次使用都需要导入头文件)。
- 方法二 : 使用Runtime 交换方法。
实现步骤
1、给系统的方法添加分类
2、自己实现一个带有扩展功能的方法
3、交换方法,只需交换一次
案例代码 方法+调用+打印输出
- (void)viewDidLoad {
[super viewDidLoad];
//Runtime 交换方法
UIImage *img = [UIImage imageNamed:@"icon"];
//方案一:先搞个分类,定义一个能加载图片并且能打印的方法 + (instancetype)imageWithName:(NSString *)name;
//方案二:交换imageNamed 和 tw_imageNamed 的实现,就能调用 imageNamed 间接调用 tw_imageNamed 的实现。
}
#import "UIImage+Image.h"
#import
@implementation UIImage (Image)
/**
load方法: 把类加载进内存时候调用,只会调用一次
方法应先交换,再去调用
*/
+ (void)load {
//1、获取 imageNamed 方法地址
// class_getClassMethod 获取某个类的方法
Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
//2、获取 tw_iamgeNamed 方法地址
Method tw_imageNamedMethod = class_getClassMethod(self, @selector(tw_imageNamed:));
//3、交换方法地址,相当于交换实现方式 method_exchangeImplementations 交换两个方法的实现
method_exchangeImplementations(imageNamedMethod, tw_imageNamedMethod);
}
/**
以下代码是不会有死循环的
调用 imageNamed => tw_imageNamed
调用 tw_imageNamed => imageNamed
*/
+ (UIImage *)tw_imageNamed:(NSString *)name {
UIImage *image = [UIImage tw_imageNamed:name];
if (image) {
NSLog(@"加载成功");
}else {
NSLog(@"加载失败");
}
return image;
}
//不能在分类中重写系统方法 imageNamed ,因为会把系统的功能给覆盖掉,而且分类中不能调用super。
//所以第二步,我们要自己实现一个带有扩展功能的方法。
@end
总结: 在方法调用流程第三步的时候,交换两个方法地址指向,而且改变指向要在系统的 imageNamed:
方法调用前,所以将代码写在了分类的load 方法中。最后运行的时候系统的方法就会去找我们的方法的实现。
**原理:**给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类内存空间。
应用场景: 给系统的类(或者分类)添加属性的时候,可以使用runtime 动态添加属性方法。
注:在分类中国是不能够添加成员属性的,虽然可以用@property
,但是仅仅会自动生成get
和set
方法的声明,并没有带下划线的属性和方法实现生成,这就需要通过runtime来实现。
需求: 给系统 NSObject 类动态添加属性 name
字符串。
#import
@interface NSObject (Property)
//只会生成get 和 sey 方法声明,不会生成实现,也不会生成下划线成员属性
@property (nonatomic,copy) NSString *name;
@end
#import "NSObject+Property.h"
#import
@implementation NSObject (Property)
-(void)setName:(NSString *)name {
// objc_setAssociatedObject (将某个值跟某个对象关联起来,将某个值存储到某个对象中)
// object: 给哪个对象添加属性
// key: 属性名
// value: 属性值
// policy: 保存策略
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name {
return objc_getAssociatedObject(self, @"name");
}
@end
NSObject *obj = [[NSObject alloc] init];
obj.name = @"呵呵呵";
NSLog(@"%@",obj.name);
总结: 给属性赋值的本质,就是让属性与一个对象产生关联,所以要给NSObject
的分类的name 属性赋值就是让 name 和 NSObject 产生关联。
- (void)viewDidLoad {
[super viewDidLoad];
//动态添加方法
Person *p = [[Person alloc] init];
//默认person 没有实现run:方法,可以通过performSelector 调用,但是会报错
//动态添加方法就不会报错
[p performSelector:@selector(run:) withObject:@10];
}
#import "Person.h"
#import
@implementation Person
//没有返回值,1个参数
//void,(id,SEL)
void aaa(id self,SEL _cmd,NSNumber *meter) {
NSLog(@"跑了%@米",meter);
}
//任何方法默认都有两个隐式参数,self,_cmd (当前方法的方法编号)
//什么时候调用: 只要一个对象调用了一个未实现的方法就会调用这个方法进行处理
//作用:动态添加方法,处理未实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == NSSelectorFromString(@"run:")) {
//动态添加run方法
//class : 给哪个类添加方法
//SEL : 添加哪个方法,即添加方法的方法编号
//IMP : 方法实现 => 函数 => 函数入口 => 函数名 (添加方法的函数实现(函数地址))
//type : 方法类型(返回值 + 参数类型) v: void @ : 对象-> self : 表示SEL->_cmd
class_addMethod(self, sel, (IMP)aaa, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
@dynamic : 当把一个属性标记为 @dynamic 时,表示不需要编译器在编译时生成 getter 和 setter 方法的实现,而是当调用的时候才去为其生成 getter 和 setter 方法的实现。
@property(iOS 6以后出来的关键词)有两个对应的词,@synthsize @dynamic如果都没写,那么默认就是@synthsize var = _var(Xcode 4.5 以后);
//动态添加的方法必须是已经实现的
void RuntimeTestMethodIMP(id self,SEL _cmd,NSDictionary *dict) {
NSLog(@"传递进来的 dict%@",dict);
NSLog(@"打印成员变量name: %@",object_getIvar(self, class_getInstanceVariable([self class], "name")));
}
//1、在运行时动态生成一个类
Class cls = objc_allocateClassPair([NSObject class], "RuntimeClass", 0);
//2、为类添加方法和成员变量
//给指定的类添加成员变量 ,只能在objc_allocateClassPair() 和 objc_registerClassPair() 之间调用,并且不能为一个已经存在的类添加成员变量
class_addIvar(cls, "name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
class_addIvar(cls, "age", sizeof(int), sizeof(int), @encode(int));
SEL sel = sel_registerName("RuntimeTestMethod");
class_addMethod(cls, sel, (IMP)RuntimeTestMethodIMP, "i@:@");
//3、注册类到Runtime
objc_registerClassPair(cls);
//使用创建的类
//4、创建该类的实例,为新增的成员变量赋值
id person = [[cls alloc] init];
NSLog(@"实例所属的类: %@ ,实例所属的父类: %@",object_getClass(person),class_getSuperclass(object_getClass(person)));
Ivar nameIvar = class_getInstanceVariable(cls, "name");
object_setIvar(person, nameIvar, @"xiaohua");
Ivar ageIvar = class_getInstanceVariable(cls, "age");
object_setIvar(person, ageIvar, @18);
NSLog(@"实例变量Person的值%@ %@",object_getIvar(person, nameIvar),object_getIvar(person, ageIvar));
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"xiaoli",@"name",@"25",@"age", nil];
[person performSelector:@selector(RuntimeTestMethod) withObject:dic];
结果:
实例所属的类: RuntimeClass ,实例所属的父类: NSObject
实例变量Person的值xiaohua 18
传递进来的 dict{
age = 25;
name = xiaoli;
}
打印成员变量name: xiaohua
在做数据持久化的时候,如果一个模型有许多个属性,那么我们需要对每个属性都实现一遍 encodeObject 和 decodeObjectForKey 方法,如果这样的模型又有很多个,这还真的是一个十分麻烦的事情。
@interface Movie : NSObject
@property (nonatomic,copy) NSString *movieId;
@property (nonatomic,copy) NSString *movieName;
@property (nonatomic,copy) NSString *pic_url;
@end
@implementation Movie
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_movieId forKey:@"id"];
[aCoder encodeObject:_movieName forKey:@"name"];
[aCoder encodeObject:_pic_url forKey:@"url"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.movieId = [aDecoder decodeObjectForKey:@"id"];
self.movieName = [aDecoder decodeObjectForKey:@"name"];
self.pic_url = [aDecoder decodeObjectForKey:@"url"];
}
return self;
}
@end
像上面的代码,如果有100个属性,那就太麻烦了,可以使用Runtime 来处理:
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);
for (int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
//查看成员变量
const char *name = ivar_getName(ivar);
//归档
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);
for (int i = 0; i
这样不管有多少属性,几行代码就能搞定,下面对代码进行精简(两句话搞定):
#import "Movie.h"
#import
#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i
上面就是抽成了两个宏,这样使用起来就很方便了。
Runtime下Class的各项操作:
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i ++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property %@",[NSString stringWithUTF8String:propertyName]);
}
unsigned int count = 0;
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i = 0; i
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i = 0; i < count; i ++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar %@", [NSString stringWithUTF8String:ivarName]);
}
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i
Class PersonClass = object_getClass([父类 class]);
SEL oriSEL = @selector(xxx);
Method oriMethod = class_getMethod(子类, oriSEL);
Class PersonClass = object_getClass([xiaoming class]);
SEL oriSEL = @selector(test2);
Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
method_exchangeImplementations(method1, method2);
// 根据名字得到实例变量的Ivar指针
Ivar oneIVIvar = class_getInstanceVariable([Person class], name);
//找到后可以直接对私有成员变量赋值(强制修改name属性)
object_setIvar(_per, oneIVIvar, @"age");
//动态添加方法
class_addMethod([person class]:Class cls 类型, @selector(eat):待调用的方法名称, (IMP)myAddingFunction:(IMP)myAddingFunction,IMP是一个函数指针,这里表示指定具体实现方法myAddingFunction, 0:0代表没有参数);
//获得某个类的类方法
Method class_getClassMethod(Class cls , SEL name)
//获得成员变量的名字
const char *ivar_getName(Ivar v);
//将某个值跟某个对象关联起来,将某个值存储到某个对象中
void objc_setAssociatedObject(id object:表示关联者,是一个对象,变量名理所当然也是object , const void *key:获取被关联者的索引key ,id value :被关联者 ,objc_AssociationPolicy policy:关联时采用的协议,有assign,retain,copy等协议,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//利用参数key 将对象object中存储的对应值取出来
id objc_getAssociatedObject(id object , const void *key)
@interface Son : Person
@end
@implementation Son
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
答案:都是Son
class
获取当前方法的调用者的类superClass
获取当前方法的调用者的父类super
仅仅是一个编译器指示器,就是给编译器看的,不是一个指针- 只要编译器看到
super
这个标志,就会让当前对象去调用父类方法,本质还是当前对象在调用。self
是类的隐藏参数,指向当前调用方法的这个类的实例。而super
本质是一个编译器标识符,和self
是指向的同一个消息接受者。- 当使用
self
调用方法时,会从当前类的方法列表中开始查找,如果没有就从父类中再找。- 而当使用
super
时,则从父类的方法列表中开始找。然后调用父类的这个方法。- 调用
[self class]
时,会转化成objc_msgSend
函数。
id objc_msgSend(id self, SEL op, ...)
- 调用
[super class]
时,会转化成objc_msgSendSuper
函数。
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一个参数是 objc_super 这样一个结构体,其定义如下
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};
第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self
第二个成员是记录当前类的父类是什么,告诉程序从父类中开始找方法,找到方法后,最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用, 此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son
objc Runtime 开源代码对- (Class)class方法的实现
-(Class)class {
return object_getClass(self);
}
@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
- (void)print;
@end
@implementation Person
- (void)print{
NSLog(@"my name's %@",self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print]; // my name's
Person *person = [[Person alloc] init];
[person print]; //my name's (null)
}
@end
可以看出是能执行成功的,打印结果如下:
Runtime-面试题[34340:1470326] my name's
Runtime-面试题[34340:1470326] my name's (null)
比较好的总结:
西木 完整总结
天口三水羊 objc_msgSend
夜千寻墨
袁峥
消息机制
Method Swizzling开发实例汇总
OC最实用的runtime总结
Runtime在实际开发中的应用
https://www.jianshu.com/p/dbd184b0ba4d