2018总结-2019展望

2018已完成目标

1.4月换了一份工作到了lc;
2.10月买了一辆车;
3.11月拿到了c1牌照。


说说读书

感觉能从书里面听到很多以前没有想法,没有见过的事,每次读到的时候都会很激动,我也在不断的寻找这样的书。但总觉着不够深入,因为没能表达自己的想法,每次看完一本书,总觉着不是真的结束了,又好像还没开始,对书的内容,没有深入的理解,对那些可以随意看的书另当别论,可是遇到好书,也会有这种感觉,看完后,比较心慌,于是马上找下一本书,让自己忘记这种感觉。可是下次看完新的一本书后,心慌、甚至有点失落的感觉又上心头,没完没了。且记住这种感觉吧。


说说工作

虽然从已经倒闭了的公司wb出来后,进了新的东家,但是不顺心的时候还是会有,新公司给自己的心理预期还是有所差距「证明了面试的承诺千万不能相信,尤其是小的创业公司」。主要是还是工作的环境和工作的时间。当然,新的工作也给我带来了收获,收获就是学会了swift编程,这是最大的收获。工作上,编程的能力还是很欠缺,主要是性能优化,对iOS的深入了解,还是很肤浅。希望在新的2019,我能突破一下。


说说家庭

家里的欠债现在好了一些了,希望2019年能过的更好,这样我们家也越来越好。说到自己的儿,已经3岁了,现在已经会打酱油了,很快也上学了,我自己也一直坚持,多给他一些陪伴,这3年,自己虽说做的不是十全十美,但也问心无愧了。


说说朋友

知心的朋友真的越来越少,以前可能还有几个,时光飞逝,大家都各奔东西,和自己没交集的朋友,都离去了,朋友dqq,朋友zyy,都没联系了,不管什么原因,最后还是相忘于江湖吧。珍惜眼前的朋友吧,朋友h,希望能做未来10年的好朋友。


2019展望

  • 健康
    1.参加 2019 年中山或者佛山马拉松半程,拿到奖牌;
    2.参加一次中山或者佛山组织的50公里徒步;
    3.跑步300公里,每周6公里;
    4.完成6块腹肌。

  • 软实力
    1.加入一个能表达能多发言的组织;
    2.重新经营博客,博客30篇,每月3篇 ;
    3.报一个演讲班;
    4.报一个练字班;
    5.AACTP
    6.加入樊登读书会,读 50 本精华的书,一个月一本,写下50篇笔记,每篇500字以上。

  • 技术
    1.学习一门新的编程语言,达到可以编译一个项目,熟悉这门新语言常用的工具;
    2.在 LeetCode 上 刷 100 道题, 100 /10,每个月10道;

  • 奖励
    1.锤子手机奖励
    2.柔记RoWrite智能手写本

【2018-12-31 22:53 by codeRiding in 佛山】【完 】

消息传递和消息转发-从源代码看起

第一部分:从方法调用得出的结论

1.1 文字的描述:先来看一段代码:
  1. ClangClass类有个handle方法
  2. handle方法里面是创建AClass对象并调用addO方法
  3. 我们来看看调用addO方法的具体过程

1.2 源代码的描述:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import "ClangClass.h"
#import "AClass.h"

@interface ClangClass()
@property(nonatomic,strong)AClass *acClass;
@end

@implementation ClangClass

- (instancetype)init{
if (self = [super init]) {
_acClass = [[AClass alloc] init];
}

return self;
}

- (void)handle{
[_acClass addO];
}

@end

1.3 将代码操作一下:
  1. 进入到上面ClangClass.m的文件夹下面,使用命令编译,命令为:clang -rewrite-objc ClangClass.m
  2. 看文件ClangClass.cpp得到的结果为,我们具体看调用的代码是:[_acClass addO];
  3. {是不是感觉一大堆很乱,我也觉得}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void _I_ClangClass_handle(ClangClass * self, SEL _cmd) {
((void (*)(id, SEL))(void *)objc_msgSend)((id)(*(AClass **)((char *)self + OBJC_IVAR_$_ClangClass$_acClass)), sel_registerName("addO"));
}

// 把上面代码优化下,去掉强制转换的符号得到
objc_msgSend(_acClass, sel_registerName("addO"));

---------------------------------------------

IMP本质:是一个函数指针,保存了方法地址,指向方法的实现,这是由编译器生成的。
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id

---------------------------------------------
得出结论:
1. 调用方法addO就是调用objc_msgSend进行消息传递;
2. 而objc_msgSend就是去找到IMP,来执行实现的代码;

第二部分:消息传递objc_msgSend、objc_msgSendSuper、objc_msgSend_stret、objc_msgSendSuper_stret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
objc_msgSend定义:id objc_msgSend(id self, SEL op, …);

self:A pointer that points to the instance of the class that is to receive the message.{消息的接收者}
op:The selector of the method that handles the message.{是selector}
…:A variable argument list containing the arguments to the method.{是参数列表}


id的本质:id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类
typedef struct objc_object *id;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
}


SEL的本质:SEL 是系统在编译过程中,根据 方法的名字 以及 参数序列 生成一个用来区分这个方法的唯一 ID 编号;这个 ID 就是 SEL 类型的。和C的函数指针还不一样,函数指针直接保存了方法的地址,但是SEL只是方法编号。
Selector(typedef struct objc_selector *SEL)

2.1 文字描述objc_msgSend的工作流程:
  1. 在self中沿着isa找到AClass的类对象;
  2. 优先在AClass的cache查找addO方法,如果有就返回imp;
  3. 如果第一次没找到缓存中的addO方法,就查看这个类有没有被释放,如果释放了,就返回;
  4. 如果这个类没有被释放,就检查这个类有没有实现 +initialize方法,如果有但是没有初始化,就初始化;
  5. 判断初始化后,尝试再在AClass的cache查找addO方法,如果找不到,就到AClass的methodLists查找addO方法;
  6. 如果没有在AClass找到addO方法,就会到super_class就是NSObject里面的cache查找;如果在父类的cache找不到,就到父类的methodLists中查找addO方法;
  7. 如果在父类的methodLists也没有找到addO方法,且如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了。
  8. 如果resolver设置为NO,且还没有找到IMP,就返回_objc_msgForward_impcache开始消息转发;
  9. 不管上面的哪一步找到imp,都直接返回imp。

2.2 相关:
  • _objc_msgForward是用于消息转发的。这个函数的实现并没有在objc-runtime的开源代码里面,而是在Foundation框架里面实现的。加上断点启动程序后,会发现__CFInitialize这个方法会调用objc_setForwardHandler函数来注册一个实现。
  • 当向一般对象发送消息时,调用objc_msgSend;当向super发送消息时,调用的是objc_msgSendSuper; 如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。

2.3 本质:
  • objc_msgSend的本质就是找到imp指针执行代码,而静态语言的imp函数指针在编译的时候就已经确定好了,动态语言是编译后可以变换的。

2.4 源代码描述objc_msgSend的工作流程:
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
{其实我没有找到这段的实现,就用大神的代码了}
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //调用这个函数,伪代码...
}

// 源码
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;

if (!cls || !sel) return nil;

imp = lookUpImpOrNil(cls, sel, nil,
YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

// Translate forwarding function to C-callable external version
if (!imp) {
// 如果找不到imp就返回消息转发
return _objc_msgForward;
}

return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
// 查找imp或决定转发
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}

IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP methodPC = nil;
Method meth;
bool triedResolver = NO;

methodListLock.assertUnlocked();

// Optimistic cache lookup
if (cache) {
// 查找imp,如果有缓存,就返回缓存
methodPC = _cache_getImp(cls, sel);
if (methodPC) return methodPC;
}

// Check for freed class
// 查看释放的类
if (cls == _class_getFreedObjectClass())
// 如果类已经释放,返回_freedHandler
return (IMP) _freedHandler;

// Check for +initialize
// 检查有没有实现+initialize方法
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}

// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
// be added but ignored indefinitely because the cache was re-filled
// with the old value after the cache flush on behalf of the category.
retry:
// 加锁
methodListLock.lock();

// Try this class's cache.
// 查找类的缓存
methodPC = _cache_getImp(cls, sel);
if (methodPC) goto done;

// Try this class's method lists.
// 查找类的方法列表
meth = _class_getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, cls, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}

// Try superclass caches and method lists.

curClass = cls;
while ((curClass = curClass->superclass)) {
// Superclass cache.
// 查找父类的缓存
meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
if (meth) {
if (meth != (Method)1) {
// Found the method in a superclass. Cache it in this class.
// 在父类的缓存中查找
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}

// Superclass method list.
// 查找父类的方法列表
meth = _class_getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
}

// No implementation found. Try method resolver once.

if (resolver && !triedResolver) {
// 解锁
methodListLock.unlock();
// 如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了
_class_resolveMethod(cls, sel, inst);
triedResolver = YES;
goto retry;
}

// No implementation found, and method resolver didn't help.
// Use forwarding.
// 方法找不到:尝试一次resolver方法无效;开始转发
_cache_addForwardEntry(cls, sel);
// 返回_objc_msgForward_impcache表示转发
methodPC = _objc_msgForward_impcache;

done:
// 解锁
methodListLock.unlock();

return methodPC;
}

第三部分:消息转发_objc_msgForward

1
2
3
4
5
// add1方法是不存在的,现在试试转发
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AClass *acClass = [[AClass alloc] init];
[acClass performSelector:@selector(add1)];
}

3.1 oc转发文字描述:
  1. 调用resolveInstanceMethod:方法。允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回YES,重新开始objc_msgSend流程。这次对象会响应这个选择器,一般是因为它已经调用过了class_addMethod。如果仍没有实现,继续下面的动作。
  2. 如果调用resolveInstanceMethod没有实现,就开始调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非nil对象。否则返回nil,继续下面的动作。注意这里不要返回self,否则会形成死循环。
  3. 如果调用forwardingTargetForSelector没有效果,就开始调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil;传给一个NSInvocation并传给forwardInvocation:。
  4. 将第三步获取到的方法签名包装成Invocation传入,调用forwardInvocation:方法,如何处理就在这里面了,并返回非nil。
  5. 调用doesNotRecognizeSelector:,默认的实现是抛出异常。如果第三步没能获得一个方法签名,执行该步骤 。

3.2 oc转发源代码描述:
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
使用instrumentObjcMessageSends的方法来查看app的log信息,具体使用方法:在appdelegate.m里面定义如下:

#import "AppDelegate.h"
#import <objc/runtime.h>

void instrumentObjcMessageSends();
@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
instrumentObjcMessageSends(YES);
return YES;
}


// 下面是消息转发的log信息
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject performSelector:
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject forwardingTargetForSelector:
- AClass NSObject forwardingTargetForSelector:
- AClass NSObject methodSignatureForSelector:
- AClass NSObject methodSignatureForSelector:
- AClass NSObject class
- AClass NSObject doesNotRecognizeSelector:
- AClass NSObject doesNotRecognizeSelector:
- AClass NSObject class

第四部分 respondsToSelector、instancesRespondToSelector

1
2
3
4
5
// add1方法是不存在的,现在试试转发,看第四部分
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AClass *acClass = [[AClass alloc] init];
[acClass respondsToSelector:@selector(add1)];
}

4.1 文字分析:
  1. 如果没有找到imp方法,就尝试一次resolveInstanceMethod,不会进行消息转发
  2. 不会进行消息转发,所以就算找不到方法,也不会报doesNotRecognizeSelector这个找不到方法的报错了
  3. 主要的原因,看respondsToSelector的源码就知道,在下面

4.2 源码分析:
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
+ (BOOL)respondsToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector_inst(object_getClass(self), sel, self);
}

- (BOOL)respondsToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector_inst([self class], sel, self);
}

---------------------------------------------
+ (BOOL)instancesRespondToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector(self, sel);
}

---------------------------------------------
BOOL class_respondsToSelector(Class cls, SEL sel)
{
return class_respondsToSelector_inst(cls, sel, nil);
}

bool class_respondsToSelector_inst(Class cls, SEL sel, id inst)
{
IMP imp;

if (!sel || !cls) return NO;

// Avoids +initialize because it historically did so.
// We're not returning a callable IMP anyway.
// 这里的resolver传入的是YES,所以当找不到方法的时候,就只会执行一次resolveInstanceMethod方法,避开了消息转发,具体看最上面的lookUpImpOrNil方法
imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, YES/*resolver*/);
return bool(imp);
}

---------------------------------------------

// respondsToSelector消息转发部分的log
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject respondsToSelector:
- AClass NSObject class
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject dealloc

第五部分 methodForSelector、instanceMethodForSelector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// add1方法是不存在的,现在试试转发,看第四部分
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AClass *acClass = [[AClass alloc] init];
[acClass methodForSelector:@selector(add1)];
}
---------------------------------------------

+ (IMP)methodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return object_getMethodImplementation((id)self, sel);
}

- (IMP)methodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return object_getMethodImplementation(self, sel);
}

---------------------------------------------
+ (IMP)instanceMethodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return class_getMethodImplementation(self, sel);
}

5.1 看源码可以知道,是和respondsToSelector一样的实现机制:没有消息转发
1
2
3
4
5
6
7
8
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject methodForSelector:
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject dealloc

write: by coderiding \ by erliucxy \ 东区-中山-广东 2018.2.12 5:25:51

codeRiding博客说明

用到的技术

博客的方向

  • 记录iOS开发的问题
  • 记录自己学习的东西
  • 记录自己感兴趣的东西
  • 自己的一些总结

关于域名

  • http://coderiding001.xyz/
  • 域名本来用coderiding这个就是完美的,但是因为域名续费太贵,所以,我打算只用一年,明年再换,所以后面跟001,以后就会是002,以此类推