Alan_Sim

心魔,一辈子的敌人


  • 首页

  • 分类

  • 关于

  • 标签

  • 搜索

Objective-C运行时的一些黑魔法-方法与消息

发表于 2016-06-15   |   分类于 ”iOS”   |       阅读次数

笔记:

基础数据类型:

SEL:


SEL又叫选择器,是表示一个方法的selector的指针
Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL
1
2
SEL sel1 = @selector(method1);
NSLog(@"sel : %p", sel1);

1
输出:RuntimeTest[52734:466626] sel : 0x100002d72

注意点:
在 Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法
不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP
工程中的所有的SEL组成一个Set集合,Set的特点就是唯一,因此SEL是唯一的。因此,如果我们想到这个方法集合中查找某个方法时,只需要去 找到这个方法对应的SEL就行了,SEL实际上就是根据方法名hash化了的一个字符串,而对于字符串的比较仅仅需要比较他们的地址就可以,效率高

IMP


IMP实际上是一个函数指针,指向方法实现的首地址
定义如下:
1
id (*IMP)(id, SEL, …)

这个函数使用当前CPU架构实现的标准的C调用约定。第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),接下来是方法的实际参数列表。
由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的 IMP,查找过程将在下面讨论。取得IMP后,我们就获得了执行这个方法代码的入口点,此时,我们就可以像调用普通的C语言函数一样来使用这个函数指针了

Method


1
2
3
4
5
6
7
typedef struct objc_method *Method;
 
struct objc_method {
    SEL method_name                 OBJC2_UNAVAILABLE;  // 方法名
    char *method_types                  OBJC2_UNAVAILABLE;
    IMP method_imp                      OBJC2_UNAVAILABLE;  // 方法实现
}

该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码

方法相关操作函数


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 调用指定方法的实现
id method_invoke ( id receiver, Method m, ... );
 
// 调用返回一个数据结构的方法的实现
void method_invoke_stret ( id receiver, Method m, ... );
 
// 获取方法名
SEL method_getName ( Method m );
 
// 返回方法的实现
IMP method_getImplementation ( Method m );
 
// 获取描述方法参数和返回值类型的字符串
const char * method_getTypeEncoding ( Method m );
 
// 获取方法的返回值类型的字符串
char * method_copyReturnType ( Method m );
 
// 获取方法的指定位置参数的类型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
 
// 通过引用返回方法的返回值类型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
 
// 返回方法的参数的个数
unsigned int method_getNumberOfArguments ( Method m );
 
// 通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
 
// 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );
 
// 设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );
 
// 交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );

方法调用流程


在Objective-C中,消息直到运行时才绑定到方法实现上。编译器会将消息表达式[receiver message]转化为一个消息函数的调用,即objc_msgSend。
1
2
3
objc_msgSend(receiver, selector)
//含多参数
objc_msgSend(receiver, selector, arg1, arg2, ...)

当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表里面查找方法的selector。如果 没有找到selector,则通过objc_msgSend结构体中的指向父类的指针找到其父类,并在父类的分发表里面查找方法的selector。依 此,会一直沿着类的继承体系到达NSObject类。一旦定位到selector,函数会就获取到了实现的入口点,并传入相应的参数来执行方法的具体实 现。如果最后没有定位到selector,则会走消息转发流程。
消息传递

隐藏参数


objc_msgSend有两个隐藏参数:

  • 消息接收对象
  • 方法的selector
    1
    2
    3
    4
    5
    6
    7
    8
    9
    - strange
    {
        id  target = getTheReceiver();
        SEL method = getTheMethod();
     
        if ( target == self || method == _cmd )
            return nil;
        return [target performSelector:method];
    }

获取方法地址


NSObject类提供了methodForSelector:方法,让我们可以获取到方法的指针,然后通过这个指针来调用实现代码。我们需要将methodForSelector:返回的指针转换为合适的函数类型,函数参数和返回值都需要匹配上。
1
2
3
4
5
6
7
void (*setter)(id, SEL, BOOL);
int i;
 
setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);

消息转发


当一个对象无法接收某一消息时,就会启动所谓”消息转发(message forwarding)“机制,通过这一机制,我们可以告诉对象如何处理未知的消息。默认情况下,对象接收到未知的消息,会导致程序崩溃,控制台输出异常信息。
消息转发机制基本上分为三个步骤:

  • 动态方法解析
    对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或 者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法”“。不过使用该方法的前提是我们已经 实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void functionForMethod1(id self, SEL _cmd) {
       NSLog(@"%@, %p", self, _cmd);
    }
     
    + (BOOL)resolveInstanceMethod:(SEL)sel {
     
        NSString *selectorString = NSStringFromSelector(sel);
     
        if ([selectorString isEqualToString:@"method1"]) {
            class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
        }
     
        return [super resolveInstanceMethod:sel];
    }
  • 备用接收者
    如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    @interface SUTRuntimeMethodHelper : NSObject
     
    - (void)method2;
     
    @end
     
    @implementation SUTRuntimeMethodHelper
     
    - (void)method2 {
        NSLog(@"%@, %p", self, _cmd);
    }
     
    @end
     
    #pragma mark -
     
    @interface SUTRuntimeMethod () {
        SUTRuntimeMethodHelper *_helper;
    }
     
    @end
     
    @implementation SUTRuntimeMethod
     
    + (instancetype)object {
        return [[self alloc] init];
    }
     
    - (instancetype)init {
        self = [super init];
        if (self != nil) {
            _helper = [[SUTRuntimeMethodHelper alloc] init];
        }
     
        return self;
    }
     
    - (void)test {
        [self performSelector:@selector(method2)];
    }
     
    - (id)forwardingTargetForSelector:(SEL)aSelector {
     
        NSLog(@"forwardingTargetForSelector");
     
        NSString *selectorString = NSStringFromSelector(aSelector);
     
        // 将消息转发给_helper来处理
        if ([selectorString isEqualToString:@"method2"]) {
            return _helper;
        }
     
        return [super forwardingTargetForSelector:aSelector];
    }
     
    @end
  • 完整转发
    运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息 有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation 方法中选择将消息转发给其它对象。
    forwardInvocation:方法的实现有两个任务:

  1. 定位可以响应封装在anInvocation中的消息的对象。这个对象不需要能处理所有未知消息。
  2. 使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
     
        if (!signature) {
            if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
                signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
            }
        }
     
        return signature;
    }
     
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:_helper];
        }
    }

Objective-C运行时的一些黑魔法-成员变量与属性

发表于 2016-06-15   |   分类于 ”iOS”   |       阅读次数

文章地址

笔记:

类型编码(Type Encoding)


作为对Runtime的补充,编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联在一起。这种编码方案在其它情况下也是非常有用的,因此我们可以使用@encode编译器指令来获取它。当给定一个类型时,@encode返回这个类型的字符串编码。这些类型可以是诸如int、指针这样的基本类型,也可以是结构体、类等类型。事实上,任何可以作为sizeof()操作参数的类型都可以用于@encode()。

EXAMPLE:


1
2
float a[] = {1.0, 2.0, 3.0}; 
NSLog(@"array encoding type: %s", @encode(typeof(a)));

成员变量、属性

  • Ivar
    Ivar是表示实例变量的类型,其实际是一个指向objc_ivar结构体的指针。
  • objc_property_t
    objc_property_t是表示Objective-C声明的属性的类型,其实际是指向objc_property结构体的指针
  • objc_property_attribute_t
    objc_property_attribute_t定义了属性的特性(attribute),它是一个结构体
  • 关联对象(Associated Object)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    static const void *kTapGestureRecognizer = &kTapGestureRecognizer;
    static const void *kDTActionHandlerTapBlockKey = &kDTActionHandlerTapBlockKey;

    //static NSString *cellIdentifier = @"cellIdentifier";

    @implementation ViewController

    - (void)viewDidLoad {
    [super viewDidLoad];
    [self setTapActionWithBlock:^{
    NSLog(@"TAP!!!!!!!!!!TAP!!!!!!!!!TAP!!!!!!!!!");
    }];
    }

    - (void)setTapActionWithBlock:(void (^)(void))block {
    UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, kTapGestureRecognizer);
    if (!gesture) {
    gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(e__handleActionForTapGesture:)];
    [self.view addGestureRecognizer:gesture];
    objc_setAssociatedObject(self, kTapGestureRecognizer, gesture, OBJC_ASSOCIATION_RETAIN);
    }

    objc_setAssociatedObject(self, kDTActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);
    }

    - (void)e__handleActionForTapGesture:(UITapGestureRecognizer *)gesture {
    if (gesture.state == UIGestureRecognizerStateRecognized) {
    void (^action)(void) = objc_getAssociatedObject(self, kDTActionHandlerTapBlockKey);
    if (action) {
    action();
    }
    }
    }
    @end

成员变量、属性的操作方法


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#import "MyObject.h"
#import <objc/runtime.h>

static NSMutableDictionary *map = nil;

@implementation MyObject

+ (void)load {
map = [NSMutableDictionary dictionary];

map[@"name1"] = @"name";
map[@"status1"] = @"status";
map[@"name2"] = @"name";
map[@"status2"] = @"status";
}

- (void)setDataWithDic:(NSDictionary *)dic {
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSString *propertyKey = [self propertyForKey:key];
if (propertyKey)
{
objc_property_t property = class_getProperty([self class], [propertyKey UTF8String]);

// TODO: 针对特殊数据类型做处理
NSString *attributeString = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];

NSLog(@"%@", attributeString);

[self setValue:obj forKey:propertyKey];
}
}];
}

- (NSString *)propertyForKey:(NSString *)key {
return map[key];
}

@end

Objective-C运行时的一些黑魔法-类与对象

发表于 2016-06-14   |   分类于 ”iOS”   |       阅读次数

文章地址

笔记:


OC类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针

objc_class结构体中重要的字段:

  • isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。
  • metaClass:我们向一个对象发送消息(调用对象方法)时,runtime会在这个对象的所属类的方法列表中查找响应方法;而向一个类发送消息(调用类方法)时,会在这个类的meta-class的方法列表查找响应方法。
    Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。
  • super_class:指向该类的父类,如果是根类的话,则super_class为NULL
  • cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
  • version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。
  • info:供运行期使用的一些位标识。
  • instance_size:该类的实例变量大小
  • ivars:成员变量的数组

EXAMPLE:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//-----------------------------------------------------------
// MyClass.h
 
@interface MyClass : NSObject @property (nonatomic, strong) NSArray *array;
 
@property (nonatomic, copy) NSString *string;
 
- (void)method1;
 
- (void)method2;
 
+ (void)classMethod1;
 
@end
 
//-----------------------------------------------------------
// MyClass.m
 
#import "MyClass.h"
 
@interface MyClass () {
    NSInteger       _instance1;
 
    NSString    *   _instance2;
}
 
@property (nonatomic, assign) NSUInteger integer;
 
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
 
@end
 
@implementation MyClass
 
+ (void)classMethod1 {
 
}
 
- (void)method1 {
    NSLog(@"call method method1");
}
 
- (void)method2 {
 
}
 
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
 
    NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
 
@end
 
//-----------------------------------------------------------
// main.h
 
#import "MyClass.h"
#import "MySubClass.h"
#import int main(int argc, const char * argv[]) {
    @autoreleasepool {
 
        MyClass *myClass = [[MyClass alloc] init];
        unsigned int outCount = 0;
 
        Class cls = myClass.class;
 
        // 类名
        NSLog(@"class name: %s", class_getName(cls));
 
        NSLog(@"==========================================================");
 
        // 父类
        NSLog(@"super class name: %s", class_getName(class_getSuperclass(cls)));
        NSLog(@"==========================================================");
 
        // 是否是元类
        NSLog(@"MyClass is %@ a meta-class", (class_isMetaClass(cls) ? @"" : @"not"));
        NSLog(@"==========================================================");
 
        Class meta_class = objc_getMetaClass(class_getName(cls));
        NSLog(@"%s's meta-class is %s", class_getName(cls), class_getName(meta_class));
        NSLog(@"==========================================================");
 
        // 变量实例大小
        NSLog(@"instance size: %zu", class_getInstanceSize(cls));
        NSLog(@"==========================================================");
 
        // 成员变量
        Ivar *ivars = class_copyIvarList(cls, &outCount);
        for (int i = 0; i < outCount; 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(@"instace variable %s", ivar_getName(string));
        }
 
        NSLog(@"==========================================================");
 
        // 属性操作
        objc_property_t * properties = class_copyPropertyList(cls, &outCount);
        for (int i = 0; i < outCount; i++) {
            objc_property_t property = properties[i];
            NSLog(@"property'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));
        }
 
        NSLog(@"==========================================================");
 
        // 方法操作
        Method *methods = class_copyMethodList(cls, &outCount);
        for (int i = 0; i < outCount; i++) {
            Method method = methods[i];
            NSLog(@"method's signature: %s", method_getName(method));
        }
 
        free(methods);
 
        Method method1 = class_getInstanceMethod(cls, @selector(method1));
        if (method1 != NULL) {
            NSLog(@"method %s", method_getName(method1));
        }
 
        Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
        if (classMethod != NULL) {
            NSLog(@"class method : %s", method_getName(classMethod));
        }
 
        NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(cls, @selector(method3WithArg1:arg2:)) ? @"" : @" not");
 
        IMP imp = class_getMethodImplementation(cls, @selector(method1));
        imp();
 
        NSLog(@"==========================================================");
 
        // 协议
        Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &outCount);
        Protocol * protocol;
        for (int i = 0; i < outCount; i++) {
            protocol = protocols[i];
            NSLog(@"protocol name: %s", protocol_getName(protocol));
        }
 
        NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"" : @" not", protocol_getName(protocol));
 
        NSLog(@"==========================================================");
    }
    return 0;
}


动态创建类和对象

动态创建类:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)];

动态创建对象:


1
2
3
4
5
6
7
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
 
NSLog(@"%@", [str1 class]);
 
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);

实例操作函数:

  • 针对整个对象进行操作的函数

    1
    2
    3
    4
    NSObject *a = [[NSObject alloc] init];
    id newB = object_copy(a, class_getInstanceSize(MyClass.class));
    object_setClass(newB, MyClass.class);
    object_dispose(a);
  • 针对对象实例变量进行操作的函数

  • 针对对象的类进行操作的函数

获取类定义:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}

方法汇总:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// 获取类的类名
const char * class_getName ( Class cls );

// 获取类的父类
Class class_getSuperclass ( Class cls );
 
// 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );

// 获取实例大小
size_t class_getInstanceSize ( Class cls );
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
 
// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
 
// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
 
// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );

// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );
 
// 获取属性列表
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 );
 
// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

//垃圾回收器扫描
const uint8_t * class_getIvarLayout ( Class cls );

void class_setIvarLayout ( Class cls, const uint8_t *layout );

const uint8_t * class_getWeakIvarLayout ( Class cls );

void class_setWeakIvarLayout ( Class cls, const uint8_t *layout );

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
 
// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );
 
// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );
 
// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
 
// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
 
// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
 
// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );
 
// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
 
// 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

// 获取版本号
int class_getVersion ( Class cls );
 
// 设置版本号
void class_setVersion ( Class cls, int version );

// 创建一个新类和元类
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
 
// 销毁一个类及其相关联的类
void objc_disposeClassPair ( Class cls );
 
// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair ( Class cls );

// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );
 
// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
 
// 销毁类实例
void * objc_destructInstance ( id obj );

// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );
 
// 释放指定对象占用的内存
id object_dispose ( id obj );

// 修改类实例的实例变量的值
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 );

// 返回给定对象的类名
const char * object_getClassName ( id obj );
 
// 返回对象的类
Class object_getClass ( id obj );
 
// 设置对象的类
Class object_setClass ( id obj, Class cls );

// 获取已注册的类定义的列表
int objc_getClassList ( Class *buffer, int bufferCount );
 
// 创建并返回一个指向所有已注册类的指针列表
Class * objc_copyClassList ( unsigned int *outCount );
 
// 返回指定类的类定义
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );
 
// 返回指定类的元类
Class objc_getMetaClass ( const char *name );

Objective-C运行时的一些黑魔法-Method Swizzling

发表于 2016-06-14   |   分类于 ”iOS”   |       阅读次数

Method swizzling

Method swizzling指的是改变一个已存在的选择器对应的实现的过程,它依赖于Objectvie-C中方法的调用能够在运行时进改变——通过改变类的调度表(dispatch table)中选择器到最终函数间的映射关系。

举个例子,假设我们想跟踪在一个iOS应用中每个视图控制器展现给用户的次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#import <objc/runtime.h>
 
@implementation UIViewController (Tracking)
 
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];

SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
 
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
 
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
 
#pragma mark - Method Swizzling
 
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
} 
 
@end

在计算机学科中,指针变换(pointer swizzling)是指将基于名字或位置的引用转变为直接的指针引用。 然而在Objective-C中,这个词的起源并不完全知道,但关于这一借鉴其实也很好理解,method swizzling可以通过选择器来改变它引用的函数指针。

向视图控制器的生命周期中注入操作、事件的响应、视图的绘制,或Foundation中的网络堆栈都是能够利用method swizzling产生明显效果的场景。还有一些其他的场景使用swizzling会是一个合适的选择,这随着Objective-C开发者经验不断丰富会变得越来越明显。

先不说为什么和在哪些地方使用swizzling,来看一下应该怎样实现:

+load vs. +initialize


Swizzling应该在+load方法中实现。
每个类的这两个方法会被Objective-C运行时系统自动调用,+load是在一个类最开始加载时调用,+initialize是在应用中第一次调用该类或它的实例的方式之前调用。这两个方法都是可选的,只有实现了才会被执行。

因为method swizzling会影响全局,所以减少冒险情况就很重要。+load能够保证在类初始化的时候就会被加载,这为改变系统行为提供了一些统一性。但+initialize并不能保证在什么时候被调用——事实上也有可能永远也不会被调用,例如应用程序从未直接的给该类发送消息。

dispatch_once


Swizzling应该在dispatch_once中实现。

还是因为swizzling会改变全局,我们需要在运行时采取所有可用的防范措施。保障原子性就是一个措施,它确保代码即使在多线程环境下也只会被执行一次。GCD中的diapatch_once就提供这些保障,它应该被当做swizzling的标准实践。

选择器、方法及实现


在Objective-C中,尽管这些词经常被放在一起来描述消息传递的过程,但选择器、方法及实现分别代表运行时的不同方面。

下面是苹果Objective-C Runtime Reference文档中对它们的描述:

  • 选择器(typedef struct objc_selector *SEL):选择器用于表示一个方法在运行时的名字,一个方法的选择器是一个注册到(或映射到)Objective-C运行时中的C字符串,它是由编译器生成并在类加载的时候被运行时系统自动映射。

  • 方法(typedef struct objc_method *Method):一个代表类定义中一个方法的不明类型。

  • 实现(typedef id (*IMP)(id, SEL, …)):这种数据类型是实现某个方法的函数开始位置的指针,函数使用的是基于当前CPU架构的标准C调用规约。第一个参数是指向self的指针(也就是该类的某个实例的内存空间,或者对于类方法来说,是指向元类(metaclass)的指针)。第二个参数是方法的选择器,后面跟的都是参数。

理解这些概念之间关系最好的方式是:一个类(Class)维护一张调度表(dispatch table)用于解析运行时发送的消息;调度表中的每个实体(entry)都是一个方法(Method),其中key值是一个唯一的名字——选择器(SEL),它对应到一个实现(IMP)——实际上就是指向标准C函数的指针。

Method Swizzling就是改变类的调度表让消息解析时从一个选择器对应到另外一个的实现,同时将原始的方法实现混淆到一个新的选择器。

调用_cmd


下面这段代码看起来像是会导致一个死循环:
1
2
3
4
- (void)xxx_viewWillAppear:(BOOL)animated { 
    [self xxx_viewWillAppear:animated]; 
    NSLog(@"viewWillAppear: %@", NSStringFromClass([self class])); 
}

但其实并没有,在Swizzling的过程中,xxx_viewWillAppear:会被重新分配给UIViewController的-viewWillAppear:的原始实现。一个优秀程序员应有的直觉会告诉你在一个方法的实现中通过self调用当前方法自身会产生错误,但是在当前这种情况下,如果我们记住到底是怎么回事更有意义。反而,如果我们在这个方法中调用viewWillAppear:才会真的导致死循环,因为这个方法的实现会在运行时被swizzle到viewWillAppear:的选择器。

记住给swizzled方法加上前缀,这和你需要给可能产生冲突的分类方法加前缀是一个道理。

注意事项


Swizzling被普遍认为是一种巫术,容易导致不可预料的行为和结果。尽管不是最安全的,但是如果你采取下面这些措施,method swizzling还是很安全的。

  • 始终调用方法的原始实现(除非你有足够的理由不这么做): API为输入和输出提供规约,但它里面具体的实现其实是个黑匣子,在Method Swizzling过程中不调用它原始的实现可能会破坏一些私有状态,甚至是程序的其他部分。

  • 避免冲突:给分类方法加前缀,一定要确保不要让你代码库中其他代码(或是依赖库)在做与你相同的事。

  • 理解:只是简单的复制粘贴swizzling代码而不去理解它是怎么运行的,这不仅非常危险,而且还浪费了学习Objective-C运行时的机会。阅读 Objective-C Runtime Reference 和 去理解代码是怎样和为什么这样执行的,努力的用你的理解来消灭你的疑惑。

谨慎行事:不管你多么自信你能够swizzling Foundation、UIKit 或者其他内置框架,请记住所有这些都可能在下一个版本中就不好使。提前做好准备,防范于未然才不至于到时候焦头烂额。

不敢放心大胆的直接使用Objective-C运行时?Jonathan ‘Wolf’ Rentzsch提供了经过实战检验的、支持CocoaPads的库JRSwizzle,它会为你考虑好了一切。

与associated objects一样,method swizzling是一个强大的技术,但是你也应该谨慎使用。

本文参考NSHipster的文章Method Swizzling


关于iOS加解密编解码那些事

发表于 2016-06-01   |   分类于 “iOS”   |       阅读次数

编解码

base64编解码


Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一。Base64编码可用于在HTTP环境下传递较长的标识信息。在应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。
推荐GTMBase64


加解密

MD5数字摘要


MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。
MD5算法具有以下特点:
1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2、容易计算:从原数据计算出MD5值很容易。
3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4、强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被”压缩”成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串)。除了MD5以外,其中比较有名的还有sha-1、RIPEMD以及Haval等。

基本原理


对MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
1
2
3
4
5
6
7
8
9
10
11
12
- (NSString *)md5 {

const char *cStr = [self UTF8String];
unsigned char result[16];
CC_MD5( cStr, strlen(cStr), result );
return [NSString stringWithFormat:@%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X,
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}


SHA1摘要


安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准 (Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64 位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。 SHA1有如下特性:不可以从消息摘要中复原信息;两个不同的消息不会产生同样的消息摘要,(但会有1x10 ^ 48分之一的机率出现相同的消息摘要,一般使用时忽略)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (NSString*) sha1
{
    const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
    NSData *data = [NSData dataWithBytes:cstr length:self.length];
  
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
  
    CC_SHA1(data.bytes, data.length, digest);
  
    NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
  
    for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
        [output appendFormat:@%02x, digest[i]];
  
    return output;
}


SHA1与base64结合


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (NSString *) sha1_base64
{
    const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
    NSData *data = [NSData dataWithBytes:cstr length:self.length];
  
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
  
    CC_SHA1(data.bytes, data.length, digest);
  
    NSData * base64 = [[NSData alloc]initWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
    base64 = [GTMBase64 encodeData:base64];
  
    NSString * output = [[NSString alloc] initWithData:base64 encoding:NSUTF8StringEncoding];
    return output;
}


MD5与base64结合


1
2
3
4
5
6
7
8
9
10
11
12
- (NSString *) md5_base64
{
    const char *cStr = [self UTF8String];
    unsigned char digest[CC_MD5_DIGEST_LENGTH];
    CC_MD5( cStr, strlen(cStr), digest );
  
    NSData * base64 = [[NSData alloc]initWithBytes:digest length:CC_MD5_DIGEST_LENGTH];
    base64 = [GTMBase64 encodeData:base64];
  
    NSString * output = [[NSString alloc] initWithData:base64 encoding:NSUTF8StringEncoding];
    return output;
}

AES加解密


高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。
推荐RNCryptor


DES加解密


DES是一种分组数据加密技术(先将数据分成固定长度的小数据块,之后进行加密),速度较快,适用于大量数据加密
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*字符串加密
 *参数
 *plainText : 加密明文
 *key        : 密钥 64位
 */
+ (NSString *) encryptUseDES:(NSString *)plainText key:(NSString *)key
{
    NSString *ciphertext = nil;
    const char *textBytes = [plainText UTF8String];
    NSUInteger dataLength = [plainText length];
    unsigned char buffer[1024];
    memset(buffer, 0, sizeof(char));
    Byte iv[] = {1,2,3,4,5,6,7,8};
    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmDES,
                                          kCCOptionPKCS7Padding,
                                          [key UTF8String], kCCKeySizeDES,
                                          iv,
                                          textBytes, dataLength,
                                          buffer, 1024,
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
        
        ciphertext = [[[NSString alloc] initWithData:[GTMBase64 encodeData:data] encoding:NSUTF8StringEncoding] autorelease];
    }
    return ciphertext;
}
 
//解密
+ (NSString *) decryptUseDES:(NSString*)cipherText key:(NSString*)key 
{
    NSData* cipherData = [GTMBase64 decodeString:cipherText];
    unsigned char buffer[1024];
    memset(buffer, 0, sizeof(char));
    size_t numBytesDecrypted = 0;
    Byte iv[] = {1,2,3,4,5,6,7,8};
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, 
                                          kCCAlgorithmDES, 
                                          kCCOptionPKCS7Padding, 
                                          [key UTF8String], 
                                          kCCKeySizeDES, 
                                          iv, 
                                          [cipherData bytes], 
                                          [cipherData length], 
                                          buffer, 
                                          1024, 
                                          &numBytesDecrypted);
    NSString* plainText = nil;
    if (cryptStatus == kCCSuccess) {
        NSData* data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesDecrypted];
        plainText = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
    }
    return plainText;
}


3DES加解密


3DES是一种基于DES的加密算法,使用3个不同密匙对同一个分组数据块进行3次加密,如此以使得密文强度更高。
相较于DES和3DES算法而言,AES算法有着更高的速度和资源使用效率,安全级别也较之更高了,被称为下一代加密标准。
相对于DES,新增核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//前端与后台商量KEY
#define gkey @""
#define gIv @""

+ (NSString*)TripleDES:(NSString*)plainText encryptOrDecrypt:(CCOperation)encryptOrDecrypt key:(NSString*)key {


const void *vplainText;
size_t plainTextBufferSize;

if (encryptOrDecrypt == kCCDecrypt)
{
NSData *EncryptData = [GTMBase64 decodeData:[plainText dataUsingEncoding:NSUTF8StringEncoding]];
plainTextBufferSize = [EncryptData length];
vplainText = [EncryptData bytes];
}
else
{
NSData* data = [plainText dataUsingEncoding:NSUTF8StringEncoding];
plainTextBufferSize = [data length];
vplainText = (const void *)[data bytes];
}

CCCryptorStatus ccStatus;
uint8_t *bufferPtr = NULL;
size_t bufferPtrSize = 0;
size_t movedBytes = 0;
// uint8_t ivkCCBlockSize3DES;

bufferPtrSize = (plainTextBufferSize + kCCBlockSize3DES) & ~(kCCBlockSize3DES - 1);
bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t));
memset((void *)bufferPtr, 0x0, bufferPtrSize);
// memset((void *) iv, 0x0, (size_t) sizeof(iv));

NSString *initVec = @"";
const void *vkey = (const void *) [key UTF8String];
const void *vinitVec = (const void *) [initVec UTF8String];

ccStatus = CCCrypt(encryptOrDecrypt,
kCCAlgorithm3DES,
kCCOptionPKCS7Padding|kCCOptionECBMode,
vkey, //key
kCCKeySize3DES,
vinitVec, //"init Vec", //iv,
vplainText, //"Your Name", //plainText,
plainTextBufferSize,
(void *)bufferPtr,
bufferPtrSize,
&movedBytes);
NSString *result;

if (encryptOrDecrypt == kCCDecrypt)
{
result = [[NSString alloc] initWithData:[NSData dataWithBytes:(const void *)bufferPtr
length:(NSUInteger)movedBytes]
encoding:NSUTF8StringEncoding];
}
else
{
NSData *myData = [NSData dataWithBytes:(const void *)bufferPtr length:(NSUInteger)movedBytes];
result = [GTMBase64 stringByEncodingData:myData];
}

return result;

}


RSA加密


RSA算法是一种非对称加密算法,常被用于加密数据传输.如果配合上数字摘要算法, 也可以用于文件签名.

基本原理


RSA使用”秘匙对”对数据进行加密解密.在加密解密数据前,需要先生成公钥(public key)和私钥(private key).

  • 公钥(public key):用于加密数据. 用于公开, 一般存放在数据提供方, 例如iOS客户端.
  • 私钥(private key):用于解密数据.必须保密,私钥泄露会造成安全问题.
    iOS中的Security.framework提供了对RSA算法的支持.这种方式需要对密匙对进行处理,根据public key生成证书,通过private key生成p12格式的密匙
    除了Security.framework, 也可以将openssl库编译到iOS工程中, 这可以提供更灵活的使用方式.
    推荐Objective-C-RSA
Alan_Sim

Alan_Sim

心魔,一辈子的敌人

5 日志
2 分类
3 标签
RSS
GitHub CNBlogs 知乎
Links
  • 百度
© 2013 - 2016 Alan_Sim
由 Hexo 强力驱动
主题 - NexT.Pisces