RxCocoa之DelegateProxy源码解析

前言

前段时间学习了RxSwift,然而每次想要把某个代理方法暴露给某个类的rx扩展时,都因为记不住语法而苦恼。

于是在强大的好奇心的驱使下,点开了DelegateProxy的源码。结果,阅读下来发现这部分代码堪称经典,运用了iOS和Swift中的多种高级技能,故记录一下思路和收获,并推荐给大家阅读,可以用来进阶或者回顾这些知识点!

具体可以get到什么技能点
  • (☆☆☆☆☆) 学会通过OC+Swift混编的方式来利用强大的Runtime
  • (☆☆☆☆) 看到如何通过 继承 结合 Protocol 完成很好的封装
  • (☆☆☆) 学会使用 associated object(关联对象)来给某个对象添加“变量”
  • (☆☆) 加深对 Rx 的理解
  • (☆☆) 加深对Delegate的理解
  • (☆☆) 其他零碎的知识点

不过,为了阅读这部分代码,需要读者已经对RxSwift比较熟悉,对Swift的语法和iOS的基础知识也有所了解,否则会影响收获。

其他

文中摘录和阅读的RxCocoa版本号为3.5.0(版本号不同代码可能有细节上的差异)


正文

首先,看如何把XXXDelegate的一个方法暴露给XXX对象的rx扩展:
(没错,就是我怎么都记不住的那部分代码,掀桌 (╯‵□′)╯︵┻━┻ ......)

class RxXXXDelegateProxy: DelegateProxy, DelegateProxyType, XXXDelegate {
    static func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
        let xxx:XXX = (object as? XXX)!
        xxx.delegate = delegate as? XXXDelegate
    }
    static func currentDelegateFor(_ object: AnyObject) -> AnyObject? {
        let xxx: XXX = (object as? XXX)!
        return xxx.delegate
    }
}
extension Reactive where Base: XXX {
    public var delegate:DelegateProxy {
        return RxXXXDelegateProxy.proxyForObject(base)
    }
    public func setDelegate(_ delegate:XXXDelegate) -> Disposable {
        return RxXXXDelegateProxy.installForwardDelegate(
            delegate,
            retainDelegate: false,
            onProxyForObject: self.base)
    }
    public var aNonVoidMethod: ControlEvent<Bool> {
        let source = delegate.
methodInvoked(#selector(XXXDelegate.a_non_void_method(_:param:)))
            .map { parameters in
                return parameters[1] as! SomeType
            }
        return ControlEvent(events: source)
    }
}
理清对象之间的关系

开始阅读代码运行过程之前,我们先把对象之间的关系弄清楚,会帮助我们理解后面的代码。我们看到:

class RxXXXDelegateProxy: DelegateProxy, DelegateProxyType, XXXDelegate { ... }

所以先来看 协议 DelegateProxyType

public protocol DelegateProxyType : AnyObject {

    public static func createProxyForObject(_ object: AnyObject) -> AnyObject

    public static func assignedProxyFor(_ object: AnyObject) -> AnyObject?

    public static func assignProxy(_ proxy: AnyObject, toObject object: AnyObject)

    public static func currentDelegateFor(_ object: AnyObject) -> AnyObject?

    public static func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject)

    public func forwardToDelegate() -> AnyObject?

    public func setForwardToDelegate(_ forwardToDelegate: AnyObject?, retainDelegate: Bool)
}

其定义了一系列的方法,然后在extension中实现了两个方法...具体用途,暂不用去管。
然后,这里基本都是定义的协议方法,那么必然有实体类遵循它。我们看到我们前面写的 RxXXXDelegateProxy 遵循了它!咦?但是我们怎么只实现了两个方法,那其他方法是哪里来的呢?考虑到我们的 RxXXXDelegateProxy 还继承了 DelegateProxy,于是进去瞅瞅:

/// Base class for `DelegateProxyType` protocol.
open class DelegateProxy : _RXDelegateProxy { ... }

看来没错,虽然它没有遵循 DelegateProxyType 这个协议,但是上面的一行注释 “Base class for DelegateProxyType protocol” 说明 DelegateProxy 就是实现 DelegateProxyType 中方法的那个类了。

所以,应该是 DelegateProxy 实现了绝大部分的协议方法,然后我们自定义的 RxXXXDelegateProxy 继承自它,继承到了这些已实现的协议方法,然后再添加两个未实现的函数,就可以完全遵循 DelegateProxyType 了。

问题是,怎么又来了一个 _RXDelegateProxy ?再进去看一下,发现这个类竟然是用OC写的,好奇心又一次被激发!!\(≧▽≦)/

首先,发现其继承自 NSObject,说明对象之间的关系到此为止;其次,浏览一下 .m 文件中的代码,发现一堆这样的函数调用(或函数定义):

  • protocol_copyMethodDescriptionList
  • class_copyProtocolList
  • forwardInvocation

它们有一个共同特定,就是来自于Runtime,所以就可以理解为什么要用OC来编写这部分代码了。

(可能有的童鞋要说了,swift 也是支持 Runtime 的呀,但是如果写在 swift 里,动态性就大大减弱了,只能动态获取到继承自 NSObject 的类的信息 和 加了 @dynamic 的变量和函数,这对于一个基础的库是不可取的。)

总结,对象之间的关系如下图:

图1. 对象之间的关系图

其中虚线表示虽然没有继承关系,但是基本是按照协议来写的,这有点像『鸭子类型』的编程语言的处理方式。

另外,还有一个问题:

RxXXXDelegateProxy 遵循了 XXXDelegate 协议,
但是奇怪的是并没有实现其定义的方法。
那么是如何在rx里面产生数据流的呢?这个问题稍后再看,记为『遗留问题1』。

开始阅读

接着,我们来阅读代码。

首先,我们在使用时,会直接使用 anObjectOfXXX.rx.aNonVoidMethod,所以,我们定位到起点是 aNonVoidMethod ,那么进入之后,我们看到它相当于是:

RxXXXDelegateProxy
.proxyForObject(base)  //Step1
.methodInvoked(#selector(XXXDelegate.a_non_void_method(_:param:)))  //Step2
.map { parameters in
    return parameters[1] as! SomeType
 }

所以这里的关键是Step1 和 Step2 这两行,我们潜进去看看:

Step 1

我们发现 proxyForObject 这个函数在 "DelegateProxyType.swift" 这个文件的 DelegateProxyType 这个协议中定义:

// 为了方便阅读,(仅)去掉了一些 Assert语句
extension DelegateProxyType {
    public static func proxyForObject(_ object: AnyObject) -> Self {
        MainScheduler.ensureExecutingOnScheduler()
        let maybeProxy = Self.assignedProxyFor(object) as? Self    // A
        let proxy: Self
        if let existingProxy = maybeProxy {
            proxy = existingProxy
        }else {
            proxy = Self.createProxyForObject(object) as! Self    // A2
            Self.assignProxy(proxy, toObject: object)    // A3
        }

        let currentDelegate: AnyObject? = Self.currentDelegateFor(object)    // B1
        if currentDelegate !== proxy {
            proxy.setForwardToDelegate(currentDelegate, retainDelegate: false)     // B2
            Self.setCurrentDelegate(proxy, toObject: object)    // B3
        }
        return proxy
    }

解析这段代码之前,先提一点:

由于我们现在知道了对象之间的关系,所以这里Self调用的方法都来自于图1中最左边这一条线路(自下向上)。其中绝大部分在中间的 DelegateProxy 类中,一部分需要自定义的在 RxXXXDelegateProxy 中,另外需要Runtime支持的部分则在 _RXDelegateProxy 中。

上面这段代码可以分为A、B两部分,分别来看:

  • A: 先看 assignedProxyFor 和 assignProxy 这两个函数,完全可以看作是 proxy 的getter 和 setter函数。而为了给一个类的.rx扩展添加一个名曰 proxy 的变量,那么只能祭出『关联对象』这一大法了:
    // 稍作编辑,省略了中间函数调用

    // 用作关联对象的 key 值:
    var delegateAssociatedTag: UnsafeRawPointer = UnsafeRawPointer(UnsafeMutablePointer<UInt8>.allocate(capacity: 1))    
    open class func assignedProxyFor(_ object: AnyObject) -> AnyObject? {       
        // getter:
        let maybeDelegate = objc_getAssociatedObject(object, delegateAssociatedTag)
        return castOptionalOrFatalError(maybeDelegate.map { $0 as AnyObject })
    }
    open class func assignProxy(_ proxy: AnyObject, toObject object: AnyObject) {
        precondition(proxy.isKind(of: self.classForCoder()))
        // setter:
        objc_setAssociatedObject(object, delegateAssociatedTag, proxy, .OBJC_ASSOCIATION_RETAIN)
    }

所以,A部分的代码即是,get 这个 RxXXXDelegateProxy 对象 的变量,如果没有,则 create 一个,然后set。

那么我们来看一下create 的过程,createProxyForObject 只是调用了从父类 DelegateProxy 继承来的初始化函数,把Object对象保存在自己的parentObject变量中,便调用了父类的init。

然而我们看到 父类 _RXDelegateProxy 并没有实现 init 函数,而是有一个 initialize 方法,因此实际上 RxXXXDelegateProxy 对象的创建过程应该在这里:

(注意:熟悉 initialize 的童鞋应该知道,实际上 initialize 函数会在最开始执行,也就是在执行 proxyForObject 之前就会调用。不过这个顺序不太影响,为了保持思路的顺畅,放在这里讲)

+(void)initialize {
    @synchronized (_RXDelegateProxy.class) {
        // 创建一个字典 voidSelectorsPerClass:
        if (voidSelectorsPerClass == nil) {
            voidSelectorsPerClass = [[NSMutableDictionary alloc] init];
        }
        // voidSelectors 集合,用来存储 “返回值为空的协议方法”
        NSMutableSet *voidSelectors = [NSMutableSet set];

       #define CLASS_HIERARCHY_MAX_DEPTH 100

        NSInteger  classHierarchyDepth = 0;
        Class      targetClass         = NULL;

        // 利用 class_getSuperclass 来不断地获取父类 ,但不超过100层(防止堆栈溢出)
        // 初始值为self,也就是我们自定义的那个类: RxXXXDelegateProxy
        for (classHierarchyDepth = 0, targetClass = self;
             classHierarchyDepth < CLASS_HIERARCHY_MAX_DEPTH && targetClass != nil;
             ++classHierarchyDepth, targetClass = class_getSuperclass(targetClass)
        ) {
            unsigned int count;
            // 利用 class_copyProtocolList 获取到该类遵循的各个协议
            Protocol *__unsafe_unretained *pProtocols = class_copyProtocolList(targetClass, &count);
            
            // 调用私有方法 collectVoidSelectorsForProtocol 来递归获取各个协议所遵循的协议
            for (unsigned int i = 0; i < count; i++) {
                // 针对每个协议,将其 “返回值为空的方法”union到voidSelectors中
                NSSet *selectorsForProtocol = [self collectVoidSelectorsForProtocol:pProtocols[i]];
                [voidSelectors unionSet:selectorsForProtocol];
            }
            free(pProtocols);
        }
        if (classHierarchyDepth == CLASS_HIERARCHY_MAX_DEPTH) {
            NSLog(@"Detected weird class hierarchy with depth over %d. Starting with this class -> %@", CLASS_HIERARCHY_MAX_DEPTH, self);
        }
        voidSelectorsPerClass[CLASS_VALUE(self)] = voidSelectors;
    }
}

根据上面的代码(及注释),我们可以看到,这里把 RxXXXDelegateProxy 和 自己所有的父类 所遵循的协议(以及协议遵循的协议...)中所有的 『空返回值的协议方法』都保存在 voidSelectorsPerClass 字典中,key 为 object 的地址,value为『空返回值的协议方法』的集合。

(其中,voidSelectorsPerClass 是一个 static 变量,与 initialize 配合,是给OC的类加 class variables 的一个经典做法。)

这里又有一个问题,为什么要找返回值为空的协议方法呢?有什么特殊之处呢?记为『遗留问题2』。

小结:A部分其实是 RxXXXDelegateProxy 对象初始化的过程(若无,则创建自己;如有,则直接返回),并加到对应的XXX对象rx扩展的关联对象中。

  • B:这部分主要调用的我们自定义的方法 setCurrentDelegate 和 currentDelegateFor,与A部分类似,这也是一个类似初始化的过程,如果currentDelegate不是自己,则setCurrentDelegate为自己。从此,该XXX对象的delegate就是这个新创建的 RxXXXDelegateProxy 对象了,所有代理方法会发给它。
咦? B2那行的setForwardToDelegate做了什么呢?
可以先记住,作为『遗留问题3』

Step 2

在这一步骤中,RxXXXDelegateProxy 调用了自己的 methodInvoked 方法,并把某个 XXXDelegate 方法的 selector 当做参数传了进去。于是我们去看一下这个方法:

    // 省略部分不相关代码
    open func methodInvoked(_ selector: Selector) -> Observable<[Any]> {
        checkSelectorIsObservable(selector)
        if let subject = methodInvokedForSelector[selector] {
            return subject.asObservable()
        }else {
            methodInvokedForSelector[selector] = MessageDispatcher(delegateProxy: self)
            return subject.asObservable()
        }
    }

除了第一句 checkSelectorIsObservable ( 保证该selector方法返回值为空,或 forwardToDelegate 实现了该方法,与『遗留问题2』相关,暂跳过 ),后面的依旧是一个“若有,则返回;若无,则创建”的过程,目标对象是 methodInvokedForSelector[selector].asObservable() ,翻看定义会发现 methodInvokedForSelector 是一个 [Selector: MessageDispatcher] 类型的数组,所以我们需要知道MessageDispatcher的asObservable() 会返回什么。

MessageDispatcher 的主要代码如下:

fileprivate final class MessageDispatcher {
    private let dispatcher: PublishSubject<[Any]>
    private let result: Observable<[Any]>

    init(delegateProxy _delegateProxy: DelegateProxy) {
        weak var weakDelegateProxy = _delegateProxy

        let dispatcher = PublishSubject<[Any]>()
        self.dispatcher = dispatcher

        self.result = dispatcher
            .do(onSubscribed: { weakDelegateProxy?.reset() }, onDispose: { weakDelegateProxy?.reset() })
            .share()
            .subscribeOn(mainScheduler)
    }
    var on: (Event<[Any]>) -> () {
        return self.dispatcher.on
    }
    func asObservable() -> Observable<[Any]> {
        return self.result
    }
}

所以,其返回的是一个名为 result 的Observable,而这个 result 的数据流来自一个 PublishSubject。

好了,自此就可以解释,为什么 Step2 之后,我们可以 .map 操作数据流,因为之前其创造返回了一个Observable。所以,之后rx.aNonVoidMethod就会产生数据流。但是产生数据流的过程在哪里?仔细一想,应该是a_non_void_method 这个代理方法被调用的时候吧?但是我们自定义的 RxXXXDelegateProxy 没有实现这些代理方法呀?那是如何产生数据流的呢?( 是时候解决『遗留问题1』了!)


Step 1.5

怎么办呢?既然没有实现,却可以得知什么时候调用,想到什么了?再加上前面我们一直提到的OC的Runtime?Bingo!就是『消息转发』。
我们去翻看一下,果然,找到了 forwardInvocation 这个方法:

-(void)forwardInvocation:(NSInvocation *)anInvocation {
    // 判断返回值是否为空:
    BOOL isVoid = RX_is_method_signature_void(anInvocation.methodSignature);
    NSArray *arguments = nil;
    // 若为空,则 _sentMessage
    if (isVoid) {
        arguments = RX_extract_arguments(anInvocation);
        [self _sentMessage:anInvocation.selector withArguments:arguments];
    }
    
    // 如果 _forwardToDelegate 实现了该方法,则调用
    if (self._forwardToDelegate && [self._forwardToDelegate respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self._forwardToDelegate];
    }

    // 若为空,则 _methodInvoked
    if (isVoid) {
        [self _methodInvoked:anInvocation.selector withArguments:arguments];
    }
}

从注释中我们可以看到,这里做了这么一件事:

如果 _forwardToDelegate 实现了该方法,则去调用。如果该方法返回值为空,则在调用前后分别执行 _sentMessage 和 _methodInvoked。

返回值为空这件事情,我们继续跳过 (『遗留问题2』),_forwardToDelegate 这个呢?也继续跳过(『遗留问题3』)。

先去看这两个方法,结果发现是空的方法,注释为“abstract method”,那就去看子类喽:

    open override func _sentMessage(_ selector: Selector, withArguments arguments: [Any]) {
        sentMessageForSelector[selector]?.on(.next(arguments))
    }
    open override func _methodInvoked(_ selector: Selector, withArguments arguments: [Any]) {
        methodInvokedForSelector[selector]?.on(.next(arguments))
    }

Gotcha!原来是这里产生的数据流,根据前面 MessageDispatcher 的代码,最终是 MessageDispatcher 中的dispatcher(PublishSubject)产生了数据流,数据为 arguments 数组。

上面我们注意到了另一个方法:_sentMessage,那么应该也有对应的 喽,往上翻看代码,就会找到。所以,如果我们想获取代理方法执行后产生的数据流,则:

RxXXXDelegateProxy.delegate
.methodInvoked(#selector(XXXDelegate.a_non_void_method(_:param:)))
...

想获取代理方法执行之前产生的数据流,则:

RxXXXDelegateProxy.delegate
.sentMessage(#selector(XXXDelegate.a_non_void_method(_:param:)))  //Step2
...

然而,就在我们翻看代码寻找 sentMessage 函数的时候,必然会看到一堆注释:

Only methods that have void return value can be observed using this method because those methods are used as a notification mechanism. It doesn't matter if they are optional or not. Observing is performed by installing a hidden associated PublishSubject that is used to dispatch messages to observers.

Delegate methods that have non void return value can't be observed directly using this method. Because:
* those methods are not intended to be used as a notification mechanism, but as a behavior customization mechanism
* there is no sensible automatic way to determine a default return value

In case observing of delegate methods that have return type is required, it can be done by manually installing a PublishSubject or BehaviorSubject and implementing delegate method.

这三段注释写的非常好,第一段基本上总结了 整个DelegateProxy的作用和原理;第二段解释了我们的『遗留问题2』,即为什么要费尽心思值过滤掉返回值非空的代理方法,因为 返回值非空的代理方法 不是用来做通知策略的,而是用做 定义对象行为 的(比如UITableView的 numberOfSections 等);第三段则告诉我们,如果真正有需求观测 返回值非空的代理方法,则可以自己 install 一个 PublishSubject。这个也跟我们的 『遗留问题3』有关。

我们的 『遗留问题3』 是:

在给XXX对象设置代理的时候,除了setCurrentDelegate,
还调用了setForwardToDelegate,这是做什么的呢?
后来在消息转发的时候,如果 _forwardToDelegate 这个变量实现了某个selector,
会去调用它实现的方法,这个在做什么?

简单浏览代码,我们发现setForwardToDelegate果然就是设置了_forwardToDelegate而已,最开始默认设置为自己(RxXXXDelegateProxy)。

同时,我们回顾最开始,我们为了实现某些代理方法,需要实现那些非空的代理方法,此时就需要:

    public func setDelegate(_ delegate:XXXDelegate) -> Disposable {
        return RxXXXDelegateProxy.installForwardDelegate(
            delegate,
            retainDelegate: false,
            onProxyForObject: self.base)
    }

到 installForwardDelegate 里一看,同样调用了setForwardToDelegate,只不过,这次,它设置的参数中的delegate为_forwardToDelegate。

所以 _forwardToDelegate 是为了 接收「向前转发的代理方法」的一个对象,其实也就是我们之前正常使用的那个代理。此时再去看 forwardInvocation,感觉特别像AOP(面向切片编程,让我想起了给python函数加装饰器的感觉),即:

最开始的代理为A,结果我们把代理改为AProxy,然后把A设置为AProxy的 _forwardToDelegate。于是,所有的代理方法将会到达AProxy,AProxy针对这些方法进行一下操作:

+ 提示方法将要被调用

  调用_forwardToDelegate的原代理方法

+ 提示方法已经被调用

总结

就酱,原来看似很复杂的 DelegateProxy 只是一个通过『关联对象』给扩展加上的一个『用 Runtime 来转发代理方法 & 在返回值为空的代理方法调用前后分别产生两种数据流』的代理。


Where to go from here?
  • 虽然自认为写的已经很详细,主干已经理清,但是仍然还有一些细枝末节可以去探究
  • 针对不熟悉或遗忘的地方可以再去阅读其他资料进行学习、实践练习
  • 我们通过阅读这部分代码,知道了它是什么样子的,甚至知道了它为什么是这个样子的,但是,还可以去思考,它为什么不能是其他样子呢?是会影响性能吗?还是会造成封装不好?或者,也许你通过思考最终发现,它还可以是其他更好的样子,那么提前恭喜你!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,050评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,650评论 18 139
  • 幸运 青春 花样年华 一束花 再见,旧时光 缘分 陌上花开
    从你的世界路過阅读 501评论 0 1
  • 夜晚的声音总是如水般沉寂 偶尔的荡漾也会慢慢迷藏 在很轻很轻的记忆里 把你拽出到我的视野 没人嘈杂喧嚣的夜 来仔细...
    八度黑白阅读 292评论 0 2
  • 有几个同事要去国外做推广,这涉及到演讲,于是我换位思考了一下,如果是我去做这个招聘演讲的话,我会怎么做?更重要的是...
    塔希提的风阅读 994评论 4 21