IMP指针和ObjcSendMsg

下边是我整理的一些文档编辑的,那么第一次看可能很生涩难懂,看的时候不要着急,先理解IMP的含义,然后就是C的一些基础一定要知道,这样后面的更容易理解,好了闲话少说

1.那么什么是IMP呢?

其实,还有一种更加简单的方法可以让我们办到相同的目的,运用IMP指针,IMP就是Implementation的缩写,顾名思义,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP,所以,我们可以直接调用方法的IMP指针,来避免方法调用死循环的问题。

调用一个IMP的方式和调用普通C函数相同,比如:


1

id returnObjc = someIMP(objc,SEL,params...);

不过如果你的项目没有做其他配置的话这样调用编译器是不会通过的,我们来看一下先它的定义:


1

2

3

4

5

if !OBJC_OLD_DISPATCH_PROTOTYPES

typedef void (*IMP)(void /* id, SEL, ... */ );

else

typedef id (*IMP)(id, SEL, ...);

endif

在默认情况下你的工程是打开这个配置的

这种情况下IMP被定义为无参数无返回值的函数。所以你需要到工程里搜索到这个选项并把它关闭。这样的麻烦就是,每次使用,你都需要修改工程配置,所以这里我再介绍另外一种办法:重新定义一个和有参数的IMP指针相同的指针类型,在获取IMP时把它强转为此类型。这样运用IMP指针后,就不需要额外的给ViewController写新的方法:

还有一个地方我们需要注意,如果这样直接调用IMP的话就会发生经典的EXC_BAD_ACCESS错误,我们定义的IMP指针是一个有返回值的类型,而其实我们获取的viewDidLoad这个方法是没有返回值的,所以我们需要新定义一个和IMP相同类型的函数指针比如VIMP,把他的返回值定位Void,这样如果你修改的方法有返回值就用IMP,没有返回值就用VIMP。

值得注意的是,如果你重写的方法有返回值,不要忘记在最后做return。

在objective c中,如果细心的话会发现,每个类中都会自动生成一个class 类型的isa,

[plain] view
plain
 copy

  1. @interface NSObject <NSObject> {
  2. Class    isa;
  3. }

isa是什么,class又是什么呢,找到Class的定义我们会发现如下:

[plain] view
plain
 copy

  1. typedefstruct objc_class *Class;

而objc_class以前的定义又如下,现在据说被封闭了,不知道有没有再作修改,总之方便我们理解就好:

[plain] view
plain
 copy

  1. struct objc_class {
  2. Class isa;
  3. Class super_class;
  4. const char *name;
  5. long version;
  6. long info;
  7. long instance_size;
  8. struct objc_ivar_list *ivars;
  9. struct objc_method_list **methodLists;
  10. struct objc_cache *cache;
  11. struct objc_protocol_list *protocols;
  12. }

于是我们就有了点头绪了,isa,is a pointer,是个指针(根据网上的资料,这样理解是最贴近事实的,不管你们信不信,反正我是信了),每个类都有一个class类型的指针isa,继承自NSObject中,继承关系,方法变量等信息都存放在isa中,isa作为一个隐藏的属性,会自动生成于每个类之中。有了这个前提,也就可以解释为什么我们可以根据@class来代替任意一个类了,看代码:

Human.h

[plain] view
plain
 copy

  1. #import <Foundation/Foundation.h>
  2. @interface Human : NSObject
  3. -(void)say;
  4. @end

Human.m

[plain] view
plain
 copy

  1. #import "Human.h"
  2. @implementation Human
  3. -(void)say
  4. {
  5. NSLog(@"Human中的say方法");
  6. }
  7. @end

main.h

[plain] view
plain
 copy

  1. #import <Foundation/Foundation.h>
  2. #import "Human.h"
  3. int main(int argc, const char * argv[])
  4. {
  5. @autoreleasepool {
  6. Class c =NSClassFromString(@"Human");
  7. [[c new] say];
  8. //以上CLASS类型的c,就相当于Human类。
  9. }
  10. return 0;
  11. }

class可以灵活的代替别的类,SEL与其类似,不同的是SEL代替的是方法,可以方便的代替其他方法,class中是因为有isa属性保存有类的信息,而SEL是因为即使是在不同的类中,方法名只要相同,这两个方法的ID就相同,SEL就是根据这个ID来找到该方法,再根据调用该方法的类的不同来找到唯一的地址。看代码再作解释:

[plain] view
plain
 copy

  1. #import <Foundation/Foundation.h>
  2. #import <Foundation/Foundation.h>
  3. @interface Human : NSObject
  4. -(void)say;
  5. @end
  6. @implementation Human
  7. -(void)say
  8. {
  9. NSLog(@"Human中的say方法");
  10. }
  11. @end
  12. //上面定义了一个human类,里面有一个say方法
  13. @interface man:NSObject
  14. {}
  15. -(void)say; @end
  16. @implementation man
  17. -(void)say
  18. {
  19. NSLog(@"man中的say方法");
  20. }
  21. @end
  22. //在上面定义了一个man类,同样有一个say方法
  23. int main(int argc, const char * argv[])
  24. {
  25. @autoreleasepool {
  26. Class a =NSClassFromString(@"Human");
  27. Class b =NSClassFromString(@"man");
  28. //根据方法名say找到该方法的id,将sel与其绑定;
  29. SEL sel = NSSelectorFromString(@"say");
  30. [[a new] performSelector:sel];
  31. [[b new] performSelector:sel];
  32. }
  33. return 0;
  34. }

结果如下:

[plain] view
plain
 copy

  1. 2012-03-13 10:13:24.900 String[2725:403] Human中的say方法
  2. 2012-03-13 10:13:24.901 String[2725:403] man中的say方法

通过以上代码我们会发现,SEL通过方法名绑定后,可以被多个类实例调用,找了些网上的资料,解释都是说方法名一样的话,ID会一样,地址仍不同,才会实现这样的效果,我们不谈论是否准确,但我个人认为这是目前最合理的解释。这种用法的优势一方面是灵活性更高,类似于多态,另一方面是,这种用法sel找方法时匹配的是ID而不是字符串方法名,所以在效率上会高一些。还有一种更终极的方法,直接对应方法的地址,这种方法效率最高,请看代码:

[plain] view
plain
 copy

  1. #import <Foundation/Foundation.h>
  2. #import <Foundation/Foundation.h>
  3. @interface Human : NSObject
  4. -(void)say;
  5. @end
  6. @implementation Human
  7. -(void)say
  8. {
  9. NSLog(@"Human中的say方法");
  10. }
  11. @end
  12. //上面定义了一个human类,里面有一个say方法
  13. @interface man:NSObject
  14. {}
  15. -(void)say; @end
  16. @implementation man
  17. -(void)say
  18. {
  19. NSLog(@"man中的say方法");
  20. }
  21. @end
  22. //在上面定义了一个man类,同样有一个say方法
  23. int main(int argc, const char * argv[])
  24. {
  25. @autoreleasepool {
  26. Human *human =[Human new];
  27. man *ma=[man new];
  28. //根据方法名say找到该方法的id,将sel与其绑定;
  29. SEL sel [email protected](say);//也可以这样写:SEL sel=NSSelectorFromString(@"say");
  30. IMP imp1 = [human methodForSelector:sel];
  31. IMP imp2 = [ma methodForSelector:sel];
  32. imp1(human,sel);
  33. imp2(ma,sel);
  34. //因为每个方法都有自己的地址,这种方式直接找到地址区分相同ID的方法,效率最高,但灵活性不如SEL方式。
  35. }
  36. return 0;
  37. }

输出语句:

[plain] view
plain
 copy

  1. 2012-03-13 10:35:21.446 String[3763:403] Human中的say方法
  2. 2012-03-13 10:35:21.450 String[3763:403] man中的say方法

今天这些内容不太好理解,我用自己理解的方式给大家再解释一遍,class用于代替类,增加灵活性,因为我们不知道什么时候会用到什么类,方法也是如此,所以SEL可以代替方法,每个方法有方法名,ID,地址,相同的方法名,ID也一样,正常情况下我们根据方法名找到方法,用SEL方法可以根据ID找到方法,而用IMP方式可以直接找到地址,但是灵活性不如SEL方法,虽然效率最高。

2.下面介绍下从C的伪代码到汇编,如何用IMP实现objc_msgSend

当你写了一个发送 Objective-C 消息的方法:


1

[obj message]

编译器会生成一个 objc_msgSend 调用:


1

objc_msgSend(obj, @selector(message));

之后 objc_msgSend 会负责转发这个消息。

它都做了什么?它会查找合适的函数指针或者 IMP,然后调用,最后跳转。任何传给 objc_msgSend 的参数,最终都会成为 IMP 的参数。 IMP 的返回值成为了最开始被调用的方法的返回值。

因为 objcmsgSend 只是负责接收参数,找到合适的函数指针,然后跳转,有时管这种叫做 trampoline(蹦床). 更通用的来说,任何一段负责把一段代码转发到另一处的代码,都可以被叫做 trampoline。

这种转发的行为使 objc_msgSend 变得特殊起来。因为它只是简单的查找合适的代码,然后直接跳转过去,这相当的通用。传入任何参数组合都可以,因为它只是把这些参数留给 IMP 去读取。返回值有些棘手,但最终都可以看成 objc_msgSend 的不同变种。

不幸的是,这些转发行为都不能用纯 C 实现。因为没有方法可以将传入 C 函数的泛参(generic parameters)传给另一个函数。 你可以使用变参,但是变参和普通参数的传递方法不同,而且慢,所以这不适合普通的 C 参数。

如果要用 C 来实现 objc_msgSend,基本样子应该像这样:


1

2

3

4

5

6

id objc_msgSend(id self, SEL _cmd, ...)

{

Class c = object_getClass(self);

IMP imp = class_getMethodImplementation(c, _cmd);

return imp(self, _cmd, ...);

}

这有点过于简单。事实上会有一个方法缓存来提升查找速度,像这样:


1

2

3

4

5

6

7

8

id objc_msgSend(id self, SEL _cmd, ...)

{

Class c = object_getClass(self);

IMP imp = cache_lookup(c, _cmd);

if(!imp)

imp = class_getMethodImplementation(c, _cmd);

return imp(self, _cmd, ...);

}

通常为了速度,cache_lookup 使用 inline 函数实现。

汇编

在 Apple 版的 runtime 中,为了最大化速度,整个函数是使用汇编实现的。在 Objective-C 中每次发送消息都会调用 objc_msgSend,在一个应用中最简单的动作都会有成千或者上百万的消息。

为了让事情更简单,我自己的实现中会尽可能少的使用汇编,使用独立的 C 函数抽象复杂度。汇编代码会实现下面的功能:


1

2

3

4

5

id objc_msgSend(id self, SEL _cmd, ...)

{

IMP imp = GetImplementation(self, _cmd);

imp(self, _cmd, ...);

}

GetImplementation 可以用更可读的方式工作。

汇编代码需要:

1. 把所有潜在的参数存储在安全的地方,确保 GetImplementation 不会覆盖它们。

2. 调用 GetImplementation。

3. 把返回值保存在某处。

4. 恢复所有的参数值。

5. 跳转到 GetImplementation 返回的 IMP。

让我们开始吧!

这里我会尝试使用 x86-64 汇编,这样可以很方便的在 Mac 上工作。这些概念也可以应用于 i386 或者 ARM。

这个函数会保存在独立的文件中,叫做 msgsend-asm.s。这个文件可以像源文件那样传递给编译器,然后会被编译并链接到程序中。

第一件事要做的是声明全局的符号(global symbol)。因为一些无聊的历史原因,C 函数的 global symbol 会在名字前有个下划线:


1

2

.globl _objc_msgSend

_objc_msgSend:

编译器会很高兴的链接最近可使用的(nearest available) objc_msgSend。简单的链接这个到一个测试 app 已经可以让 [obj message] 表达式使用我们自己的代码而不是苹果的 runtime,这样可以相当方便的测试我们的代码确保它可以工作。

整型数和指针参数会被传入寄存器 %rsi, %rdi, %rdx, %rcx, %r8 和 %r9。其他类型的参数会被传进栈(stack)中。这个函数最先做的事情是把这六个寄存器中的值保存在栈中,这样它们可以在之后被恢复:


1

2

3

4

5

6

pushq %rsi

pushq %rdi

pushq %rdx

pushq %rcx

pushq %r8

pushq %r9

除了这些寄存器,寄存器 %rax 扮演了一个隐藏的参数。它用于变参的调用,并保存传入的向量寄存器(vector registers)的数量,用于被调用的函数可以正确的准备变参列表。以防目标函数是个变参的方法,我同样也保存了这个寄存器中的值:


1

pushq %rax

为了完整性,用于传入浮点类型参数的寄存器 %xmm 也应该被保存。但是,要是我能确保 GetImplementation 不会传入任何的浮点数,我就可以忽略掉它们,这样我就可以让代码更简洁。

接着,对齐栈。 Mac OS X 要求一个函数调用栈需要对齐 16 字节边界。上面的代码已经是栈对齐的,但是还是需要显式手动处理下,这样可以确保所有都是对齐的,就不用担心动态调用函数时会崩溃。要对齐栈,在保存 %r12 的原始值到栈中后,我把当前的栈指针保存到了 %r12 中。%r12 是随便选的,任何保存的调用者寄存器(caller-saved register)都可以。重要的是在调用完 GetImplementation
后这些值仍然存在。然后我把栈指针按位与(and)上 -0x10,这样可以清除栈底的四位:


1

2

3

pushq %r12

mov %rsp, %r12

andq $-0x10, %rsp

现在栈指针是对齐的了。这样可以安全的避开上面(above)保存的寄存器,因为栈是向下增长的,这种对齐的方法会让它更向下(move it further down)。

是时候该调用 GetImplementation 了。它接收两个参数,self 和 _cmd。 调用习惯是把这两个参数分别保存到 %rsi 和 %rdi 中。然而传入 objc_msgSend 中时就是那样了,它们没有被移动过,所以不需要改变它们。所有要做的事情实际上是调用 GetImplementation,方法名前面也要有一个下划线:


1

callq _GetImplementation

整型数和指针类型的返回值保存在 %rax 中,这就是找到返回的 IMP 的地方。因为 %rax 需要被恢复到初始的状态,返回的 IMP 需要被移动到别的地方。我随便选了个 %r11。


1

mov %rax, %r11

现在是时候该恢复原样了。首先要恢复之前保存在 %r12 中的栈指针,然后恢复旧的 %r12 的值:


1

2

mov %r12, %rsp

popq %r12

然后按压入栈的相反顺序恢复寄存器的值:


1

2

3

4

5

6

7

popq %rax

popq %r9

popq %r8

popq %rcx

popq %rdx

popq %rdi

popq %rsi

现在一切都已经准备好了。参数寄存器(argument registers)都恢复到了之前的样子。目标函数需要的参数都在合适的位置了。 IMP 在寄存器 %r11 中,现在要做的是跳转到那里:


1

jmp *%r11

就这样!不需要其他的汇编代码了。jump 把控制权交给了方法实现。从代码的角度看,就好像发送消息者直接调用的这个方法。之前的那些迂回的调用方法都消失了。当方法返回,它会直接放回到 objc_msgSend 的调用处,不需要其他的操作。这个方法的返回值可以在合适的地方找到。

非常规的返回值有一些细节需要注意。比如大的结构体(不能用一个寄存器大小保存的返回值)。在 x86-64,大的结构体使用隐藏的第一个参数返回。当你像这样调用:


1

NSRect r = SomeFunc(a, b, c);

这个调用会被翻译成这样:


1

2

NSRect r;

SomeFunc(&r, a, b, c);

用于返回值的内存地址被传入到 %rdi 中。因为 objc_msgSend 期望 %rdi 和 %rsi 中包含 self 和 _cmd,当一个消息返回大的结构体时不会起作用的。同样的问题存在于多个不同平台上。runtime 提供了 objc_msgSend_stret 用于返回结构体,工作原理和 objc_msgSend 类似,只是知道在 %rsi 中寻找 self 和在 %rdx 中寻找 _cmd。

相似的问题发生在一些平台上发送消息(messages)返回浮点类型值。在这些平台上,runtime 提供了 objc_msgSend_fpret(在 x86-64,objc_msgSend_fpret2 用于特别极端的情况)。

方法查找

让我们继续实现 GetImplementation。上面的汇编蹦床意味着这些代码可以用 C 实现。记得吗,在真正的 runtime 中,这些代码都是直接用汇编写的,是为了尽可能的保证最快的速度。这样不仅可以更好的控制代码,也可以避免重复像上面那样保存并恢复寄存器的代码。

GetImplementation 可以简单的调用 class_getMethodImplementation 实现,混入 Objective-C runtime 的实现。这有点无聊。真正的 objc_msgSend 为了最大化速度首先会查找类的方法缓存。因为 GetImplementation 想模仿 objc_msgSend,所以它也会这么做。要是缓存中不包含给定的 selector 入口点(entry),它会继续查找
runtime(it fall back to querying the runtime)。

我们现在需要的是一些结构体定义。方法缓存是类(class)结构体中的私有结构体,为了得到它我们需要定义自己的版本。尽管是私有的,这些结构体的定义还是可以通过苹果的 Objective-C runtime 开源实现获得。

首先需要定义一个 cache entry:


1

2

3

4

5

typedef struct {

SEL name;

void *unused;

IMP imp;

} cache_entry;

相当简单。别问我 unused 字段是干什么的,我也不知道它为什么在那。这是 cache 的全部定义:


1

2

3

4

5

struct objc_cache {

uintptr_t mask;

uintptr_t occupied;

cache_entry *buckets[1];

};

缓存使用 hash table(哈希表)实现。实现这个表是为了速度的考虑,其他无关的都简化了,所以它有点不一样。表的大小永远都是 2 的幂。表格使用 selector 做索引,bucket 是直接使用 selector 的值做索引,可能会通过移位去除不相关的低位(low bits),并与 mask 执行一个逻辑与(logical and)。下面是一些宏,用于给定 selector 和 mask 时计算 bucket 的索引:


1

2

3

4

5

#ifndef __LP64__

# define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))

#else

# define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>0)) & (mask))

#endif

最后是类的结构体。 这是 Class 指向的类型:


1

2

3

4

5

6

struct class_t {

struct class_t *isa;

struct class_t *superclass;

struct objc_cache *cache;

IMP *vtable;

};

需要的结构体都已经有了,现在开始实现 GetImplementation 吧:


1

2

IMP GetImplementation(id self, SEL _cmd)

{

首先要做的是获取对象的类。真正的 objc_msgSend 通过类似 self->isa 的方式获取,但是它会使用官方的 API 实现:


1

Class c = object_getClass(self);

因为我想访问最原始的形式,我会为指向 class_t 结构体的指针执行类型转换:


1

struct class_t *classInternals = (struct class_t *)c;

现在该查找 IMP 了。首先我们把它初始为 NULL。如果我们在缓存中找到,我们会赋值为它。如果查找缓存后仍为 NULL,我们会回退到速度较慢的方法:


1

IMP imp = NULL;

接着,获取指向 cache 的指针:


1

struct objc_cache *cache = classInternals->cache;

计算 bucket 的索引,获取指向 buckets 数组的指针:


1

2

uintptr_t index = CACHE_HASH(_cmd, cache->mask);

cache_entry **buckets = cache->buckets;

然后,我们使用要找的 selector 查找缓存。runtime 使用的是线性链(linear chaining),之后只是遍历 buckets 子集直到找到需要的 entry 或者 NULL entry:


1

2

3

4

5

6

7

8

for(; buckets[index] != NULL; index = (index + 1) & cache->mask)

{

if(buckets[index]->name == _cmd)

{

imp = buckets[index]->imp;

break;

}

}

如果没有找到 entry,我们会调用 runtime 使用一种较慢的方法。在真正的 objc_msgSend 中,上面的所有代码都是使用汇编实现的,这时候就该离开汇编代码调用 runtime 自己的方法了。一旦查找缓存后没有找到需要的 entry,期望快速发送消息的希望就要落空了。这时候获取更快的速度就没那么重要了,因为已经注定会变慢,在一定程度上也极少的需要这么调用。因为这点,放弃汇编代码转而使用更可维护的 C 也是可以接受的:


1

2

if(imp == NULL)

imp = class_getMethodImplementation(c, _cmd);

不管怎样,IMP 现在已经获取到了。如果它在缓存中,就会在那里找到它,否则它会通过 runtime 查找到。class_getMethodImplementation 调用同样会使用缓存,所以下次调用会更快。剩下的就是返回 IMP:


1

2

return 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

39

40

41

42

43

44

45

46

47

@interface Test : NSObject

- (void)none;

- (void)param: (int)x;

- (void)params: (int)a : (int)b : (int)c : (int)d : (int)e : (int)f : (int)g;

- (int)retval;

@end

@implementation Test

- (id)init

{

fprintf(stderr, "in init method, self is %p\n", self);

return self;

}

- (void)none

{

fprintf(stderr, "in none method\n");

}

- (void)param: (int)x

{

fprintf(stderr, "got parameter %d\n", x);

}

- (void)params: (int)a : (int)b : (int)c : (int)d : (int)e : (int)f : (int)g

{

fprintf(stderr, "got params %d %d %d %d %d %d %d\n", a, b, c, d, e, f, g);

}

- (int)retval

{

fprintf(stderr, "in retval method\n");

return 42;

}

@end

int main(int argc, char **argv)

{

for(int i = 0; i < 20; i++)

{

Test *t = [[Test alloc] init];

[t none];

[t param: 9999];

[t params: 1 : 2 : 3 : 4 : 5 : 6 : 7];

fprintf(stderr, "retval gave us %d\n", [t retval]);

NSMutableArray *a = [[NSMutableArray alloc] init];

[a addObject: @1];

[a addObject: @{ @"foo" : @"bar" }];

[a addObject: @("blah")];

a[0] = @2;

NSLog(@"%@", a);

}

}

以防因为一些意外调用的是 runtime 的实现。我在 GetImplementation 中加了一些调试的日志确保它被调用了。一切都正常,即使是 literals and subscripting 也都调用的是替换的实现。

结论

objc_msgSend 的核心部分相当的简单。但它的实现需要一些汇编代码,这让它比它应该的样子更难理解。但是为了性能的优化还是得使用一些汇编代码。但是通过构建了一个简单的汇编蹦床,然后使用 C 实现了它的逻辑,我们可以看到它是如何工作的,它真的没有什么高深的。

时间: 2024-10-08 04:57:30

IMP指针和ObjcSendMsg的相关文章

IMP指针

可能大家一直看到有许多朋友在Runtime相关文章中介绍IMP指针的概念,那么IMP究竟有什么实际作用呢?让我们先从一个函数看起来. Method Swizzling 如果对Runtime有一定了解的话,一定听说过或者用过这个函数: 1 void method_exchangeImplementations(Method m1, Method m2) 它通常叫做method swizzling,算是ObjC的"黑魔法"了,作用就是在程序运行期间动态的给两个方法互换实现,比如有这样一种使

轻松学习之 IMP指针的作用

http://www.cocoachina.com/ios/20150717/12623.html 可能大家一直看到有许多朋友在Runtime相关文章中介绍IMP指针的概念,那么IMP究竟有什么实际作用呢?让我们先从一个函数看起来. Method Swizzling 如果对Runtime有一定了解的话,一定听说过或者用过这个函数: 1 void method_exchangeImplementations(Method m1, Method m2) 它通常叫做method swizzling,算

IMP的简单使用

什么是IMP指针呢? IMP其实就是Implementation的缩写,指向一个方法实现的指针,每一个方法都有一个对应的IMP; 调用一个IMP的方式和调用普通C函数相同,比如: id returnObjc = someIMP(objc,SEL,params...); IMP指针有什么作用呢? 假如有这样一种应用场景,有许多个ViewController,我想在对项目改动最小的情况下,在当每个Controller执行完ViewDidLoad以后就在控制台把自己的名字打印出来,方便我去做调试或者了

Effective Objective-C 2.0重读笔记---1

上次看这本书的时候匆匆走了一遍,最近不太忙,重温了一遍,把笔记写出来~.. 有兴趣的可以去买一本,感觉这本书还是挺不错的 由于大部分是在坐车的时候用手机写的,所以代码很少,图也很少 1. 尽量使用向前声明,延后引入头文件的时间,这样可以减少编译时间2. 使用arraywithobjects:....如果遇到为空的变量,就会自动终止,后面的变量便会添加不上,切不会报错,会造成数据不一致问题,所以尽量使用字面量语法创建相关对象,减少出错且减少代码量3. 使用字面量创建的对象都是不可变的,如果要获得可

runtime记录

前言: 最初对于runtime的了解其实只停留在,知道这是一组C的方法,知道消息机制中会把方法调用转成objc_msgSend(theObject,@selector(objectMethod)).随后有一个具体的了解得益于一次尝试,使用runtime解决按钮连续点击限制:这个例子网上有一堆,就不再赘述,但是不得不说,runtime的基本使用都很巧妙的包含其中.也因此,我对runtime的理解更深入了:随后看了runtime的官方文档,知道了原来方法还有很多,但至此,依然不能灵活的运用.真正让我

EFFECTIVE OBJECTIVE-C 2.0 TIPS 总结 CHAPTER 1 &amp; CHAPTER 2

下面只是对读到的所有 Tips 结合我平时开发中遇到的问题进行总结,每一个 Tips 和书中的每一条对应,本文的目的是去掉书中的大部分讨论的内容,让人能够马上使用这些 Tips,建议阅读过原书后食用更佳. CHAPTER 1 熟悉 OBJECTIVE-C Tips 1 Objective-C 的起源 Objective-C 是从 C 语言演化而来,有 C 的一些基础会有很大帮助 Tips 2 头文件中减少引用 减少在类的头文件中 import 其他头文件,如果使用其他类,那么使用@class C

swift项目第六天:中间发布按钮的封装以及监听点击事件

import UIKit /* 总结:1:给UIButton写分类,新建文件swiftFile,一般为了区分起名字都是名字-Extension,要想调用UI控件需要导入 import UIKit框架,然后给系统的类写分类:extension UIButton {},提供类方法或是构造函数的方法,把与该控件有关的业务逻辑全封装在分类的内部.2:封装方法:类方法:都是以class开头,class func 函数名(参数)->返回值类型{业务逻辑代码,return 返回值}:例子: class fun

Objective-C Runtime能做什么?

在之前的文章中我们介绍了Runtime是什么,属于理论性介绍,你看了上篇很迫切的想知道Runtime到底能干什么?不要着急,这一篇Blog将将讲解Runtime怎么应用到实战中Runtime官方文档在这里,包括了接口名字以及使用说明.下文讲到的接口都能在此文档中找到. KVC中setValue中使用 我们知道在KVC中如果直接setValue如果对象没有这个属性或者是变量就会直接Crash,如: RuntimeObj *obj = [[RuntimeObj alloc]init]; [obj s

iOS深度学习 - Runtime

这里是iOS深度学习-Runtime的大纲. 一.Class 1,isa指针.super_class指针 2,metaclass(元类) 3,objc_object(表示一个 类的实例 的结构体) 和  id类型(typedef struct objc_object *id) 二.Ivar objc_setAssociatedObject 三.Method cache机制.SEL.IMP指针.Method Swizzling 四.Protocal objc_protocol_list 五.Cat