runtime变奏曲,那些藏在runtime中的接口(二)

学习进度:

  • runtime小序曲,从运行时多态看这股神秘力量
  • runtime进行曲,objc_msgSend的前世今生(一)
  • runtime进行曲,objc_msgSend的前世今生(二)
  • runtime变奏曲,那些藏在runtime中的接口(一)
  • runtime变奏曲,那些藏在runtime中的接口(二)

一、回顾与概要

前一章内容中,我们介绍了NSObject里面的常用runtime方法、runtime库中的主要数据结构和runtime库中的class相关方法。runtime库中除却class相关方法还有很多实用的方法,比如object相关、ivar相关、property相关、protocol相关等等等等。下面我将介绍runtime库中所有其它的public api。

二、Class外的Runtime API

前提

@protocol AProtocol 
- (void)aProtocolMethod;
@end
@interface B : NSObject
- (void)bTest;
@end
@implementation B
- (void)bTest {
    NSLog(@"bTest");
}
@end
@interface A : NSObject  {
    NSString *strA;
}
@property (nonatomic, assign) NSUInteger uintA;
- (void)test;
@end
@implementation A
- (void)test {
    NSLog(@"%lu", (unsigned long)_uintA);
}
@end
void aNewMethod() {
    NSLog(@"aNewMethod");
}

1、object相关

代码

// 获取类实例的大小
size_t size = class_getInstanceSize([A class]);
NSLog(@"%zu", size);    // a
// 动态创建一个实例
A *a = class_createInstance([A class], size);
a.uintA = 1;
NSLog(@"%d", a.uintA);  // b
// 销毁一个实例,但是并没有回收内存
objc_destructInstance(a);
// 回收内存
object_dispose(a);
size_t allocSize = 2 * size;
uintptr_t ptr = (uintptr_t)calloc(allocSize, 1);
// 构造一个实例并将其存入ptr
a = objc_constructInstance([A class], &ptr);
// 创建一个对象的copy,肯定是深copy,会开辟新空间
NSString *str = @"1";
NSString *str1 = object_copy(str, size);
NSLog(@"%@ %p %p", str1, str1, str); // c
// 动态为实例的成员变量赋值
NSString *b = @"111";
object_setInstanceVariable(a, "strA", b);
// 获取实例的某个成员变量的值
NSString *b1 = nil;
object_getInstanceVariable(a, "strA", (void **)&b1);
NSLog(@"%@", b1);   // d
// 指向a中额外的空间,也就是多余的,没测试
object_getIndexedIvars(a);
//  获取实例的某个成员变量的值,比object_getInstanceVariable快
unsigned int count;
Ivar *intIvar = class_copyIvarList([A class], &count);
id ivarValue =object_getIvar(a, intIvar[0]);
NSLog(@"%@", ivarValue);    // e
// 动态为实例的成员变量赋值,比object_setInstanceVariable快
object_setIvar(a, intIvar[0], @"222");
NSLog(@"%@", ivarValue);    // f
// 获取实例的class名称
const char *name = object_getClassName(a);
NSLog(@"%s", name); // g
// 获取实例的class
object_getClass(a);
// 将实例的isa指向一个新的class
object_setClass(a, [B class]);

输出

2017-02-15 17:11:31.723 block[48944:8475706] 12        // a
2017-02-15 17:11:31.728 block[48944:8475706] 1          // b
2017-02-15 17:11:31.729 block[48944:8475706] 1 0x7c035a80 0x64194        // c
2017-02-15 17:11:31.730 block[48944:8475706] 111        // d
2017-02-15 17:28:13.255 block[50522:8493921] 111  // e
2017-02-15 17:28:13.255 block[50522:8493921] 111  // f 
2017-02-15 17:28:13.256 block[50522:8493921] A  // g

2、ivar相关

代码

// 获取ivar名称
unsigned int count;
Ivar *ivars = class_copyIvarList([A class], &count);
const char *ivarName = ivar_getName(ivars[0]);
NSLog(@"%s", ivarName); // a
// 获取ivar类型编码
const char *encoding = ivar_getTypeEncoding(ivars[0]);
NSLog(@"%s", encoding); // b
// 获取某个成员变量的偏移量
ptrdiff_t offset = ivar_getOffset(ivars[0]);
NSLog(@"%td", offset);  // c

输出

2017-02-15 17:32:51.193 block[50992:8500649] strA  // a
2017-02-15 17:32:51.197 block[50992:8500649] @"NSString"  // b
2017-02-15 17:32:51.197 block[50992:8500649] 4  // c

3、property相关

代码

// 获取某个property的name
const char *prpertyName = property_getName(class_getProperty([A class], "uintA"));
NSLog(@"%s", prpertyName);  // a
// 获取property描述符,比如修饰词、类型、名字
const char *propertyAttributes = property_getAttributes(class_getProperty([A class], "uintA"));
NSLog(@"%s", propertyAttributes);   // b
// 获取property某个描述符的value
const char *value = property_copyAttributeValue(class_getProperty([A class], "uintA"), "T");
NSLog(@"%s", value);    // c
// 获取property描述符的列表
unsigned int attriCount;
objc_property_attribute_t *attrs = property_copyAttributeList(class_getProperty([A class], "uintA"), &attriCount);
NSLog(@"%s", attrs[0]); // d

输出

2017-02-15 17:39:54.064 block[51662:8507683] uintA
2017-02-15 17:39:54.069 block[51662:8507683] TI,N,V_uintA
2017-02-15 17:39:54.069 block[51662:8507683] I

4、关联property相关

代码

// 绑定属性,不会添加到class的propertylist
static void *kAssociatedObjectKey = &kAssociatedObjectKey;
NSString *assStr = @"assStr";
unsigned int count;
class_copyPropertyList([A class], &count);
objc_setAssociatedObject([A class], kAssociatedObjectKey, assStr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
class_copyPropertyList([A class], &count);
// 获取之前绑定的属性
objc_getAssociatedObject([A class], kAssociatedObjectKey);
NSLog(@"%@", assStr);   // a
A *assA = [A new];
assA.uintA = 10;
// 移除所有绑定的属性,不会影响class自身的property
objc_removeAssociatedObjects(assA);
NSLog(@"%@ %d", objc_getAssociatedObject(assA, kAssociatedObjectKey), assA.uintA); // b

输出

2017-02-15 17:52:08.162 block[52913:8524607] assStr
2017-02-15 17:52:08.167 block[52913:8524607] (null) 10

疑惑点
objc_setAssociatedObject绑定的属性并没有存在属性列表。具体可参见runtime源码。

5、sel相关

代码

// 获取sel的名称
const char *selName = sel_getName(@selector(test));
NSLog(@"%s", selName);  // a
// 生成一个新的sel
SEL newSel = sel_registerName("newSel");
NSLog(@"%s", newSel);   // b
// 同上。生成一个新的sel
SEL newSel1 = sel_getUid("newSel1");
NSLog(@"%s", newSel1);  // c
// 判断两个sel是否相同
if(sel_isEqual(newSel, newSel1)) {
  NSLog(@"相同sel");
} else {
  NSLog(@"不相同sel");   // d
}

输出

2017-02-15 18:06:09.912 block[54209:8542407] test  // a
2017-02-15 18:06:09.916 block[54209:8542407] newSel  // b
2017-02-15 18:06:09.917 block[54209:8542407] newSel1  // c
2017-02-15 18:06:09.917 block[54209:8542407] 不相同sel  // d

6、method相关

代码

// 调用一个object的method
// method_invoke_stret(testMethod, method); 参照void method_invoke_stret(void *stretAddr, id theReceiver, SEL theSelector, ....) stretAddr为返回的数据结构
// 若报错,project里面设定Enable Strict Checking of objc_msgSend Calls为NO
A *testMethod = [A new];
testMethod.uintA = 100;
unsigned int outCount;
Method *methods = class_copyMethodList([A class], &outCount);
Method method = methods[0];
method_invoke(testMethod, method);  // a
// 将某个method指向一个新的IMP
method_setImplementation(method, (IMP)aNewMethod);
method_invoke(testMethod, method);  // b
// 获取method的SEL
SEL methodSel = method_getName(method);
NSLog(@"%s", methodSel);    // c
// 获取method的IMP
// 随意传两个参数,无所谓的
IMP methodImp = method_getImplementation(method);
methodImp(0,0); // d
// 获取method的类型编码,包含返回值和参数
const char *methodEncoding = method_getTypeEncoding(method);
NSLog(@"%s", methodEncoding);   // e
// 获取method的返回值类型
const char *returnType = method_copyReturnType(method);
NSLog(@"%s", returnType);   // f
// 获取method的某个参数类型
const char *oneArgumentType = method_copyArgumentType(method, 0);
NSLog(@"%s", oneArgumentType);  // g
// 获取method的返回值类型
char dst[256];
method_getReturnType(method, dst, 256);
NSLog(@"%s", dst);  // h
// 获取method的参数个数
unsigned int numOfArgu = method_getNumberOfArguments(method);
NSLog(@"%d" ,numOfArgu);    // i
// 获取method的某个参数的类型
method_getArgumentType(method, 0, dst, 256);
NSLog(@"%s", dst);  // j
// 获取method的描述
objc_method_description des = *method_getDescription(method);
NSLog(@"%s", des);  // k
// 更换method method_exchangeImplementations
method_exchangeImplementations(methods[0], methods[1]);
method_invoke(testMethod, methods[1]);  // l

输出

2017-02-15 18:16:16.714 block[55151:8552107] 100  // a
2017-02-15 18:16:16.727 block[55151:8552107] aNewMethod  // b
2017-02-15 18:16:16.727 block[55151:8552107] test  // c
2017-02-15 18:16:16.728 block[55151:8552107] aNewMethod  // d
2017-02-15 18:16:16.728 block[55151:8552107] v8@0:4  // e
2017-02-15 18:16:16.728 block[55151:8552107] v  // f
2017-02-15 18:16:16.728 block[55151:8552107] @  // g
2017-02-15 18:16:16.729 block[55151:8552107] v  // h
2017-02-15 18:16:16.729 block[55151:8552107] 2  // i  
2017-02-15 18:16:16.729 block[55151:8552107] @  // j
2017-02-15 18:16:16.729 block[55151:8552107] test  // k
2017-02-15 18:16:16.729 block[55151:8552107] aNewMethod  // l

7、protocol相关

代码

// 根据char *获取Protocol
Protocol *protocol = objc_getProtocol("AProtocol");
NSLog(@"%s", protocol_getName(protocol));   // a
// 获取项目所有的protocol列表
unsigned int protocolCount;
Protocol * __unsafe_unretained *protocols = objc_copyProtocolList(&protocolCount);
NSLog(@"%d", protocolCount);    // b
// 动态创建一个protocol
Protocol *bProtocol = objc_allocateProtocol("BProtocol");
// 动态为protocol添加一个实例方法
protocol_addMethodDescription(bProtocol, NSSelectorFromString(@"bProtocolMethod"), "v@:", NO, YES);
// 动态为protocol添加property
// 添加属性必须两个参数都是YES,因为属性的方法必须是实例和requried
objc_property_attribute_t attributes[] = { { "T", "@\"NSString\"" }, { "&", "N" }, { "V", "" } };
protocol_addProperty(bProtocol, "bProtocolProperty", attributes, 3, YES, YES);
// 为protocol添加其需要遵循的protocol
protocol_addProtocol(bProtocol, @protocol(NSObject));
// 注册这个protocol
objc_registerProtocol(bProtocol);
// 获取protocol的所有可选的实例方法
class_addProtocol([A class], bProtocol);
class_addMethod([A class], NSSelectorFromString(@"bProtocolMethod"), (IMP)aNewMethod, "v@:");
unsigned int protocolOutCount;
protocol_copyMethodDescriptionList(bProtocol, NO, YES, &protocolOutCount);
NSLog(@"%d", protocolOutCount); // c
// 获取protocol中某个方法的描述
objc_method_description desc = protocol_getMethodDescription(bProtocol, NSSelectorFromString(@"bProtocolMethod"), NO, YES);
NSLog(@"%s", desc); // d
// 获取protocol的属性列表
protocol_copyPropertyList(bProtocol, &protocolOutCount);
NSLog(@"%d", protocolOutCount); // e
// 获取protocol中某个属性
objc_property_t proNewPro = protocol_getProperty(bProtocol, "bProtocolProperty", YES, YES);
NSLog(@"%s", proNewPro);    // f
// 获取protocol遵循的protocol列表
protocol_copyProtocolList(bProtocol, &protocolOutCount);
NSLog(@"%d", protocolOutCount); // g
// 判断某个protocol是否遵循另一个protocol
if (protocol_conformsToProtocol(bProtocol, @protocol(NSObject))) {
  NSLog(@"bProtocol遵循NSObject");  // h
}
// 判断两个protocol是否相同
if(!protocol_isEqual(bProtocol, @protocol(AProtocol))) {
  NSLog(@"bProtocol和Aprotocol不同");    // i
}

输出

2017-02-15 18:35:27.722 block[56930:8579131] Protocol  // a
2017-02-15 18:35:27.733 block[56930:8579131] 737  // b
2017-02-15 18:35:27.735 block[56930:8579131] 1  // c
2017-02-15 18:35:27.736 block[56930:8579131] bProtocolMethod  // d
2017-02-15 18:35:27.737 block[56930:8579131] 1  // e
2017-02-15 18:35:27.738 block[56930:8579131] §=  // f
2017-02-15 18:35:27.739 block[56930:8579131] 1  // g
2017-02-15 18:35:27.741 block[56930:8579131] bProtocol遵循NSObject  // h
2017-02-15 18:35:27.742 block[56930:8579131] bProtocol和Aprotocol不同  // i

疑惑点
a、若某类遵循了某个协议,则其派生类均遵循这个协议。
b、protocol的property和class的property区别:protocol的property相当于@requried的set/get,即property存在于protocol的结构体中,set/get存在于遵循protocol的class的结构体中。而class的property和set/get都存在于自身的结构体中。

8、message相关

代码

// objc_msgSend返回id类型
// objc_msgSend_fpret返回double类型
// void objc_msgSend_stret(id self, SEL op, ...) 为返回一个结构体,参照void objc_msgSend_stret(void *stretAddr, id theReceiver, SEL theSelector, ....)
objc_msgSend([B new], @selector(bTest)); // a
objc_msgSend_fpret([B new], @selector(bTest));  // b
// objc_msgSendSuper,向superClass发消息,返回id
// OBJC_EXPORT void objc_msgSendSuper_stret(struct objc_super *super, SEL op, ...) 为返回一个结构体,参照void objc_msgSendSuper_stret(void *stretAddr, id theReceiver, SEL theSelector, ....)
objc_super superClass = {(id)[B new], [B superclass]};
objc_msgSendSuper(&superClass, @selector(bTest));   // c

输出

2017-02-16 10:12:34.779 block[63306:8664764] bTest  // a
2017-02-16 10:12:34.785 block[63306:8664764] bTest  // b
c会crash,因为B父类NSObject中没有bTest方法。

9、library相关

代码

// 获取工程中所有的frameworks和dynamic libraries名称
unsigned int count;
const char **arr = objc_copyImageNames(&count);
NSLog(@"%s", arr[0]);
// 获取某个class所在的库名称
const char *frameworkName = class_getImageName([UIImage class]);
NSLog(@"%s", frameworkName);
// 根据某个库名,获取该库所有的class
const char **classArr = objc_copyClassNamesForImage(arr[0], &count);
NSLog(@"%s", classArr[0]);

输出

2017-02-16 11:00:57.510 block[67800:8717176] /Users/wuyang/Downloads/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/system/introspection/libdispatch.dylib
2017-02-16 11:00:57.551 block[67800:8717176] /Users/wuyang/Downloads/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/UIKit.framework/UIKit
2017-02-16 11:00:57.551 block[67800:8717176] OS_object

10、OC Features相关

代码

// A、
// void objc_enumerationMutation(id obj)
// void objc_setEnumerationMutationHandler(void (*handler)(id))
// 两个方法是用来在快速遍历的时候捕获一些突发性的问题的。(没测试过)
// B、 
// IMP imp_implementationWithBlock(id block)
// id imp_getBlock(IMP anImp)
// BOOL imp_removeBlock(IMP anImp)
// 三个方法是用来 绑定/获取/解绑 block到某个方法的。
// C、   
// id objc_loadWeak(id *location)
// id objc_storeWeak(id *location, id obj)
// objc_storeWeak即为正常的weak类型赋值的时候需要调用的方法。objc_loadWeak为在某个方法中使用weak类型对象的话,先retain并加入autoreleasepool,pop autoreleasepool的时候release。

疑惑点
A参见objc-foreach.c。
B参见Aspects。
C参见objc_loadWeak和objc_storeWeak。

三、Runtime的简单实用样例

1、Category关联属性

因为category只能为class增加方法,而不能为class增加属性,但是,在某些情况下,动态增加属性是必须的。所以有了这样的黑科技的方法,即并没有为class增加了属性,但是也实现了增加属性的效果,有点绕。
代码

// .h
#import 
@interface NSObject (Test)
@property (nonatomic, strong) NSString *test;
@end
// .m
#import "NSObject+Test.h"
#import 
@implementation NSObject (Test)
- (void)setTest:(NSString *)test {
    objc_setAssociatedObject(self, @selector(test), test, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)test {
    return objc_getAssociatedObject(self, @selector(test));
}
@end

推荐
关于为class动态绑定属性的深入了解,推荐文章:Objective-C Associated Objects 的实现原理。

2、Json映射Model

这里就不举例了,很多代码。推荐参看YYModel。后续我也会写一些解读YY系列的文章,毕竟代码质量太高了。

3、Swizzle

也不多说了,核心函数就是method_exchangeImplementations。后续会写两篇大量使用runtime,尤其是swizzle的方案:无埋点方案和防crash方案。

四、总结

断断续续一个月,总结了runtime的消息转发,和runtime所有的public api。就是希望自己能对runtime有个深入的认识,如果能帮到您那是更好。runtime到这里先画一个小逗号,无埋点方案和防crash方案的blog和代码将会在不久后放出。

五、文献

1、http://blog.leichunfeng.com/blog/2015/06/26/objective-c-associated-objects-implementation-principle/。
2、https://github.com/ibireme/YYModel/tree/master/YYModel。
3、https://github.com/opensource-apple/objc4
4、https://github.com/gcc-mirror/gcc/blob/master/libobjc/objc-foreach.c。
5、https://github.com/steipete/Aspects/blob/master/Aspects.m。
6、http://blog.devtang.com/2014/03/21/weak_object_lifecycle_and_tagged_pointer/。
7、http://esoftmobile.com/2012/12/16/memory-managment/。

你可能感兴趣的:(runtime变奏曲,那些藏在runtime中的接口(二))