runtime模型與字典互轉(zhuǎn)

前言

在開發(fā)中必不可少的模型與字典互轉(zhuǎn)喘先,但是一直以來都是使用他人的庫(kù),從來沒有研究其原理或者說深究其所以然⊥⒘#現(xiàn)在窘拯,在這里我們一起來學(xué)習(xí)通過runtime完成模型與字典的互轉(zhuǎn)。

聲明Model

在開始介紹詳細(xì)API之前坝茎,我們先來聲明一個(gè)模型類TestModel涤姊,這個(gè)類提供了根據(jù)字典轉(zhuǎn)換成模型類對(duì)象的功能,還提供了將模型類轉(zhuǎn)換成字典的功能:

//
//  TestModel.h
//  RuntimeDemo
//
//  Copyright ? 2017年 . All rights reserved.
//

#import <Foundation/Foundation.h>

@protocol EmptyPropertyProperty <NSObject>

// 設(shè)置默認(rèn)值嗤放,若為空思喊,則取出來的就是默認(rèn)值
- (NSDictionary *)defaultValueForEmptyProperty;

@end

@interface TestModel : NSObject <EmptyPropertyProperty>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, strong) NSNumber *count;
@property (nonatomic, assign) int    commentCount;
@property (nonatomic, strong) NSArray *summaries;
@property (nonatomic, strong) NSDictionary *parameters;
@property (nonatomic, strong) NSSet *results;

@property (nonatomic, strong) TestModel *testModel;

// 只讀屬性
@property (nonatomic, assign, readonly) NSString *classVersion;

// 通過這個(gè)方法來實(shí)現(xiàn)自動(dòng)生成model
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;

// 轉(zhuǎn)換成字典
- (NSDictionary *)toDictionary;

// 測(cè)試
+ (void)test;

@end

我們這里聲明了幾種類型,主要是模型中的數(shù)組次酌、字典酗钞、集合娇澎、對(duì)象屬性,我們最后要轉(zhuǎn)換對(duì)象成字典。

實(shí)現(xiàn)代碼

在我們講解之前骇扇,先把代碼全部放出來档押,我們?cè)僖灰恢v解:

#import "TestModel.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation TestModel

- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
  if (self = [super init]) {
    for (NSString *key in dictionary.allKeys) {
      id value = [dictionary objectForKey:key];
      
      if ([key isEqualToString:@"testModel"]) {
        TestModel *testModel = [[TestModel alloc] initWithDictionary:value];
        value = testModel;
        self.testModel = testModel;
        
        continue;
      }
      
      SEL setter = [self propertySetterWithKey:key];
      if (setter != nil) {
        ((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);
      }
    }
  }
  
  return self;
}

- (NSDictionary *)toDictionary {
  unsigned int outCount = 0;
  objc_property_t *properties = class_copyPropertyList([self class], &outCount);
  
  if (outCount != 0) {
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:outCount];
    
    for (unsigned int i = 0; i < outCount; ++i) {
      objc_property_t property = properties[i];
      const void *propertyName = property_getName(property);
      NSString *key = [NSString stringWithUTF8String:propertyName];
      
      // 繼承于NSObject的類都會(huì)有這幾個(gè)在NSObject中的屬性
      if ([key isEqualToString:@"description"]
          || [key isEqualToString:@"debugDescription"]
          || [key isEqualToString:@"hash"]
          || [key isEqualToString:@"superclass"]) {
        continue;
      }
      
      // 我們只是測(cè)試窖维,不做通用封裝啦吧,因此這里不額外寫方法做通用處理,只是寫死測(cè)試一下效果
      if ([key isEqualToString:@"testModel"]) {
        if ([self respondsToSelector:@selector(toDictionary)]) {
          id testModel = [self.testModel toDictionary];
          if (testModel != nil) {
            [dict setObject:testModel forKey:key];
          }
          continue;
        }
      }
      
      SEL getter = [self propertyGetterWithKey:key];
      if (getter != nil) {
        // 獲取方法的簽名
        NSMethodSignature *signature = [self methodSignatureForSelector:getter];
        
        // 根據(jù)方法簽名獲取NSInvocation對(duì)象
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        // 設(shè)置target
        [invocation setTarget:self];
        // 設(shè)置selector
        [invocation setSelector:getter];
        
        // 方法調(diào)用
        [invocation invoke];
        
        // 接收返回的值
        __unsafe_unretained NSObject *propertyValue = nil;
        [invocation getReturnValue:&propertyValue];
        
        //        id propertyValue = [self performSelector:getter];
        
        if (propertyValue == nil) {
          if ([self respondsToSelector:@selector(defaultValueForEmptyProperty)]) {
            NSDictionary *defaultValueDict = [self defaultValueForEmptyProperty];
            
            id defaultValue = [defaultValueDict objectForKey:key];
            propertyValue = defaultValue;
          }
        }
        
        if (propertyValue != nil) {
          [dict setObject:propertyValue forKey:key];
        }
      }
    }
    
    free(properties);
    
    return dict;
  }
  
  free(properties);
  return nil;
}

- (SEL)propertyGetterWithKey:(NSString *)key {
  if (key != nil) {
    SEL getter = NSSelectorFromString(key);
    
    if ([self respondsToSelector:getter]) {
      return getter;
    }
  }
  
  return nil;
}

- (SEL)propertySetterWithKey:(NSString *)key {
  NSString *propertySetter = key.capitalizedString;
  propertySetter = [NSString stringWithFormat:@"set%@:", propertySetter];
  
  // 生成setter方法
  SEL setter = NSSelectorFromString(propertySetter);
  
  if ([self respondsToSelector:setter]) {
    return setter;
  }
  
  return nil;
}

#pragma mark - EmptyPropertyProperty
- (NSDictionary *)defaultValueForEmptyProperty {
  return @{@"name" : [NSNull null],
           @"title" : [NSNull null],
           @"count" : @(1),
           @"commentCount" : @(1),
           @"classVersion" : @"0.0.1"};
}

+ (void)test {
  NSMutableSet *set = [NSMutableSet setWithArray:@[@"可變集合", @"字典->不可變集合->可變集合"]];
  NSDictionary *dict = @{@"name"  : @"技術(shù)博客",
                         @"title" : @"http://www.henishuo.com",
                         @"count" : @(11),
                         @"results" : [NSSet setWithObjects:@"集合值1", @"集合值2", set , nil],
                         @"summaries" : @[@"sm1", @"sm2", @{@"keysm": @{@"stkey": @"字典->數(shù)組->字典->字典"}}],
                         @"parameters" : @{@"key1" : @"value1", @"key2": @{@"key11" : @"value11", @"key12" : @[@"三層", @"字典->字典->數(shù)組"]}},
                         @"classVersion" : @(1.1),
                         @"testModel" : @{@"name"  : @"技術(shù)博客",
                                          @"title" : @"http://www.henishuo.com",
                                          @"count" : @(11),
                                          @"results" : [NSSet setWithObjects:@"集合值1", @"集合值2", set , nil],
                                          @"summaries" : @[@"sm1", @"sm2", @{@"keysm": @{@"stkey": @"字典->數(shù)組->字典->字典"}}],
                                          @"parameters" : @{@"key1" : @"value1", @"key2": @{@"key11" : @"value11", @"key12" : @[@"三層", @"字典->字典->數(shù)組"]}},
                                          @"classVersion" : @(1.1)}};
  TestModel *model = [[TestModel alloc] initWithDictionary:dict];
  
  NSLog(@"%@", model);
  
  NSLog(@"model->dict: %@", [model toDictionary]);
}

@end

細(xì)節(jié)講解

注意到這一行代碼了嗎璃搜?這是將對(duì)應(yīng)的值賦值給對(duì)應(yīng)的屬性:

((void (*)(id, SEL, id))objc_msgSend)(self, setter, value)

我們需要明確一點(diǎn)拖吼,objc_msgSend函數(shù)是用于發(fā)送消息的,而這個(gè)函數(shù)是可以很多個(gè)參數(shù)的这吻,但是我們必須手動(dòng)轉(zhuǎn)換成對(duì)應(yīng)類型參數(shù)吊档,比如上面我們就是強(qiáng)制轉(zhuǎn)換objc_msgSend函數(shù)類型為帶三個(gè)參數(shù)且返回值為void函數(shù),然后才能傳三個(gè)參數(shù)橘原。如果我們直接通過objc_msgSend(self, setter, value)是報(bào)錯(cuò)籍铁,說參數(shù)過多。

(void (*)(id, SEL, id)這是C語言中的函數(shù)指針趾断,如果不了解C拒名,沒有關(guān)系,我們只需要記住參數(shù)列表前面是一個(gè)(*)這樣的就是對(duì)應(yīng)函數(shù)指針了芋酌。

生成Setter方法

我們要通過objc_msgSend函數(shù)來發(fā)送消息給對(duì)象增显,然后通過屬性的setter方法來賦值,那么我們要生成setter選擇器:

- (SEL)propertySetterWithKey:(NSString *)key {
  NSString *propertySetter = key.capitalizedString;
  propertySetter = [NSString stringWithFormat:@"set%@:", propertySetter];
  
  // 生成setter方法
  SEL setter = NSSelectorFromString(propertySetter);
  
  if ([self respondsToSelector:setter]) {
    return setter;
  }
  
  return nil;
}

這里就是生成屬性的setter選擇器。我們知道同云,系統(tǒng)生成屬性的setter方法的規(guī)范是setKey:這樣的格式糖权,因此我們只要按照同樣的規(guī)則生成setter就可以了。另外我們還需要判斷是否可以響應(yīng)此setter方法炸站。

模型中有模型屬性

對(duì)于本demo中星澳,模型中還有模型屬性,那么我們應(yīng)該如何來賦值呢旱易?其實(shí)禁偎,我們需要注意一點(diǎn),一定要給模型屬性分配內(nèi)存阀坏,否則看起來賦值了如暖,但是對(duì)象還是空。

if ([key isEqualToString:@"testModel"]) {
    TestModel *testModel = [[TestModel alloc] initWithDictionary:value];
    value = testModel;
    self.testModel = testModel;
        
    continue;
}

這里是在for循環(huán)中的忌堂,我們一定要注意加上continue盒至,否則下邊可能會(huì)將其值設(shè)置為空哦。

生成Getter方法

我們可以通過NSSelectorFromString函數(shù)來生成SEL選擇器士修,當(dāng)然我們也可以通過@selector()生成SEL選擇器枷遂,但是我們這里只能使用前者:

- (SEL)propertyGetterWithKey:(NSString *)key {
  if (key != nil) {
    SEL getter = NSSelectorFromString(key);
    
    if ([self respondsToSelector:getter]) {
      return getter;
    }
  }
  
  return nil;
}

模型轉(zhuǎn)字典

首先,我們需要先獲取所有的屬性李命,以便獲取屬性值:

unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);

objc_property_t類型代表屬性登淘,它是一個(gè)結(jié)構(gòu)體。通過函數(shù)class_copyPropertyList可以獲取對(duì)象的屬性列表及屬性的個(gè)數(shù)封字。

在遍歷屬性列表時(shí),我們通過這樣獲取名稱:

objc_property_t property = properties[i];
const void *propertyName = property_getName(property);
NSString *key = [NSString stringWithUTF8String:propertyName];

由于繼承于NSObject的對(duì)象耍鬓,都有這幾個(gè)屬性阔籽,因此我們需要過濾掉:

// 繼承于NSObject的類都會(huì)有這幾個(gè)在NSObject中的屬性
if ([key isEqualToString:@"description"]
  || [key isEqualToString:@"debugDescription"]
  || [key isEqualToString:@"hash"]
  || [key isEqualToString:@"superclass"]) {
    continue;
}

調(diào)用getter方法獲取值

我們要通過runtime獲取值,常用的有兩種方式:

  • 通過performSelector方法
  • 通過NSInvocation對(duì)象

下面是通過NSInvocation的方法牲蜀,流程可以這樣:先獲取方法簽名笆制,然后根據(jù)方法簽名生成NSInvocation對(duì)象,設(shè)置target涣达、SEL在辆,然后調(diào)用,最后獲取返回值:

SEL getter = [self propertyGetterWithKey:key];
if (getter != nil) {
  // 獲取方法的簽名
  NSMethodSignature *signature = [self methodSignatureForSelector:getter];
  
  // 根據(jù)方法簽名獲取NSInvocation對(duì)象
  NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
  // 設(shè)置target
  [invocation setTarget:self];
  // 設(shè)置selector
  [invocation setSelector:getter];
  
  // 方法調(diào)用
  [invocation invoke];
  
  // 接收返回的值
  __unsafe_unretained NSObject *propertyValue = nil;
  [invocation getReturnValue:&propertyValue];
  
  //        id propertyValue = [self performSelector:getter];
}

如果是通過performSelector方式度苔,一行代碼就可以了匆篓,但是會(huì)提示有內(nèi)存泄露的風(fēng)險(xiǎn),通常使用上面這種方式寇窑,而不是直接使用下面這種方式:

id propertyValue = [self performSelector:getter];

我們這里還做了額外的處理鸦概,當(dāng)屬性值獲取到是空的時(shí)候,我們可以通過協(xié)議指定默認(rèn)值甩骏。當(dāng)值為空時(shí)窗市,我們就會(huì)使用默認(rèn)值:

if (propertyValue == nil) {
  if ([self respondsToSelector:@selector(defaultValueForEmptyProperty)]) {
    NSDictionary *defaultValueDict = [self defaultValueForEmptyProperty];
    
    id defaultValue = [defaultValueDict objectForKey:key];
    propertyValue = defaultValue;
  }
}

模型屬性轉(zhuǎn)換成字典

我們這里的模型有一個(gè)屬性也是一個(gè)模型先慷,因此我們需要額外處理一下:

// 我們只是測(cè)試,不做通用封裝咨察,因此這里不額外寫方法做通用處理论熙,只是寫死測(cè)試一下效果
if ([key isEqualToString:@"testModel"]) {
  if ([self respondsToSelector:@selector(toDictionary)]) {
    id testModel = [self.testModel toDictionary];
    if (testModel != nil) {
      [dict setObject:testModel forKey:key];
    }
    continue;
  }
}

注意,這里是寫死的哦摄狱,因?yàn)槲覀冞@里寫死了testModel脓诡,只是為了簡(jiǎn)化,如果要封裝成通用的方法二蓝,那么就需要做更多的工作了誉券。不過我們這里的目的是學(xué)習(xí)如何轉(zhuǎn),而不是封裝成能用的庫(kù)刊愚。因此踊跟,研究明白其原理才是目的。

測(cè)試

我們測(cè)試一下這樣復(fù)雜的結(jié)構(gòu):

+ (void)test {
  NSMutableSet *set = [NSMutableSet setWithArray:@[@"可變集合", @"字典->不可變集合->可變集合"]];
  NSDictionary *dict = @{@"name"  : @"技術(shù)博客",
                         @"title" : @"http://www.henishuo.com",
                         @"count" : @(11),
                         @"results" : [NSSet setWithObjects:@"集合值1", @"集合值2", set , nil],
                         @"summaries" : @[@"sm1", @"sm2", @{@"keysm": @{@"stkey": @"字典->數(shù)組->字典->字典"}}],
                         @"parameters" : @{@"key1" : @"value1", @"key2": @{@"key11" : @"value11", @"key12" : @[@"三層", @"字典->字典->數(shù)組"]}},
                         @"classVersion" : @(1.1),
                         @"testModel" : @{@"name"  : @"技術(shù)博客",
                                          @"title" : @"http://www.henishuo.com",
                                          @"count" : @(11),
                                          @"results" : [NSSet setWithObjects:@"集合值1", @"集合值2", set , nil],
                                          @"summaries" : @[@"sm1", @"sm2", @{@"keysm": @{@"stkey": @"字典->數(shù)組->字典->字典"}}],
                                          @"parameters" : @{@"key1" : @"value1", @"key2": @{@"key11" : @"value11", @"key12" : @[@"三層", @"字典->字典->數(shù)組"]}},
                                          @"classVersion" : @(1.1)}};
  TestModel *model = [[TestModel alloc] initWithDictionary:dict];
  
  NSLog(@"%@", model);
  
  NSLog(@"model->dict: %@", [model toDictionary]);
}

打印效果

模型轉(zhuǎn)字典的效果:

2017-12-29 16:02:15.207 RuntimeDemo[40233:1083396] model->dict:     {
    classVersion = "0.0.1",
    count = 11,
    parameters =    {
        key1 = "value1",
        key2 =  {
            key11 = "value11",
            key12 =     (
                "三層",
                "字典->字典->數(shù)組",
            ),
        },
    },
    results =   {(
        "集合值2",
        "集合值1",
            {(
            "可變集合",
            "字典->不可變集合->可變集合",
        )},
    )},
    title = "http://www.henishuo.com",
    commentCount = 1,
    testModel =     {
        classVersion = "0.0.1",
        count = 11,
        parameters =    {
            key1 = "value1",
            key2 =  {
                key11 = "value11",
                key12 =     (
                    "三層",
                    "字典->字典->數(shù)組",
                ),
            },
        },
        results =   {(
            "集合值2",
            "集合值1",
                {(
                "可變集合",
                "字典->不可變集合->可變集合",
            )},
        )},
        title = "http://www.henishuo.com",
        commentCount = 1,
        name = "技術(shù)博客",
        summaries =     (
            "sm1",
            "sm2",
                {
                keysm =     {
                    stkey = "字典->數(shù)組->字典->字典",
                },
            },
        ),
    },
    name = "技術(shù)博客",
    summaries =     (
        "sm1",
        "sm2",
            {
            keysm =     {
                stkey = "字典->數(shù)組->字典->字典",
            },
        },
    ),
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸥诽,一起剝皮案震驚了整個(gè)濱河市商玫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牡借,老刑警劉巖拳昌,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異钠龙,居然都是意外死亡炬藤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門碴里,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沈矿,“玉大人,你說我怎么就攤上這事咬腋「牛” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵根竿,是天一觀的道長(zhǎng)陵像。 經(jīng)常有香客問我,道長(zhǎng)寇壳,這世上最難降的妖魔是什么醒颖? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮九巡,結(jié)果婚禮上图贸,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好疏日,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布偿洁。 她就那樣靜靜地躺著,像睡著了一般沟优。 火紅的嫁衣襯著肌膚如雪涕滋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天挠阁,我揣著相機(jī)與錄音宾肺,去河邊找鬼。 笑死侵俗,一個(gè)胖子當(dāng)著我的面吹牛锨用,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隘谣,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼增拥,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了寻歧?” 一聲冷哼從身側(cè)響起掌栅,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎码泛,沒想到半個(gè)月后猾封,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡噪珊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年晌缘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痢站。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枚钓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瑟押,到底是詐尸還是另有隱情,我是刑警寧澤星掰,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布多望,位于F島的核電站,受9級(jí)特大地震影響氢烘,放射性物質(zhì)發(fā)生泄漏怀偷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一播玖、第九天 我趴在偏房一處隱蔽的房頂上張望椎工。 院中可真熱鬧,春花似錦、人聲如沸维蒙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颅痊。三九已至殖熟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斑响,已是汗流浹背菱属。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舰罚,地道東北人纽门。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像营罢,于是被迫代替她去往敵國(guó)和親赏陵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 根據(jù)前面的學(xué)習(xí)iOS開發(fā)之使用Runtime給Model類賦值和OC和Swift中的Runtime愤钾,總結(jié)一下將字典...
    John_LS閱讀 2,568評(píng)論 5 1
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉瘟滨,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評(píng)論 0 9
  • 對(duì)于從事 iOS 開發(fā)人員來說,所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能...
    夢(mèng)夜繁星閱讀 3,721評(píng)論 7 64
  • 青生病了能颁,上吐下瀉杂瘸,還發(fā)燒。 想來是昨天竹筏漂流受涼了伙菊,沒穿雨披败玉,寒氣內(nèi)襲所致。 一向結(jié)實(shí)的他镜硕,仗著身板兒硬實(shí)运翼,梗...
    陌生如我閱讀 99評(píng)論 0 0
  • 自古人生最忌滿,半貧半富半自安兴枯; 半命半天半機(jī)遇血淌,半取半舍半行善; 半聾半啞半糊涂财剖,半智半愚半圣賢悠夯; 半人半我半自...
    半生緣_ab19閱讀 156評(píng)論 0 0