运行时Runtime 小结



第一部分


1. objc_msgSend  这是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

bnum

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文件来修改参数的映射。


示例:
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, const void *key, id value, objc_AssociationPolicypolicy)  // 设置绑定对象
  • objc_getAssociatedObject(idobject, const void *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];
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值