老生常谈了。。。
简单介绍runtime
说起OC,最具代表性的就是它的动态性,其实完全就是靠runtime
完成的。
OC语言是一门动态语言,会将程序的一些决定工作从编译期推迟到运行期。 所以在编译阶段,OC
可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错,只有当运行的时候才会报错,这是因为OC
是运行时动态调用的。
运行时机制原理 :OC的函数调用称为消息发送,属于 动态调用过程。在 编译的时候 并不能决定真正调用哪个函数,只有在真 正运行的时候 才会根据函数的名称找到对应的函数来调用。
OC
语言在编译期都会被编译为C/C++
的Runtime
代码,二进制执行过程中执行的都是C/C++
代码。而OC
的类本质上都是结构体,在编译时都会以结构体的形式被编译到二进制中。Runtime是一套由C、C++、汇编实现的API,所有的方法调用都叫做消息发送(objc_msgSend
)。
runtime源码分析
那就从objc_msgSend
说起吧。
举个栗子
@interface Cat : NSObject
- (void)eat;
+ (void)run;
@end
@implementation Cat
@end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
Cat *cat = [[Cat alloc]init];
[cat eat];
[Cat run];
}
经命令行转换后:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o VC.cpp
Cat *cat = ((Cat *(*)(id, SEL))(void *)objc_msgSend)((id)((Cat *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Cat"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)cat, sel_registerName("eat"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Cat"), sel_registerName("run"));
}
看着太复杂,删减一下后
Cat *cat = (objc_msgSend)(
(objc_msgSend)(
objc_getClass("Cat"), sel_registerName("alloc")
), sel_registerName("init")
);
(objc_msgSend)(cat, sel_registerName("eat")); //像cat对象发送一条eat消息,
(objc_msgSend)(objc_getClass("Cat"), sel_registerName("run"));//先获取Cat的类对象,发送一条run消息
可以看出,不管是类方法还是对象方法,都要经过objc_msgSend
,
那就来看看objc_msgSend
的源码
objc-msg-arm64.s
END_ENTRY _objc_msgSend
bl _lookUpImpOrForward
...
汇编就不贴出来了
objc-runtime-new.mm
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//_objc_msgForward是由method_getImplementation()之类的东西返回的外部可调用函数。
//_objc_msgForward_impcache实际上是存储在方法缓存中的函数指针。
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
if (slowpath(!cls->isInitialized())) {//类是否初始化
behavior |= LOOKUP_NOCACHE; behavior |= 8 -> behavior=1
}
checkIsKnownClass(cls);//检查类是否是已知合法注册的
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);//初始化
curClass = cls;
//开始查找类的缓存
for (unsigned attempts = unreasonableClassCount();;) {//为类的任何迭代提供一个上限,以防止运行时元数据损坏时发生自旋。
if (curClass->cache.isConstantOptimizedCache(true)) {//类经过缓存优化过
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);//缓存查找
if (imp) goto done_unlock;//如果找到就跳到done_unlock
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);//从类的方法列表中找
if (meth) {
imp = meth->imp(false);
goto done;//如果找到就跳到done
}
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {//将curClass指针指向父类
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
//如果当前类的方法列表没有找到方法,就通过superClass去父类方法中找
imp = cache_getImp(curClass, sel);//缓存查找
if (slowpath(imp == forward_imp)) {
break;
}
if (fastpath(imp)) {
// 在父类中找到方法,跳到done
goto done;
}
}
// No implementation found. Try method resolver once.
//如果至此还没有找到方法实现,就专项动态方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);//如果找到方法,就填充到当前类的方法缓存中去
}
done_unlock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
下面是查找方法(非缓存中查找)的详细步骤源码:
缓存中查找见类的结构中cache_t
相关
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
//遍历cls类的方法列表 class_rw_t -> methods
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
//查找方法
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->isExpectedSize();
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
//查找排好序的方法列表
return findMethodInSortedMethodList(sel, mlist);
} else {
//未排好序的,就线性查找
if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
return m;
}
return nil;
}
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
if (list->isSmallList()) {
if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
} else {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
}
} else {
return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
}
}
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
auto first = list->begin();
auto base = first;
decltype(first) probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
//二分查找
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) {
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
这只是消息发送的部分源码,动态方法解析、消息转发的源码会放在下面。
objc_msgSend执行流程
1、消息发送
处在这个阶段的时候,主要就是找方法;
用图来展示: 过程大概如下:
-
1、首先判断消息接受者
receiver
是否为nil
,如果为nil
直接退出消息发送 -
2、如果存在消息接受者
receiverClass
,首先在消息接受者receiverClass
的cache
中查找方法,如果找到方法,直接调用。如果找不到,往下进行 -
3、没有在消息接受者
receiverClass
的cache
中找到方法,则从receiverClass
的class_rw_t
中查找方法,如果找到方法,执行方法,并把该方法缓存到receiverClass
的cache
中;如果没有找到,往下进行 -
4、没有在
receiverClass
中找到方法,则通过superClass
指针找到superClass
,也是先在缓存中查找,如果找到,执行方法,并把该方法缓存到receiverClass
的cache
中;如果没有找到,往下进行 -
5、没有在消息接受者
superClass
的cache
中找到方法,则从superClass
的class_rw_t
中查找方法,如果找到方法,执行方法,并把该方法缓存到receiverClass
的cache
中;如果没有找到,重复4、5步骤。如果找不到了superClass
了,往下进行 -
6、如果在最底层的
superClass
也找不到该方法,则要转到动态方法解析 补充一下:
1、如果是从class_rw_t
中查找方法时:
已经排序的是二分查找
;
没有排序的就遍历查找
2、receiver
通过isa
指针找到receiverClass
receiverClass
通过superclass
指针找到superClass
消息发送的源码就是上面objc_msgSend
的那部分,此处不再重复。
消息发送流程是我们平时最经常使用的流程,其他的像动态方法解析
和消息转发
其实是补救措施。
2、动态方法解析
-
开发者可以实现以下方法,来动态添加方法实现
- +resolveInstanceMethod:
- +resolveClassMethod:
-
动态解析过后,会重新走
消息发送
的流程,从receiverClass
的cache
中查找方法这一步开始执行
如果一个类,只有方法的声明,没有方法的实现,会出现最常见错误:unrecognized selector sent to instance
动态方法解析
动态方法解析需要调用resolveInstanceMethod
或者resolveClassMethod
一个对应实例方法,一个对应类方法。
以resolveInstanceMethod
为例:
类的声明,只有testInstanceMethod方法
@interface Person : NSObject
- (void)testInstanceMethod;
@end
类的实现中无任何方法实现
@implementation Person
@end
调用:
Person *p = [[Person alloc]init];
[p testInstanceMethod];
如果没有方法的实现,就会直接报错:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person testInstanceMethod]: unrecognized selector sent to instance 0x10051b9d0'
如果不增加类的实现,如何避免崩溃呢?
这时就可以在resolveInstanceMethod
方法中,做处理
类的实现中在resolveInstanceMethod方法中动态添加方法
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(testInstanceMethod)) {
Method method = class_getInstanceMethod(self, @selector(realInstaceMethod));
//动态添加realInstanceMethod方法
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
}
return [super resolveInstanceMethod:sel];
}
- (void)realInstaceMethod
{
NSLog(@"%s",__func__);
}
@end
此时再去打印,结果是:-[Person realInstaceMethod]
这里只是对象的方法处理,如果是类方法,就需要在resolveClassMethod
方法中做处理
接下来看看源码:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
if (! cls->isMetaClass()) {//如果不是元类,就是去类对象中找对象方法
resolveInstanceMethod(inst, sel, cls);
}
else {
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
//从这里可以看出动态解析过后,会重新走消息发送的流程
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
// 给cls 发送 sel 消息
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
//有删减
}
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//给cls 发送 sel 消息
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
//有删减
}
如果在动态方法解析阶段,还没有做处理,方法还是没找到,就会进入到消息转发
阶段
3、消息转发
如果方法一个方法在消息发送阶段
没有找到相关方法,也没有进行动态方法解析
,这个时候就会走到消息转发阶段了。
- 调用
forwardingTargetForSelector
,返回值不为nil时,会调用objc_msgSend(返回值, SEL)
- 调用
methodSignatureForSelector
,返回值不为nil,调用forwardInvocation:
方法;返回值为nil时,调用doesNotRecognizeSelector:
方法 - 开发者可以在forwardInvocation:方法中自定义任何逻辑
- 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
forwardingTargetForSelector
@interface Person : NSObject
- (void)testMethod;
@end
@implementation Person
@end
调用:
Person *p = [[Person alloc]init];
[p testMethod];
调用p
的testMethod
方法,由于未实现,就会报错:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person testMethod]: unrecognized selector sent to instance 0x102404af0'
此时在Person.m中添加这个方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [[Student alloc]init];
}
return nil;
}
调用forwardingTargetForSelector,返回值不为nil时,会调用objc_msgSend(返回值, SEL),结果就是调用了objc_msgSend(Student,testMethod)
此时的Student
类如下:
@interface Student : Person
- (void)testMethod;
@end
@implementation Student
- (void)testMethod
{
NSLog(@"%s ",__func__);
}
@end
所以调用[p testMethod]
,会打印:-[Student testMethod]
;
methodSignatureForSelector(方法签名)
当forwardingTargetForSelector
返回值为nil
,或者都没有调用该方法的时候,系统会调用methodSignatureForSelector
方法。
调用methodSignatureForSelector
,返回值不为nil,调用forwardInvocation:
方法;返回值为nil时,调用doesNotRecognizeSelector:
方法
对于方法签名的生成方式
[NSMethodSignature signatureWithObjCTypes:"i@"]
实现方法签名以后我们还要实现forwardInvocation方法,当调用person的testMethod的方法的时候,就会走到这个方法中
NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
- anInvocation.target 方法调用者
- anInvocation.selector 方法名
- [anInvocation getArgument:NULL atIndex:0]
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"%s ",__func__);
return [NSMethodSignature signatureWithObjCTypes:"i@"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"%s ",__func__);
anInvocation.target = [[Student alloc]init];
[anInvocation invoke];
}
打印结果:
-[Person methodSignatureForSelector:]
-[Person forwardInvocation:]
-[Student testMethod]
最后这三个过程都找不到方法,就会报经典错误 unrecognized selector sent to instance
关于消息转发阶段的源码,没有开源,在网上找到相关的伪代码,仅供参考:
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
}
}
if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
// The point of no return.
kill(getpid(), 9);
}
一个完整的方法执行流程如图:
runtime的应用
利用关联对象给分类添加成员变量
虽然不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果。
利用关联对象方法,在分类中增加属性(分类中的属性,是不会自动生成setter方法和getter方法),重写setter方法和getter方法
@interface Cat (Test)
@property(nonatomic ,copy)NSString *name;
@end
@implementation Cat (Test)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, _cmd);
}
@end
验证:
Cat *cat = [[Cat alloc]init];
cat.name = @"Tom";
NSLog(@"cat.name = %@",cat.name);
打印结果:
cat.name = Tom
其中主要的API:
添加关联对象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
获得关联对象
id objc_getAssociatedObject(id object, const void * key)
移除所有的关联对象
void objc_removeAssociatedObjects(id object)
补充一下:设置关联对象为nil,就相当于是移除关联对象
关联策略
OBJC_ASSOCIATION_ASSIGN--------------------------- assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC----------------- strong, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC------------------- copy, nonatomic
OBJC_ASSOCIATION_RETAIN--------------------------- strong, atomic
OBJC_ASSOCIATION_COPY----------------------------- copy, atomic
问:关联对象的方法会不会与Category中的方法一样,在运行时的时候把方法合并到原来类的数据里面?
不是的,会分开存储,不会影响原来类、分类的结构
关联对象并不是存储在被关联对象本身内存中
关联对象存储在全局的统一的一个AssociationsManager
中
相关源码:objc-references.mm
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
};
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
class ObjcAssociation {
uintptr_t _policy;
id _value;
};
方法交换method swizzling(俗称黑魔法)
主要作用是在运行时将一个方法的实现替换成另一个方法的实现,这就是我们常说的iOS黑魔法
@implementation Cat
+ (void)load
{
Method runMethod = class_getInstanceMethod([Cat class], @selector(run));
Method eatMethod = class_getInstanceMethod([Cat class], @selector(eat));
method_exchangeImplementations(runMethod, eatMethod);
}
- (void)run
{
NSLog(@"%s",__func__);
}
- (void)eat
{
NSLog(@"%s",__func__);
}
@end
验证:
Cat *cat = [[Cat alloc]init];
[cat run];
[cat eat];
打印结果:
-[Cat eat]
-[Cat run]
这里的method_exchangeImplementations
是交换两个方法的实现,还有一个是用一个方法的实现,替换另一个方法的实现class_replaceMethod
Method eatMethod = class_getInstanceMethod([Cat class], @selector(eat));
class_replaceMethod([Cat class], @selector(run), method_getImplementation(eatMethod), "v");
验证:
Cat *cat = [[Cat alloc]init];
[cat run];
[cat eat];
打印结果:
-[Cat eat]
-[Cat eat]
Method Swizzle
注意事项:
1、需要注意的是交换方法实现后的副作用, method_exchangeImplementations()
.交换方法函数最终会以objc_msgSend()
方式调用,副作用主要集中在第一个参数
如下示例:
objc_msgSend(payment, @selector(quantity))
方法交换后再去调用quantity
方法将有可能会crash
.解决这种副作用的方式是使用,method_setImplementation()
来替换原来的交换方式,这样才最为合理。
2、避免交换父类方法
如果当前类没有实现被交换的方法但父类实现了,此时父类的实现会被交换,若此父类的多个继承者都在交换时会引起多次交换导致混乱,同时调用父类方法有可能因为找不到方法签名而crash。 所以交换前都应该check能否为当前类添加被交换的函数的新的实现IMP,这个过程大概分为3步骤
class_addMethod
check能否添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
给类cls的SEL添加一个实现IMP, 返回YES则表明类cls并未实现此方法,返回NO则表明类已实现了此方法。注意:添加成功与否,完全由该类本身来决定,与父类有无该方法无关。
-
class_replaceMethod 替换类cls的SEL的函数实现为imp
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
-
method_exchangeImplementations 最终方法交换
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
3、 交换的分类方法应该添加自定义前缀,避免冲突; 这个毫无疑问,方法名称一样的时候会出现,分类的方法会覆盖类中同名的方法;
4、 还有一点,大多数人觉得交换方法应在+load
方法,但不是绝对的
+load
不是消息转发的方式实现的且在运行时初始化过程中类被加载的时候调用,而且父类,当前类,category,子类等 都会调用一次。
获取类的成员变量
@interface Cat : NSObject
{
@public
NSString *_name;
int _age;
}
@end
unsigned int count;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
打印结果:
_name @"NSString"
_age i
这个常用的就是json
转model
;
还有就是查看控件内部有哪些元素,然后进行修改,
如textField
的占位文字颜色,之前都是通过KVC
修改:
[_textField setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"]
不过现在苹果不允许,只能通过attributedPlaceholder
属性了。
KVO
KVO
的实现依赖于Objective-C
强大的Runtime
,实现步骤大致如下:
1、 利用runtimeAPI动态生成一个子类(NSKVONotifying_XXX),并且让instance对象的isa指向这个全新的子类;
2、当修改instance对象的属性时,会调用Foundation的 _NSSetXXXValueAndNotify函数(_NSSetIntValueAndNotify、_NSSetDoubleValueAndNotify)
在函数内会调用以下三个方法:
1 willChangeValueForKey:
2 父类原来的setter方法
3 didChangeValueForKey: 内部会触发(Obrserve)的监听方法:observeValueForKeyPath: ofObject: change: context:
动态增加方法
刚才在动态方法解析中提到了,此处不再演示。
runtime中的其他API
其实runtime
提供了许多有用的API,此处列表一部分:
- 类方法相关
1、Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) 动态创建一个类(参数:父类,类名,额外的内存空间)
2、void objc_registerClassPair(Class cls) 注册一个类(要在类注册之前添加成员变量)
3、void objc_disposeClassPair(Class cls) 销毁一个类
4、Class object_getClass(id obj) 获取isa指向的Class
5、Class object_setClass(id obj, Class cls)设置isa指向的Class
6、BOOL object_isClass(id obj) 判断一个OC对象是否为Class
7、BOOL class_isMetaClass(Class cls) 判断一个Class是否为元类
8、Class class_getSuperclass(Class cls)获取父类
- 成员变量相关
1、Ivar class_getInstanceVariable(Class cls, const char *name) 获取一个实例变量信息
2、Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 拷贝实例变量列表(最后需要调用free释放)
3、void object_setIvar(id obj, Ivar ivar, id value) 设置成员变量的值
4、id object_getIvar(id obj, Ivar ivar) 获取成员变量的值
5、BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types) 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
6、const char *ivar_getName(Ivar v), const char *ivar_getTypeEncoding(Ivar v)获取成员变量的相关信息
- 属性相关
1、objc_property_t class_getProperty(Class cls, const char *name) 获取一个属性
2、objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)拷贝属性列表(最后需要调用free释放)
3、BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) 动态添加属性
4、void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) 动态替换属性
5、const char *property_getName(objc_property_t property) 获取属性的一些信息
6、const char *property_getAttributes(objc_property_t property) 获取属性的一些信息
- 方法相关
1、获得一个实例方法、类方法 - Method class_getInstanceMethod(Class cls, SEL name) - Method class_getClassMethod(Class cls, SEL name)
2、方法实现相关操作 - IMP class_getMethodImplementation(Class cls, SEL name) - IMP method_setImplementation(Method m, IMP imp) - void method_exchangeImplementations(Method m1, Method m2)
3、拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
4、动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
5、动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
6、选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
7、用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
利用runtime创建一个类:
// 创建类
Class newClass = objc_allocateClassPair([NSObject class], "Dog", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
//注册类
objc_registerClassPair(newClass);
// 成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList(newClass, &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
// 在不需要这个类时释放
objc_disposeClassPair(newClass);
打印:
_age i
_weight i
其他相关runtime
曾经读过一篇文章,写的很好,原文链接
问题一:现有一个继承于NSObject
的实例对象,需要在不直接修改方法实现的情况下,改变一个方法的行为,你会怎么做?
绝大多数人想的肯定就是 Runtime
的 Method Swizzling
。
问题二:如果使用 Method Swizzling
技术,相当于修改了类对象中方法选择器和IMP实现的对应关系。这将导致继承自这个类的所有子类和实例对象
都影响,如何控制受影响的范围,或者说如何让方法的行为改变只对这个实例对象生效?
答案还是 Runtime
技术。
下面给出一个示例:
@interface Person : NSObject
@property (nonatomic, strong, nullable) NSString *firstName;
@property (nonatomic, strong, nullable) NSString *lastName;
@end
@implementation Person
@end
使用时:
Person *person = [[Person alloc] init];
person.firstName = @"Tom";
person.lastName = @"Google";
NSLog(@"person full name: %@ %@", person.firstName, person.lastName);
如果想要满足问题二,做如下改动:
@interface Person : NSObject
@property (nonatomic, strong, nullable) NSString *firstName;
@property (nonatomic, strong, nullable) NSString *lastName;
@end
@implementation Person
@end
NSString *demo_getLastName(id self, SEL selector)
{
return @"Apple";
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
person.firstName = @"Tom";
person.lastName = @"Google";
NSLog(@"person full name: %@ %@", person.firstName, person.lastName);
// 1.创建一个子类
NSString *oldName = NSStringFromClass([person class]);
NSString *newName = [NSString stringWithFormat:@"Subclass_%@", oldName];
Class customClass = objc_allocateClassPair([person class], newName.UTF8String, 0);
objc_registerClassPair(customClass);
// 2.重写get方法
SEL sel = @selector(lastName);
Method method = class_getInstanceMethod([person class], sel);
const char *type = method_getTypeEncoding(method);
class_addMethod(customClass, sel, (IMP)demo_getLastName, type);
// 3.修改修改isa指针(isa swizzling)
object_setClass(person, customClass);
NSLog(@"person full name: %@ %@", person.firstName, person.lastName);
Person *person2 = [[Person alloc] init];
person2.firstName = @"Jerry";
person2.lastName = @"Google";
NSLog(@"person2 full name: %@ %@", person2.firstName, person2.lastName);
}
@end
打印:
person full name: Tom Google
person full name: Tom Apple
person2 full name: Jerry Google
我们使用 isa-swizzling
将person
对象lastName
的行为改变了,而person2
对象没有受到影响。
仅仅修改了一个实例对象的行为。
就先简单介绍runtime
这些常见的吧, end。
以上若有错误,欢迎指正。转载请注明出处。