Block(二)__block

一、__block修饰基本数据类型的局部变量

1、被__block修饰的局部变量可以在Block内部修改,__block不能修饰全局变量、静态变量(static)

  • __block修饰的变量,可以在Block内部被修改
#import "ViewController.h"
#import "RevanPerson.h"

typedef void(^RevanBlock)(void);

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block int autoA = 10;
    
    RevanBlock revan_b = ^{
        autoA = 20;
        NSLog(@"局部变量autoA=%d", autoA);
    };
    
    revan_b();
}

@end
打印输出:
2018-07-07 23:53:14.791546+0800 01-Block本质[11691:578111] 局部变量autoA=20
  • 源码分析
  • 1、__ViewController__viewDidLoad_block_impl_0结构体,发现被__block修饰的基本数据类型autoA现在是一个__Block_byref_autoA_0类型的指针
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
   //没有__block修饰时 int autoA
  __Block_byref_autoA_0 *autoA; // by ref
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_autoA_0 *_autoA, int flags=0) : autoA(_autoA->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 2、__Block_byref_autoA_0结构体如下
//结构体
struct __Block_byref_autoA_0 {
  void *__isa;//NSObject对象特有
__Block_byref_autoA_0 *__forwarding;//这个__forwarding指向了__Block_byref_autoA_0类型的地址
 int __flags;
 int __size;
 int autoA;//真正的autoA
};
  • 3、__block int autoA = 10代码底层源码如下
__attribute__((__blocks__(byref))) __Block_byref_autoA_0 autoA = {
        (void*)0,
        (__Block_byref_autoA_0 *)&autoA,
        0,
        sizeof(__Block_byref_autoA_0),
        10
    };
  • 4、Block代码段
RevanBlock revan_b =
    ((void (*)())&__ViewController__viewDidLoad_block_impl_0(
                                                             
            (void *)__ViewController__viewDidLoad_block_func_0,//Block的回调方法
            &__ViewController__viewDidLoad_block_desc_0_DATA,//Block一些信息
            (__Block_byref_autoA_0 *)&autoA,//__block修饰符对象
            570425344)
     );
  • 5、__ViewController__viewDidLoad_block_func_0函数,Block回调代码块
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
    //Block通过autoA来找到__block对象
  __Block_byref_autoA_0 *autoA = __cself->autoA; // bound by ref
        //__block对象再通过__forwarding找到__block对象具体地址(有可能从栈区复制到堆区),在找到autoA变量直接重新赋值
        (autoA->__forwarding->autoA) = 20;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_ym_kg_kmbfn17v86dqrzyv26ww80000gn_T_ViewController_2e1ccd_mi_0, (autoA->__forwarding->autoA));
    }
  • 6、__ViewController__viewDidLoad_block_desc_0_DATA的结构体类型
static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
}
  • __ViewController__viewDidLoad_block_desc_0_DATA函数内部实现
 __ViewController__viewDidLoad_block_desc_0_DATA = {
    0,
    sizeof(struct __ViewController__viewDidLoad_block_impl_0),
    //当Block从栈区copy到堆区时,自动会触发__ViewController__viewDidLoad_block_copy_0函数
    __ViewController__viewDidLoad_block_copy_0,
    //当Block销毁时,自动触发__ViewController__viewDidLoad_block_dispose_0函数
    __ViewController__viewDidLoad_block_dispose_0
    
};
  • 当Block从栈区copy到堆区时,自动触发__ViewController__viewDidLoad_block_copy_0函数
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {
    //调用_Block_object_assign函数,使Block内部强引用__block对象(__Block_byref_autoA_0)
    _Block_object_assign((void*)&dst->autoA, (void*)src->autoA, 8/*BLOCK_FIELD_IS_BYREF*/);
    
}
  • 当Block销毁时,会触发__ViewController__viewDidLoad_block_dispose_0函数,释放对__block对象的强引用
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {
    //当Block销毁时,Block对__block对象(__Block_byref_autoA_0)的强引用要release
    _Block_object_dispose((void*)src->autoA, 8/*BLOCK_FIELD_IS_BYREF*/);
    
}
  • 小结:
    • 1、使用了__block修饰的基本类型变量,在Block内部使用时,Block会把这个基本类型变量当成一个对象来捕获到结构体中,这个对象中的成员有一个是这个基本数据类型变量
      __block底层数据结构.png
    • 2、当block在栈上时,并不会对__block变量产生强引用
    • 3、当block被copy到堆时
      • 3.1、会调用block内部的copy函数
      • 3.2、copy函数内部会调用_Block_object_assign函数
      • 3.3、_Block_object_assign函数会对__block变量形成强引用
        __block修饰的基本数据类型内存管理一.png
        __block修饰的基本数据类型内存管理二.png
        __block修饰的基本数据类型内存管理__forwarding.png

二、__block修饰对象类型的局部变量

  • RevanPerson
#import <Foundation/Foundation.h>

@interface RevanPerson : NSObject

@end

#import "RevanPerson.h"

@implementation RevanPerson

- (void)dealloc {
    NSLog(@"%s", __func__);
}

@end
  • 测试代码
#import "ViewController.h"
#import "RevanPerson.h"

typedef void(^RevanBlock)(void);

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block RevanPerson *person = [[RevanPerson alloc] init];
    
    RevanBlock revan_b = ^{
        NSLog(@"局部变量autoA=%@", person);
    };
    revan_b();
    NSLog(@"__block修饰对象");
}

@end
打印输出
2018-07-08 01:18:41.131610+0800 01-Block本质[13071:639577] 局部变量autoA=<RevanPerson: 0x600000017a60>
2018-07-08 01:18:41.131807+0800 01-Block本质[13071:639577] __block修饰对象
2018-07-08 01:18:41.131922+0800 01-Block本质[13071:639577] -[RevanPerson dealloc]
  • 源码分析
  • 1、__ViewController__viewDidLoad_block_impl_0结构体
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
    //__block对象
  __Block_byref_person_0 *person; // by ref
    
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 2、__block对象__Block_byref_person_0结构体
struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
    //在__block对象中有关于捕获的对象修饰类型
 RevanPerson *__strong person;
};

当使用__block 和 __weak一起来修饰对象

  • 测试代码
#import "ViewController.h"
#import "RevanPerson.h"

typedef void(^RevanBlock)(void);

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block __weak RevanPerson *person = [[RevanPerson alloc] init];
    
    RevanBlock revan_b = ^{
        NSLog(@"局部变量autoA=%@", person);
    };
    revan_b();
    NSLog(@"__block修饰对象");
}

@end
  • 源码分析
  • 1、__ViewController__viewDidLoad_block_impl_0结构体
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
    //__block对象
  __Block_byref_person_0 *person; // by ref
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 2、__block对象中person对象的修饰是__weak类型的
struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
  //copy用来决定__block对象对person对象是强引用还是弱引用
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
    //使用__weak修饰的对象
 RevanPerson *__weak person;
};
  • 小结:
  • 当block在栈上时
    • 不会产生强引用
  • 当block拷贝到堆上时
    • 会调用__block变量内部的copy函数_Block_object_assign函数会根据所指向对象的修饰符做出相应的操作
    • 如果是基本类型局部变量,__block 对象会强引用
    • 如果是对象类型局部变量,会通过修饰对象有无__weak来决定__block对象对局部对象是强引用还是弱引用
  • 如果__block变量从堆上移除
    • 会调用__block变量内部的dispose函数
    • dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose函数会自动释放指向的对象
      __block和__weak修饰对象.png

二、Block的循环引用

1、ARC下的Block的循环引用

  • 产生循环引用
  • 1、RevanPerson
#import <Foundation/Foundation.h>

typedef void(^RevanBlock)(void);

@interface RevanPerson : NSObject
@property (nonatomic, copy) RevanBlock revan_b;
@end

#import "RevanPerson.h"
@implementation RevanPerson

- (void)dealloc {
    NSLog(@"%s", __func__);
}

@end
  • 2、测试代码
#import "ViewController.h"
#import "RevanPerson.h"


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person = [[RevanPerson alloc] init];
    
    person.revan_b = ^{
        NSLog(@"block中使用对象=%@", person);
    };
    person.revan_b();
    NSLog(@"Block的循环引用");
}

@end
打印输出:
2018-07-08 10:24:58.030126+0800 01-Block本质[14588:705621] block中使用对象=<RevanPerson: 0x604000007f40>
2018-07-08 10:24:58.030317+0800 01-Block本质[14588:705621] Block的循环引用
  • 发现程序已经运行完毕,但是RevanPerson对象并没有释放
  • 源码分析
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
    //block内部有一个person属性,是强引用
  RevanPerson *__strong person;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, RevanPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 最开始内存图
    使用Block造成循环引用.png
  • 代码运行完,person销毁
    使用Block循环引用.png
  • Block造成的循环引用
    使用Block循环引用.png

2、解决循环引用

2.1、使用__weak修饰person对象,在Block中使用经过__weak修饰的person对象

  • 1、RevanPerson
#import <Foundation/Foundation.h>

typedef void(^RevanBlock)(void);

@interface RevanPerson : NSObject
@property (nonatomic, copy) RevanBlock revan_b;
@end

#import "RevanPerson.h"

@implementation RevanPerson

- (void)dealloc {
    NSLog(@"%s", __func__);
}

@end
  • 2、测试代码
#import "ViewController.h"
#import "RevanPerson.h"


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person = [[RevanPerson alloc] init];
    __weak typeof(person) weakPerson = person;
    person.revan_b = ^{
        NSLog(@"block中使用对象=%@", weakPerson);
    };
    person.revan_b();
    NSLog(@"Block的循环引用");
}

@end
打印输出:
2018-07-08 10:51:27.219829+0800 01-Block本质[15081:722988] block中使用对象=<RevanPerson: 0x604000206c50>
2018-07-08 10:51:27.220034+0800 01-Block本质[15081:722988] Block的循环引用
2018-07-08 10:51:27.220173+0800 01-Block本质[15081:722988] -[RevanPerson dealloc]
  • 使用__weak来解决了Block的循环引用
  • 源码分析
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
    //Block中捕获的weakPerson对象是使用__weak修饰
  RevanPerson *__weak weakPerson;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, RevanPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 此时的内存图
    Block中使用__weak修饰的对象.png
  • 由于RevanPerson对象没有被强指针引用所以RevanPerson对象会销毁。
    没有被任何强引用指向的Block.png
  • 最后Block也销毁

2.2、使用__unsafe_unretained修饰person对象,在Block中使用经过__unsafe_unretained修饰的person对象

  • 测试代码
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person = [[RevanPerson alloc] init];
    __unsafe_unretained typeof(person) weakPerson = person;
    person.revan_b = ^{
        NSLog(@"block中使用对象=%@", weakPerson);
    };
    person.revan_b();
    NSLog(@"Block的循环引用");
}

@end
打印输出:
2018-07-08 11:15:09.196428+0800 01-Block本质[15436:739542] block中使用对象=<RevanPerson: 0x60400000d670>
2018-07-08 11:15:09.196622+0800 01-Block本质[15436:739542] Block的循环引用
2018-07-08 11:15:09.196723+0800 01-Block本质[15436:739542] -[RevanPerson dealloc]

2.3、使用__block修饰person对象,在Block中使用经过__block修饰的person对象

#import "ViewController.h"
#import "RevanPerson.h"


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block RevanPerson *person = [[RevanPerson alloc] init];
    person.revan_b = ^{
        NSLog(@"block中使用对象=%@", person);
        person = nil;
    };
    person.revan_b();
    NSLog(@"Block的循环引用");
}

@end
打印输出:
2018-07-08 11:18:03.735457+0800 01-Block本质[15505:742290] block中使用对象=<RevanPerson: 0x604000005590>
2018-07-08 11:18:03.735674+0800 01-Block本质[15505:742290] Block的循环引用
2018-07-08 11:18:03.735842+0800 01-Block本质[15505:742290] -[RevanPerson dealloc]
  • 源码分析
  • __ViewController__viewDidLoad_block_impl_0结构体
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
    //使用__block修饰的weakPerson被包装一个__Block_byref_weakPerson_0类型的对象
  __Block_byref_person_0 *person; // by ref
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • __block包装的对象__Block_byref_person_0结构体
struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
    //__block构造的对象强引用person
 RevanPerson *__strong person;
};
  • 内存图
    __block修饰对象造成循环引用.png
  • Block内部执行person = nil;后的内存图
    __block修饰的对象在Block中执行person=nil.png
  • 小结
    • 推荐使用__weak,当释放对象后__weak会自动把曾经指向对象的指针赋值为nil
    • __unsafe_unretained弊端在于释放对象后不会吧曾经指向对象的指针自动赋值为nil,这样会造成野指针访问
    • __block的弊端在于要在Block回调方法中手动给对象赋值为nil,并且必须要执行这个Block,否则会存在内存泄露

3、MRC下,Block的循环引用

3.1、循环引用

  • 1、RevanPerson
#import <Foundation/Foundation.h>

typedef void(^RevanBlock)(void);

@interface RevanPerson : NSObject
@property (nonatomic, copy) RevanBlock revan_b;
@end

#import "RevanPerson.h"

@implementation RevanPerson

- (void)dealloc {
    [super dealloc];
    NSLog(@"%s", __func__);
}

@end
  • 2、测试代码
#import "ViewController.h"
#import "RevanPerson.h"


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person = [[RevanPerson alloc] init];
    person.revan_b = ^{
        NSLog(@"block中使用对象=%@", person);
    };
    
    person.revan_b();
    [person release];
    NSLog(@"MRC Block的循环引用");
}

@end
打印输出:
2018-07-08 11:51:02.246755+0800 01-Block本质[16083:766749] block中使用对象=<RevanPerson: 0x600000206be0>
2018-07-08 11:51:02.246974+0800 01-Block本质[16083:766749] MRC Block的循环引用

3.2、解决Block循环引用

3.2.1、__unsafe_unretained修饰对象

  • 测试代码
tation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __unsafe_unretained RevanPerson *person = [[RevanPerson alloc] init];
    person.revan_b = ^{
        NSLog(@"block中使用对象=%@", person);
    };
    
    person.revan_b();
    [person release];
    NSLog(@"MRC Block的循环引用");
}

@end
打印输出:
2018-07-08 11:58:07.785725+0800 01-Block本质[16252:773066] block中使用对象=<RevanPerson: 0x60400000b8e0>
2018-07-08 11:58:07.785945+0800 01-Block本质[16252:773066] -[RevanPerson dealloc]
2018-07-08 11:58:07.786089+0800 01-Block本质[16252:773066] MRC Block的循环引用
  • 原理和ARC下一样

3.2.2、__block修饰对象

  • 测试代码
#import "ViewController.h"
#import "RevanPerson.h"


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block RevanPerson *person = [[RevanPerson alloc] init];
    person.revan_b = ^{
        NSLog(@"block中使用对象=%@", person);
    };
    
    person.revan_b();
    [person release];
    NSLog(@"MRC Block的循环引用");
}

@end
打印输出:
2018-07-08 12:00:22.196453+0800 01-Block本质[16323:775301] block中使用对象=<RevanPerson: 0x60000001a160>
2018-07-08 12:00:22.196673+0800 01-Block本质[16323:775301] -[RevanPerson dealloc]
2018-07-08 12:00:22.197707+0800 01-Block本质[16323:775301] MRC Block的循环引用
  • 在MRC下使用__block时,对对象包装成的__Block对象不会对对象(person)产生强引用,所以不会造成循环引用。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容