您的位置:首页 > 移动开发 > IOS开发

运行时Runtime 小结

2015-08-08 22:29 411 查看
第一部分

1. [b]objc_msgSend  [/b]这是Objective-C的方法调用的核心,它可调用一个类的所有方法,不管它有没有暴露出来。
  

例如:
TestObj.h 文件:

@interface TestObj : NSObject

@property (nonatomic, assign) NSInteger userId;
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, assign) BOOL gender;

+ (NSInteger)test;
+ (NSString *)testWithArg:(NSInteger)arg1 arg2:(NSString *)arg2;

- (BOOL)isChnage;

@end


TestObj.m 文件:

@implementation TestObj
+ (NSInteger)test
{
NSLog(@"这是测试方法");

return 6;
}

+ (NSString *)testWithArg:(NSInteger)arg1 arg2:(NSString *)arg2
{
NSLog(@"测试方法 : %ld -- %@", arg1, arg2);

return @"test";
}

+ (void)test2
{
NSLog(@"内部方法");
}

- (BOOL)isChnage
{
NSLog(@“有没有改变");

return NO;
}

- (NSString *)changeUserName:(NSString *)userName
{
return @"修改后的名字";
}

@end


以下是调用示例:
   
// 直接调用类方法,返回值需要强转,也可以不处理返回值
NSInteger test1 = (NSInteger)objc_msgSend(objc_getClass("TestObj"), sel_registerName("test"));

NSLog(@"测试1:%ld", test1);

// 调用有参数的类方法
NSString *test2 = (NSString *) objc_msgSend(objc_getClass("TestObj"), @selector(testWithArg:arg2:), 6, @"消息");

NSLog(@"测试2:%@", test2);

// 直接调用TestObj里面的类方法,尽管它没有暴露出来, 这就是运行时的厉害之处
objc_msgSend(objc_getClass("TestObj"), sel_registerName("test2"));

// 调用方法
TestObj *testObj = [TestObj new];
objc_msgSend(testObj, sel_registerName("isChnage"));


第二部分

无论目标类有没有将方法暴露出来,一样可以同过运行时将它遍历出来,并可以找出方法的入参.

示例:

unsigned int methodCount;
Method *methods = class_copyMethodList(objc_getClass("TestObj"), &methodCount);
for (int i = 0; i < methodCount; i++) {

Method tempMethod = methods[i];

NSLog(@“方法名称:%s",   sel_getName(method_getName(tempMethod)));
NSLog(@“入参个数:%d",   method_getNumberOfArguments(tempMethod));
NSLog(@“%s”,            method_getTypeEncoding(tempMethod));
NSLog(@“返回值类型:%s”,  method_copyReturnType(tempMethod));

IMP imp = method_getImplementation(tempMethod);
NSLog(@"IMP: %@", imp_getBlock(imp));

NSLog(@"---入参----\n");

unsigned int argCount = method_getNumberOfArguments(tempMethod);
for (int j = 0; j < argCount; j++) {
NSLog(@"%s", method_copyArgumentType(tempMethod, j));
}
}

// 手动释放,避免内存泄露
free(methods);


1. 方法的入参默认会有两个,这是系统运行时添加进去的。
2. 运行时的入参和返回的数据类型跟OC 的数据类型不同,以下是苹果官方提供的类型对应列表

Table 6-1  Objective-C type encodings
Code
Meaning
c

A
char

i

An
int

s

A
short

l

A
long


l
is treated as a 32-bit quantity on 64-bit programs.
q

A
long long

C

An
unsigned char

I

An
unsigned int

S

An
unsigned short

L

An
unsigned long

Q

An
unsigned long long

f

A
float

d

A
double

B

A C++
bool
or a C99
_Bool

v

A
void

*

A character string (
char *
)
@

An object (whether statically typed or typed
id
)
#

A class object (
Class
)
:

A method selector (
SEL
)
[array type]
An array
{name=type...}
A structure
(name=type...)
A union
b
num
A bit field of num bits
^
type
A pointer to type
?

An unknown type (among other things, this code is used for function pointers)
Table 6-2  Objective-C method encodings
Code
Meaning
r

const

n

in

N

inout

o

out

O

bycopy

R

byref

V

oneway

第三部分

通过运行时,可以获取方法的变量,并且可以通过映射,动态修改变量名称,MJExtension 框架就是利用了这一特点,所以它可以通过MJExtensionConfig文件来修改参数的映射。

参考:http://www.cnblogs.com/ludashi/p/4673935.html

示例:
unsigned int propertyCount;
objc_property_t *propertys = class_copyPropertyList(objc_getClass("TestObj"), &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t tempProperty = propertys[i];

NSLog(@“成员变量名称:%s", property_getAttributes(tempProperty));
NSLog(@"成员变量描述:%s”, property_getAttributes(tempProperty));
}

// 释放
free(propertys);


第四部分

 关联对象(Associated Object),一个对象可以关联任何一个OC对象,包括 block。关联对象有点类似于成员变量,不过它是在运行时添加进去的。关联对象使用的是键值对的形式进行关联,同时还需要指定相应的内存管理策略。

涉及到的方法如下:
objc_setAssociatedObject(idobject,
constvoid *key,id value,
objc_AssociationPolicypolicy)  // 设置绑定对象
objc_getAssociatedObject(idobject,
constvoid *key)  // 获得绑定对象
objc_removeAssociatedObjects(idobject)   // 移除绑定对象

绑定对象的时候,需要根据对象的属性,设置不同的关联策略,也就是Objc的内存管理的引用计数机制,包括有:
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.
OBJC_ASSOCIATION_COPY_NONATOMIC =3,
 /**< Specifies that the associated object is copied. 
OBJC_ASSOCIATION_RETAIN =01401,
     /**< Specifies a strong reference to the associated object.
OBJC_ASSOCIATION_COPY =01403         /**<
Specifies that the associated object is copied.

示例代码:

// 绑定对象
TestObj2 *a = [TestObj2 new];
a.userID = 1001;
a.userName = @"AbooJan";
a.books = @[@"Harry", @"永无止境", @"编程思想"];

_test = [[TestObj alloc] init];
objc_setAssociatedObject(_test, "testObj", a, OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(_test, "testNum", @(666), OBJC_ASSOCIATION_ASSIGN);

// 获得绑定对象
TestObj2 *a = objc_getAssociatedObject(_test, "testObj”);
NSNumber *testNum = objc_getAssociatedObject(_test, "testNum");

NSLog(@"%@", [a description]);
NSLog(@"%ld", [testNum integerValue]);


第五部分

Method Swizzling,本身Objc的方法调用是通过消息转发机制来实现的,既然如此,就可以通过重新映射方法对应的实现来达到“偷天换日”的目的。跟消息转发相比,Method Swizzling 的做法更为隐蔽,甚至有些冒险,也增大了debug的难度。

代码示例:

/*
* 1. 如果是一个子类,一般在 initialize 方法中替换方法
* 2. 如果是一个分类,一般在 load       方法中实行替换
*
* 在以上两个方法中替换,是保证它在一启动的时候就实行替换
*
*
* load和initialize有很多共同特点,下面简单列一下:

* 1.在不考虑开发者主动使用的情况下,系统最多会调用一次
* 2.如果父类和子类都被调用,父类的调用一定在子类之前
* 3.都是为了应用运行提前创建合适的运行环境
* 4.在使用时都不要过重地依赖于这两个方法,除非真正必要
*
*
* +load 能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。
* +initialize 在其所属类的方法被调用或类初始化时会被调用,否则它可能永远不会被调用
*
*/
+ (void)load
{
NSLog(@"load");
}

+ (void)initialize
{
// dispatch_once是GCD中的一个方法,它保证了代码块只执行一次,并让其为一个原子操作,
//   保证线程的安 全,避免并发引发问题,认为它是Method Swizzling 的最佳实现

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

// 第一种初始化方法
Class target = [self class];

Method originalMethod = class_getInstanceMethod(target, sel_registerName("description"));
Method swizzMethod = class_getInstanceMethod(target, sel_registerName("swizzle_description"));

// 第二种初始化方法
// Class aClass = object_getClass((id)self);
//
// Method originalMethod = class_getClassMethod(aClass, @selector(description));
// Method swizzledMethod = class_getClassMethod(aClass, @selector(swizzle_description));

/*
*  object_getClass((id)self) 与 [self class] 返回的结果类型都是 Class,
*   但前者为元类,后者为其本身,因为此时 self 为 Class 而不是实例。
*/

/*
*  如果类中不存在要替换的方法,那就先用class_addMethod和class_replaceMethod函数添加
*  和替换两个方法的实现;如果类中已经有了想要替换的方法,那么就直接调
*  用 method_exchangeImplementations函数交换了两个方法的 IMP。
*  个人建议将要替换的方法在当前类先实现出来.
*/

//        BOOL didAddMethod = class_addMethod(target, method_getName(originalMethod), method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
//
//        if (didAddMethod) {
//
//            class_replaceMethod(target, method_getName(swizzMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
//
//        }else{
//            method_exchangeImplementations(originalMethod, swizzMethod);
//        }

// 如果确定已添加替换的方法,直接执行替换就可以了
method_exchangeImplementations(originalMethod, swizzMethod);

});
}

- (NSString *)description
{
NSLog(@"原来的方法");

return [super description];
}

- (void)swizzle_description
{
NSLog(@"这是替换的方法");

// 本身替换的方法只会执行一次,当执行以下方法的时候,它就会调用该类原来的方法了
[self aboo_description];

// 如果调用以下方法,就会进入死循环
// [self description];
}


Demo: https://github.com/AbooJan/RunTimeDemo.git
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  IOS 运行时