首页 技术 正文
技术 2022年11月15日
0 收藏 399 点赞 3,878 浏览 16273 个字

本篇主要介绍的是 在真正转之前的几个辅助函数

/**
Get number from property.
@discussion Caller should hold strong reference to the parameters before this function returns.
@param model Should not be nil.
@param meta Should not be nil, meta.isCNumber should be YES, meta.getter should not be nil.
@return A number object, or nil if failed.
*/
static force_inline NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained id model,
__unsafe_unretained _YYModelPropertyMeta *meta) {
switch (meta->_type & YYEncodingTypeMask) {
case YYEncodingTypeBool: {
return @(((bool (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
}
case YYEncodingTypeInt8: { return @(((int8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
}
case YYEncodingTypeUInt8: {
return @(((uint8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
}
case YYEncodingTypeInt16: {
return @(((int16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
}
case YYEncodingTypeUInt16: {
return @(((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
}
case YYEncodingTypeInt32: {
return @(((int32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
}
case YYEncodingTypeUInt32: {
return @(((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
}
case YYEncodingTypeInt64: {
return @(((int64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
}
case YYEncodingTypeUInt64: {
return @(((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
}
case YYEncodingTypeFloat: {
float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
if (isnan(num) || isinf(num)) return nil;
return @(num);
}
case YYEncodingTypeDouble: {
double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
if (isnan(num) || isinf(num)) return nil;
return @(num);
}
case YYEncodingTypeLongDouble: {
double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
if (isnan(num) || isinf(num)) return nil;
return @(num);
}
default: return nil;
}
}

这个函数的目的 是 把 _YYModelPropertyMeta.isCNumber == YES  的情况转成NSNumber

// 像这样的类型是C 的 需要调用上边的函数进行转换
@property (nonatomic, assign) int32_t blockWord;
((int8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)这个方法 是  类似[obj method]; 这中方法调用的Runtime 内部实现 根据方法的返回值不同获取不同的返回类型
((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)

下边这个方法是 把 NSNumber 赋值给 原有c 属性

/**
Set number to property.
@discussion Caller should hold strong reference to the parameters before this function returns.
@param model Should not be nil.
@param num Can be nil.
@param meta Should not be nil, meta.isCNumber should be YES, meta.setter should not be nil.
*/
static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model,
__unsafe_unretained NSNumber *num,
__unsafe_unretained _YYModelPropertyMeta *meta) {
switch (meta->_type & YYEncodingTypeMask) {
case YYEncodingTypeBool: {
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);
} break;
case YYEncodingTypeInt8: {
((void (*)(id, SEL, int8_t))(void *) objc_msgSend)((id)model, meta->_setter, (int8_t)num.charValue);
} break;
case YYEncodingTypeUInt8: {
((void (*)(id, SEL, uint8_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint8_t)num.unsignedCharValue);
} break;
case YYEncodingTypeInt16: {
((void (*)(id, SEL, int16_t))(void *) objc_msgSend)((id)model, meta->_setter, (int16_t)num.shortValue);
} break;
case YYEncodingTypeUInt16: {
((void (*)(id, SEL, uint16_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint16_t)num.unsignedShortValue);
} break;
case YYEncodingTypeInt32: {
((void (*)(id, SEL, int32_t))(void *) objc_msgSend)((id)model, meta->_setter, (int32_t)num.intValue);
}
case YYEncodingTypeUInt32: {
((void (*)(id, SEL, uint32_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint32_t)num.unsignedIntValue);
} break;
case YYEncodingTypeInt64: {
if ([num isKindOfClass:[NSDecimalNumber class]]) {
((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.stringValue.longLongValue);
} else {
((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.longLongValue);
}
} break;
case YYEncodingTypeUInt64: {
if ([num isKindOfClass:[NSDecimalNumber class]]) {
((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.stringValue.longLongValue);
} else {
((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.unsignedLongLongValue);
}
} break;
case YYEncodingTypeFloat: {
float f = num.floatValue;
if (isnan(f) || isinf(f)) f = ;
((void (*)(id, SEL, float))(void *) objc_msgSend)((id)model, meta->_setter, f);
} break;
case YYEncodingTypeDouble: {
double d = num.doubleValue;
if (isnan(d) || isinf(d)) d = ;
((void (*)(id, SEL, double))(void *) objc_msgSend)((id)model, meta->_setter, d);
} break;
case YYEncodingTypeLongDouble: {
long double d = num.doubleValue;
if (isnan(d) || isinf(d)) d = ;
((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)model, meta->_setter, (long double)d);
} // break; commented for code coverage in next line
default: break;
}
}
//  这个相对于上边的那个Runtime 方法  多了一个参数
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);
((void (*)(id, SEL, 参数))(void *) objc_msgSend)((id)model, meta->_setter, 参数值);

下边的这个方法是核心方法, 把值 赋值给model  需要的参数 有3 个

1. 需要被复制的Model

2. 值

3. 需要把值赋值给谁

数据类型主要分三种情况,根据 _PropertyMeta对象记录的类型

  • propertyMeta->_isCNumber,基本数值类型

    • bool
    • int8/uint8
    • int16/uint16
    • int32/uint32
    • int64/uint64
    • float
    • double
    • long double
  • propertyMeta->_foundationType,Foundation Class
    • NSString
    • NSArray
    • NSSet
    • NSDictionary
    • NSNumber
    • NSDecimalNumber
    • NSValue
    • NSData
    • NSDate
    • NSURL
  • propertyMeta->_encodingType & XZHEncodingTypeIvarMask,属性变量类型其他类型
    • 自定义NSObject实体类
    • Class
    • SEL
    • Block
    • Struct/Union
    • c数组
    • c字符串
    • c指针

下边的这个方法是吧value 赋值给相应的Property 需要根据property的类型 进行各种判断, 需要注意的是当用户实现了 下边的这个方法

+ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary {
if (dictionary[@"radius"] != nil) {
return [YYCircle class];
} else if (dictionary[@"width"] != nil) {
return [YYRectangle class];
} else if (dictionary[@"y2"] != nil) {
return [YYLine class];
} else {
return [self class];
}
}

这个方法是让用户自己制定 字典内部的返回值类型,如果制定了,在字典转模型的时候,就会按照这个制定的类型进行转换

 /**
Set value to model with a property meta. @discussion Caller should hold strong reference to the parameters before this function returns. @param model Should not be nil.
@param value Should not be nil, but can be NSNull.
@param meta Should not be nil, and meta->_setter should not be nil.
*/
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta) { /**
* 先判断 是不是c 类型的数据
*/
if (meta->_isCNumber) { /**
* 把 值 转换成 NSnumber
*/
NSNumber *num = YYNSNumberCreateFromID(value); // 设置到 Property 中
ModelSetNumberToProperty(model, num, meta); // 这个不知道是什么意思
if (num) [num class]; // hold the number }
// Foundation 类型
else if (meta->_nsType) { // 如果是kCFNull 就赋值为(id)nil
if (value == (id)kCFNull) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else { // 按照不同的Foundation 类型会有不同的赋值方式
switch (meta->_nsType) {
case YYEncodingTypeNSString:
case YYEncodingTypeNSMutableString: { // value 是字符串或者可变字符串
if ([value isKindOfClass:[NSString class]]) {
if (meta->_nsType == YYEncodingTypeNSString) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
}
} // value 是 NSNumber 需要根据是不是 可变的进行不同的赋值
else if ([value isKindOfClass:[NSNumber class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSNumber *)value).stringValue :
((NSNumber *)value).stringValue.mutableCopy);
}
// value 是NSData
else if ([value isKindOfClass:[NSData class]]) {
NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
} // value 是NSURL
else if ([value isKindOfClass:[NSURL class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSURL *)value).absoluteString :
((NSURL *)value).absoluteString.mutableCopy);
}
// value 是带属性的字符串
else if ([value isKindOfClass:[NSAttributedString class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSAttributedString *)value).string :
((NSAttributedString *)value).string.mutableCopy);
}
} break; case YYEncodingTypeNSValue:
case YYEncodingTypeNSNumber:
case YYEncodingTypeNSDecimalNumber: {
if (meta->_nsType == YYEncodingTypeNSNumber) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSNumberCreateFromID(value));
} // 科学计数值 这里判断了3种情况,需要更具不同的情况转化为NSDecimalNumber
else if (meta->_nsType == YYEncodingTypeNSDecimalNumber) {
if ([value isKindOfClass:[NSDecimalNumber class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else if ([value isKindOfClass:[NSNumber class]]) {
NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
} else if ([value isKindOfClass:[NSString class]]) {
NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithString:value];
NSDecimal dec = decNum.decimalValue;
if (dec._length == && dec._isNegative) {
decNum = nil; // NaN
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
}
} else { // YYEncodingTypeNSValue
if ([value isKindOfClass:[NSValue class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
}
}
} break; // 一般NSData 有两种标示,一种是NSData 一种是NSString ,但最终都需要转换成NSData或者NSMutableData
case YYEncodingTypeNSData:
case YYEncodingTypeNSMutableData: {
if ([value isKindOfClass:[NSData class]]) {
if (meta->_nsType == YYEncodingTypeNSData) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
NSMutableData *data = ((NSData *)value).mutableCopy;
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
}
} else if ([value isKindOfClass:[NSString class]]) {
NSData *data = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding];
if (meta->_nsType == YYEncodingTypeNSMutableData) {
data = ((NSData *)data).mutableCopy;
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
}
} break; // 日期
case YYEncodingTypeNSDate: {
if ([value isKindOfClass:[NSDate class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else if ([value isKindOfClass:[NSString class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSDateFromString(value));
}
} break; // NSURL
case YYEncodingTypeNSURL: {
if ([value isKindOfClass:[NSURL class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else if ([value isKindOfClass:[NSString class]]) {
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString *str = [value stringByTrimmingCharactersInSet:set];
if (str.length == ) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, nil);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, [[NSURL alloc] initWithString:str]);
}
}
} break; // 数组
case YYEncodingTypeNSArray:
case YYEncodingTypeNSMutableArray: {
if (meta->_genericCls) {
NSArray *valueArr = nil;
if ([value isKindOfClass:[NSArray class]]) valueArr = value;
else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects;
if (valueArr) {
NSMutableArray *objectArr = [NSMutableArray new];
for (id one in valueArr) {
if ([one isKindOfClass:meta->_genericCls]) {
[objectArr addObject:one];
} // 这个把 字段转换成模型了
else if ([one isKindOfClass:[NSDictionary class]]) {
Class cls = meta->_genericCls; // 这个是 根据字典自定义了返回类型的情况
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:one];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
NSObject *newOne = [cls new];
[newOne yy_modelSetWithDictionary:one];
if (newOne) [objectArr addObject:newOne];
}
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr);
}
} // 处理没有指定数组内部类型的情况,直接把数据转成数组赋值
else {
if ([value isKindOfClass:[NSArray class]]) {
if (meta->_nsType == YYEncodingTypeNSArray) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSArray *)value).mutableCopy);
}
} else if ([value isKindOfClass:[NSSet class]]) {
if (meta->_nsType == YYEncodingTypeNSArray) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSSet *)value).allObjects.mutableCopy);
}
}
}
} break; case YYEncodingTypeNSDictionary:
case YYEncodingTypeNSMutableDictionary: {
if ([value isKindOfClass:[NSDictionary class]]) {
if (meta->_genericCls) {
NSMutableDictionary *dic = [NSMutableDictionary new];
[((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, id oneValue, BOOL *stop) {
if ([oneValue isKindOfClass:[NSDictionary class]]) {
Class cls = meta->_genericCls;
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:oneValue];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
NSObject *newOne = [cls new];
[newOne yy_modelSetWithDictionary:(id)oneValue];
if (newOne) dic[oneKey] = newOne;
}
}];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, dic);
} else {
if (meta->_nsType == YYEncodingTypeNSDictionary) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSDictionary *)value).mutableCopy);
}
}
}
} break; case YYEncodingTypeNSSet:
case YYEncodingTypeNSMutableSet: {
NSSet *valueSet = nil;
if ([value isKindOfClass:[NSArray class]]) valueSet = [NSMutableSet setWithArray:value];
else if ([value isKindOfClass:[NSSet class]]) valueSet = ((NSSet *)value); if (meta->_genericCls) {
NSMutableSet *set = [NSMutableSet new];
for (id one in valueSet) {
if ([one isKindOfClass:meta->_genericCls]) {
[set addObject:one];
} else if ([one isKindOfClass:[NSDictionary class]]) {
Class cls = meta->_genericCls;
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:one];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
NSObject *newOne = [cls new];
[newOne yy_modelSetWithDictionary:one];
if (newOne) [set addObject:newOne];
}
}
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, set);
} else {
if (meta->_nsType == YYEncodingTypeNSSet) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, valueSet);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
((NSSet *)valueSet).mutableCopy);
}
}
} // break; commented for code coverage in next line default: break;
}
}
} // 处理既不是c 也不是Foundation 的情况
else {
BOOL isNull = (value == (id)kCFNull);
switch (meta->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject: {
if (isNull) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
} else if ([value isKindOfClass:meta->_cls] || !meta->_cls) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
} else if ([value isKindOfClass:[NSDictionary class]]) {
NSObject *one = nil;
if (meta->_getter) {
one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
}
if (one) {
[one yy_modelSetWithDictionary:value];
} else {
Class cls = meta->_cls;
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:value];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
one = [cls new];
[one yy_modelSetWithDictionary:value];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
}
}
} break; case YYEncodingTypeClass: {
if (isNull) {
((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL);
} else {
Class cls = nil;
if ([value isKindOfClass:[NSString class]]) {
cls = NSClassFromString(value);
if (cls) {
((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)cls);
}
} else {
cls = object_getClass(value);
if (cls) {
if (class_isMetaClass(cls)) {
((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)value);
}
}
}
}
} break; case YYEncodingTypeSEL: {
if (isNull) {
((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)NULL);
} else if ([value isKindOfClass:[NSString class]]) {
SEL sel = NSSelectorFromString(value);
if (sel) ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)sel);
}
} break; case YYEncodingTypeBlock: {
if (isNull) {
((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())NULL);
} else if ([value isKindOfClass:YYNSBlockClass()]) {
((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())value);
}
} break; case YYEncodingTypeStruct:
case YYEncodingTypeUnion:
case YYEncodingTypeCArray: {
if ([value isKindOfClass:[NSValue class]]) { // 判断类型是否相同
const char *valueType = ((NSValue *)value).objCType;
const char *metaType = meta->_info.typeEncoding.UTF8String;
if (valueType && metaType && strcmp(valueType, metaType) == ) {
[model setValue:value forKey:meta->_name];
}
}
} break; case YYEncodingTypePointer:
case YYEncodingTypeCString: {
if (isNull) {
((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL);
} else if ([value isKindOfClass:[NSValue class]]) {
NSValue *nsValue = value;
if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == ) {
((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue);
}
}
} // break; commented for code coverage in next line default: break;
}
}
}
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,996
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,510
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,353
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,137
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,770
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,848