iOS底層探索十(方法的本質下-消息轉發流程)

前言

相關文章

iOS底層探索二(OC 中 alloc 方法 初探)

iOS底層探索三(內存對齊與calloc分析)  

iOS底層探索四(isa初探-聯合體,位域,內存優化)     

iOS底層探索五(isa與類的關係)  

iOS底層探索六(類的分析上)

iOS底層探索七(類的分析下)

iOS底層探索八(方法本質上)

iOS底層探索九(方法的本質下objc_msgSend慢速及方法轉發初探)

  相關代碼:
      objc4_752源碼    消息轉發

 上篇文章講述了方法的查找流程進行了詳細的探索,並對方法的轉發進行了初步探索,這篇文章,我們對消息的轉發流程進行詳細的探索。

消息轉發

上篇文章我們介紹了,如果在OC中如果調用一個沒有實現的方法,在方法查找過程中因爲找不到會造成崩潰,但是蘋果大大給我們提供了另一個解決思路,就是在動態解析(resolveMethod)的過程中有進行消息轉發,並且我們在上篇文章中也實現了初步的防止崩潰方法:

static void resolveMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());

    if (! cls->isMetaClass()) {//不是元類
//判斷類不是元類,那sel就是實例方法,那就先轉發resolveInstanceMethod方法,判斷有沒有實現resolveInstanceMethod,沒實現就不做處理
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        //先轉發resolveClassMethod,會先查找下resolveClassMethod,如果沒實現就不做處理
        resolveClassMethod(cls, sel, inst);
        //再次查找下方法,如果沒有的話,就再轉發一下resolveInstanceMethod方法
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            resolveInstanceMethod(cls, sel, inst);
        }
    }
}

實例方法動態消息決議

我們在main函數中調用teacher 中一個沒有實現的方法;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        #pragma clang diagnostic push
//        // 讓編譯器忽略錯誤,不然調用沒有的方法,編譯器編譯完語法分析會報錯,導致不能運行
        #pragma clang diagnostic ignored "-Wundeclared-selector"
 
                XZTeacher *teacher = [[XZTeacher alloc] init];
//        消息轉發流程這個方法.h中之聲明,不實現
        [teacher saySomthing];
        
        #pragma clang diagnostic pop
    }
    return 0;
}

因爲調用的是實力方法,我們來查看resolveInstanceMethod 方法,這裏傳入3個參數 cls 類,sel 方法編號, inst 對象

static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
//查找系統方法中是否有實現resolveInstanceMethod這個方法,這裏會根據繼承鏈進行查找
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }
    //發送SEL_resolveInstanceMethod消息,系統給你一次機會-->你是不是要針對這個沒有實現的sel進行操作一下
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //發送消息SEL_resolveInstanceMethod 傳參爲我們的方法編號
    //崩潰的方法不是這個方法,說明再NSObject方法中有實現這個方法
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
//再次查找類中是否sel方法,因爲resolveInstanceMethod方法執行後可能動態進行添加了,resolver是不要進行消息轉發了
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

我們可以看到首先lookUpImpOrNil查找類中方法中是否有實現resolveInstanceMethod這個方法,這裏會根據繼承鏈進行查找,因爲是根據繼承鏈查找,也就是說我們類中如果沒有實現的話,就需要查找系統類,如果都找不到,這裏就直接return,

我們可以在類中查找到這個方法(resolveInstanceMethod),有這個方法後,代碼繼續執行,然後使用

msg(cls, SEL_resolveInstanceMethod, sel),方法給resolveInstanceMethod發送消息從而繼續lookUpImpOrNil查詢方法IMP有沒有實現,所以我們可以在我們自己的類中實現resolveInstanceMethod方法並對IMP進行綁定,所以在teacher中實現+resolveInstanceMethod 方法,並對未實現的方法進行IMP實現及綁定,使得,本身調用saySomthing 方法,經過IMP綁定後,調用了SayHello方法。從而防止了崩潰, 

#import <objc/message.h>
 
- (void)sayHello{
    NSLog(@"%s",__func__);
 
}
 
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(saySomthing)) {
        NSLog(@"進入方法了");
        IMP saySomeIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method sayMethod = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayType = method_getTypeEncoding(sayMethod);
        return class_addMethod(self, sel, saySomeIMP, sayType);
    }
    return [super resolveInstanceMethod:sel];
}

打印結果:

根據打印結果,我們可以看出,系統調用我們在XZTeacher 類中的resolveInstanceMethod方法,並根據我們的綁定,執行了sayHello方法。

實例方法我們看到了可以這麼處理,那麼類方法呢,我們也來看一下,我們在Main 函數中調用[XZTeacher sayLove],方法,sayLove 方法在XZTeacher類中只聲明不實現,運行崩潰,這是肯定的就不進行演示了,這裏根據源碼,我們可以看到會進入resolveMethod方法,並進入resolveClassMethod方法;

static void resolveClassMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
    assert(cls->isMetaClass());
    //查找下類是否實現了resolveClassMethod方法,NSObject類已經實現了
    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //切記,此處是向元類發送resolveClassMethod消息,也就是調用resolveClassMethod方法
    bool resolved = msg(nonmeta, SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

這裏我們需要注意的是傳入的參數cls 爲元類,sel方法編號,inst爲類;我們可以看到這裏和resolveInstanceMethod方法中的邏輯基本一樣,不過這裏檢查的系統方法爲resolveClassMethod方法,這個方法我們可以看到上面的截圖裏面在NSObject類中也是有實現這個方法的;

類方法動態消息決議

我們在main函數中調用一個類方法 [XZTeacher sayLove] 在XZTeacher類中實現resolveClassMethod方法並運行程序:

可以看到確實來了resolveClassMethod方法,所以我們可以在這個方法進行類似處理來進行防止崩潰;在方法內加入代碼:

+(BOOL)resolveClassMethod:(SEL)sel
{
    NSLog(@"來了:resolveClassMethod");
    if (sel == @selector(sayLove)) {
        NSLog(@"進入方法了sayLove");
        IMP sayObjIMP = class_getMethodImplementation(objc_getMetaClass("XZTeacher"), @selector(sayObjc));
        Method sayObjMethod = class_getClassMethod(objc_getMetaClass("XZTeacher"), @selector(sayObjc));
        const char *sayObjType = method_getTypeEncoding(sayObjMethod);
        // 類方法在元類 objc_getMetaClass("XZTeacher")
        BOOL isAdd = class_addMethod(objc_getMetaClass("XZTeacher"), sel, sayObjIMP, sayObjType);
        return isAdd;
    }
    return [super resolveClassMethod:sel];
}

運行程序,查看運行結果,可以看出方法正常運行

resolveMethod方法中類方法在調用resolveClassMethod完成後還進行了一次調用

        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            resolveInstanceMethod(cls, sel, inst);
        }


IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

在這裏我們可以看出,因爲這裏是的cls是元類,所以在元類中lookUpImpOrNil查找sayLove方法肯定也是找不到的,這裏就會調用resolveInstanceMethod,那我們是不是可以考慮將所有的處理都放到這個方法中呢resolveInstanceMethod,嘗試將resolveInstanceMethod重寫,並調用sayLove方法

可以看出還是隻來了resolveClassMethod方法,這是我們考慮,因爲是在元類職工查找resolveInstanceMethod方法,所以找不到,這個時候會想到,查找方法就是根據繼承鏈來進行查找方法的,也就是如下圖:

根據繼承鏈關係,可以看到根元類也是繼承與NSObject類,那就可以在NSObject類中只處理resolveInstanceMethod方法,先寫上這個方法添加處理方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"來了,%s",__func__);
    if (sel == @selector(saySomthing)) {
        NSLog(@"進入方法了saySomthing");
        IMP saySomeIMP = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *sayType = method_getTypeEncoding(sayMethod);
        return class_addMethod(self, sel, saySomeIMP, sayType);
    }

        if (sel == @selector(sayLove)) {
            NSLog(@"進入方法了sayLove");
            IMP sayObjIMP = class_getMethodImplementation(objc_getMetaClass("NSObject"), @selector(sayEasy));
            Method sayObjMethod = class_getClassMethod(objc_getMetaClass("NSObject"), @selector(sayEasy));
            const char *sayObjType = method_getTypeEncoding(sayObjMethod);
            // 類方法在元類 objc_getMetaClass("XZTeacher")
            return  class_addMethod(objc_getMetaClass("NSObject"), sel, sayObjIMP, sayObjType);;
        }

//    [super resolveInstanceMethod:sel],因爲這裏是根類不能調用super了,直接返回NO就行
    return NO;
}

運行查看下運行結果

調用類方法sayLove方法可以調用到SayEasy方法,調用實例方法saySomething根據IMP轉換調用了SayMaster方法;說民這裏處理確實可以一勞永逸

崩潰優化策略

  1. 可以在自己的項目工程中每個模塊進行命名規範,例如首頁的所有方法都以home_開頭,我的都有me_開頭

  2. 實現一個NSObject分類,進行bug替換,當檢測到是home_開始,沒有方法時,直接跳轉到首頁,並上報服務器,這樣的簡單操作就可以實現一個優化策略了。

這個策略可以發小是有漏洞的,如果在上層類中實現了resolveInstanceMethod方法那麼實例方法在防止的時候就不會走到NSObject類中了,可能就會導致上報不全,等問題。所以我們再進行消息處理的時候,一般會放到消息轉發的最後一步,那麼我們繼續來看消息轉發中的流程,在源碼中我們可以看到resolveMethod調用之後就是cache_fill 就會導致崩潰了,那麼中件流程我們根本沒法探索,這時我們需要回頭看一下方法緩存;

消息轉發流程探索

根據查找到imp後會進行緩存調用方法爲log_and_fill_cache,看到log說明應該是有日誌產生的,進入方法進行查看

log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    //如果有這個objcMsgLogEnabled變量就會寫入日誌
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
//   寫入緩存
    cache_fill (cls, sel, imp, receiver);
}

需要一個參數爲objcMsgLogEnabled爲true時調用logMessageSend 方法進行日誌書寫,繼續進入

bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;

bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char	buf[ 1024 ];

    // Create/open the log file
    if (objcMsgLogFD == (-1))
    {
        snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
        objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
        if (objcMsgLogFD < 0) {
            // no log file - disable logging
            objcMsgLogEnabled = false;
            objcMsgLogFD = -1;
            return true;
        }
    }

    // Make the log entry
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}

可以看到日誌是寫入了本地的"/tmp/msgSends-編號"的目錄下總結下來就是需要2個參數objcMsgLogEnabled爲true 可以看到函數上面默認爲false ,還有objcMsgLogFD<0 函數上這個值默認爲-1,所以就只需要看objcMsgLogEnabled爲true即可;搜索一下這個函數的賦值方法

OBJC_EXPORT void
instrumentObjcMessageSends(BOOL flag)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}

找到這個方法我們需要在外部聲明一下這個函數來看一下在main文件中這枚寫下

#import "XZPerson.h"
#import "XZTeacher.h"
#import "NSObject+XZ.h"
#import <objc/runtime.h>

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        XZTeacher *teacher = [XZTeacher alloc];
//        方法調用日誌打開
        instrumentObjcMessageSends(true);
        [teacher saySomthing];
//        [XZTeacher sayLove];
        instrumentObjcMessageSends(false);
    }
    return 0;
}

將之前處理崩潰的都先註釋掉,然後調用日誌打開,運行並查看/tmp/路徑下以msgSends開頭的文件進行查看

+ XZTeacher NSObject resolveInstanceMethod:
+ XZTeacher NSObject resolveInstanceMethod:
- XZTeacher NSObject forwardingTargetForSelector:
- XZTeacher NSObject forwardingTargetForSelector:
- XZTeacher NSObject methodSignatureForSelector:
- XZTeacher NSObject methodSignatureForSelector:
- XZTeacher NSObject class
+ XZTeacher NSObject resolveInstanceMethod:
+ XZTeacher NSObject resolveInstanceMethod:
- XZTeacher NSObject doesNotRecognizeSelector:
- XZTeacher NSObject doesNotRecognizeSelector:
- XZTeacher NSObject class
- OS_xpc_serializer OS_xpc_object dealloc
- OS_object NSObject dealloc
+ OS_xpc_payload NSObject class
。。。根據名稱可以判斷下面都是系統方法

可以看到都是調用了2次分別爲resolveInstanceMethodforwardingTargetForSelectormethodSignatureForSelector方法,這個方法resolveInstanceMethod已經研究過了,下面我們對後面2個方法研究一下

forwardingTargetForSelector 方法研究

點擊xcode 工具中的help 搜索

自己沒有處理的方法,會使用快速轉發到forwardingTargetForSelector方法中,使用這個方法,返回一個對象,讓別人進行處理這個方法,那麼我們新定義一個類XZStudent類,在這個類實現saySomethingsayLove 方法並實現

#import "XZStudent.h"

@implementation XZStudent
- (void)saySomthing
{
    NSLog(@"%s",__func__);
}
+ (void)sayLove{
    NSLog(@"%s",__func__);
}

@end

在XZTeacher類中實現forwardingTargetForSelector方法

//實例方法調用這個轉發
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(saySomthing)) {
        return [XZStudent alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}
//類方法調用這個轉發
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(sayLove)) {
        return [XZStudent class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

並在main中調用saySomething方法查看打印效果

正常打印,可以看出這樣也是能完美解決崩潰

 

methodSignatureForSelector方法研究

根據xcode中的help 搜索,methodSignatureForSelector搜索

  這個方法methodSignatureForSelector 方法關聯使用的就是forwardInvocation方法,將之前的處理先去掉,在XZTeacher類中加入這個最新處理:

//方法簽名的下層慢速處理,返回一個方法簽名 但是隻有這個方法肯定是不夠的
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomthing)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s",__func__);
}

這裏的方法簽名是有固定格式的,標示不同的類型具體可查看官方文檔

查看一下運行結果:

可以正常執行到方法,而且不崩潰了 ,但是也沒有對這個方法進行處理,我們繼續查看文檔中的這個方法

看到可以這麼進行處理,

- (void)forwardInvocation:(NSInvocation *)invocation
{
    SEL aSelector = [invocation selector];
 
    if ([friend respondsToSelector:aSelector])
        [invocation invokeWithTarget:friend];
    else
        [super forwardInvocation:invocation];
}

按照官方文檔我們進行處理

    if ([[XZStudent alloc] respondsToSelector:aSelector])
        [anInvocation invokeWithTarget:[XZStudent alloc]];
    else
        [super forwardInvocation:anInvocation];

運行查看結果:

也可以正常運行,同樣的這個如果是類方法處理方式和實例方法處理方式不同,如下代碼:

//方法簽名的下層慢速處理,返回一個方法簽名 但是隻有這個方法肯定是不夠的
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomthing)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s",__func__);
    //    在這裏進行處理方法,如果處理的話就可以處理,不處理就會失效
    SEL aSelector = [anInvocation selector];
//    查看XZStudent 對象能否進行處理,如果能,就交給他處理不能就不處理了
    if ([[XZStudent alloc] respondsToSelector:aSelector])
        [anInvocation invokeWithTarget:[XZStudent alloc]];
    else
        [super forwardInvocation:anInvocation];
}
//類方法處理
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(sayLove)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s",__func__);
    //    在這裏進行處理方法,如果處理的話就可以處理,不處理就會失效
    SEL aSelector = [anInvocation selector];
    //    查看XZStudent 類能否進行處理,如果能,就交給他處理不能就不處理了
    if ([[XZStudent class] respondsToSelector:aSelector])
        [anInvocation invokeWithTarget:[XZStudent class]];
    else
        [super forwardInvocation:anInvocation];
}

如果這幾個方法都沒有實現的話,系統就會調用 doesNotRecognizeSelector崩潰信息出來

+ (void)doesNotRecognizeSelector:(SEL)sel {

    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 

                class_getName(self), sel_getName(sel), self);

}

總結

這篇文章我們就對方法的本質進行了完整的探索:

  1. 方法的底層就是調用objc_msgSend 方法

  2. 進行彙編在緩存中快速查找

  3. 調用_class_lookupMethodAndCache3進行慢速查找

  4. 找不到方法後進行動態解析

          4.1 :resolveInstanceMethod:爲發送消息的對象的添加一個IMP,然後再讓該對象去處理

          4.2:forwardingTargetForSelector:將該消息轉發給能處理該消息的對象

          4.3:methodSignatureForSelectorforwardInvocation:第一個方法生成方法簽名,然後創建NSInvocation對象作爲參數給第二個方法,

         4.3.2 然後在第二個方法(forwardInvocation)裏面做消息處理,只要在第二個方法裏面不執行父類的方法,即使不處理也不會崩潰

    5.如果不進行動態解析就會導致崩潰

最後附帶一份蘋果的官方消息轉發圖:

寫給自己:

喋喋不休不如觀心自省,埋怨他人不如即聽即忘。能干擾你的,往往是自己的太在意,能傷害你的,往往是自己的想不開,未完待續。。。。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章