NSObject协议及本身详解

 NSObject作为一个基类,这个类遵守了NSObject协议,并且实现了NSObject协议里的所有方法,所以NSObject类及其子类都可以调用这些方法。下面主要介绍NSObject协议里的属性和方法。

一、NSObject协议

  • - (BOOL)isEqual:(id)object;:比较两个对像是否相同。比较的是成员变量的值是否相同,这与 ==符号有很大的不同:在对对象进行比较时,==符号判断是否是同一个对象,比较的是内存地址。
UIImageView * imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"loli"]];
UIImage * image = [UIImage imageNamed:@"loli"];

NSLog(@"imageView.image == image : %d", (imageView.image == image));
NSLog(@"[imageView.image isEqual:image] : %d",[imageView.image isEqual:image]);

结果

imageView.image == image : 0
[imageView.image isEqual:image] : 1
  • @property (readonly) NSUInteger hash;:对象的哈希值,只读属性,具有唯一性。- (NSUInteger)hash方法只在对象被添加至NSSet和设置为NSDictionary的key时会调用。

  • @property (readonly) Class superclass;:获取父类。

  • - (Class)class:获取自身所属的类。

  • - (instancetype)self;:获取对象自己。

  • - (id)performSelector:(SEL)aSelector;- (id)performSelector:(SEL)aSelector withObject:(id)object;- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;:对象响应某一方法,object是传递给响应方法的参数。

    • performSelector是程序 运行时 系统负责去找方法,在编译时不做任何校验。如果方法由对象直接调用,编译时会自动校验。Cocoa支持在运行时向某个类添加方法,即方法编译时不存在,但是运行时候存在,这时候必然需要使用performSelector去调用。所以有时候如果使用了performSelector,为了使程序能正常运行,一般会使用检查方法- (BOOL)respondsToSelector:(SEL)aSelector;先行检验一下,针对不同情况做出正确的应对措施。
    • 这三个方法,均为同步执行,与线程无关,主线程和子线程中均可调用成功,等同于直接调用该方法。
- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelector:@selector(sendSomeThing:antherImage:) withObject:[UIImage imageNamed:@"boy"] withObject:[UIImage imageNamed:@"poem"]];
}

- (void)sendSomeThing:(UIImage *)aImage antherImage:(UIImage *)bImage
{
    NSLog(@"%@, %@", aImage, bImage);
}
  • - (BOOL)isProxy;:判断是否是NSProxy的实例。

  • - (BOOL)isKindOfClass:(Class)aClass;:判断某个对象是否是某个类或者子类的实例。

  • - (BOOL)isMemberOfClass:(Class)aClass;:判断某个对象是否是某个类的实例。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    UIImageView * imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"loli"]];
    NSLog(@"isKindOfClass : %d", [imageView isKindOfClass:[UIView class]]);
    NSLog(@"isMemberOfClass : %d", [imageView isMemberOfClass:[UIView class]]);
    NSLog(@"isMemberOfClass : %d", [imageView isMemberOfClass:[UIImageView class]]);
}

结果:

isKindOfClass : 1
isMemberOfClass : 0
isMemberOfClass : 1
  • - (BOOL)conformsToProtocol:(Protocol *)aProtocol;:是否遵循了某一协议,至于是否实现了协议中的方法,该方法不关心。
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    NSLog(@"UIViewAnimating : %d", [self conformsToProtocol:@protocol(UIViewAnimating)]);
    NSLog(@"UITableViewDelegate : %d", [self conformsToProtocol:@protocol(UITableViewDelegate)]);
}

结果:

UIViewAnimating : 1
UITableViewDelegate : 0
  • - (BOOL)respondsToSelector:(SEL)aSelector;:是否 能响应 某一方法,若能则返回YES,不能返回NO。这里需要特别注意:类对象(Object)只能响应类方法(+方法),实例对象(object)只能响应实例方法(-方法)
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    NSLog(@"respondsToSelector : %d", [self respondsToSelector:@selector(viewWillAppear:)]);
    NSLog(@"respondsToSelector : %d", [self respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]);
}

结果

respondsToSelector : 1
respondsToSelector : 0
  • @property (readonly, copy) NSString *description;@property (readonly, copy) NSString *debugDescription;description是代码打印输出 实例 的时候调用的方法,debugDescription是控制台也就是po的时候输出实例的时候调用的方法,都可以自定义。

关于description/debugDescription方法(下面以description为例说明):
 description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址),这样的话,使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法的默认实现。
 description有一个实例方法-(NSString *)description;(NSLog输出该类的实例对象时调用),一个类方法+(NSString *)description;(NSLog输出该类的类对象时调用)。

// 未重写description方法情况
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
NSLog(@"%@",[person_1 class]);
// 输出结果
<Person: 0x60000003a700>
Person
// 重写description方法情况
// Person.m
-(NSString *)description
{
    return @"ASD";
}
+(NSString *)description
{
    return @"QWE";
}
// 其他类调用
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
NSLog(@"%@",[person_1 class]);
// 输出结果
ASD
QWE

特别注意:在重写的description方法中不能同时使用%@ 和self。

// 下面这种写法会导致循环引用,抛出异常
-(NSString *)description
{
    return [NSString stringWithFormat:@"%@", self];
}

但是有个例外情况当在该方法内部打印时同时使用会导致该方法连续调用三次后自动结束。

// Person.m
-(NSString *)description
{
    NSLog(@"self = %@", self);
    return @"ASD";
}
// 其他类调用
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
// 输出结果
self = ASD
self = ASD
self = ASD
ASD

二、NSObject基类

  • + (void)initialize;类方法,在该类收到第一条消息前初始化该类。

1、 -- > 一般情况下,每个类的这一方法只调用一次。

@implementation FatherView

+ (void)initialize
{
    NSLog(@"father -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end


@implementation OneViewController

- (void)viewWillAppear:(BOOL)animated
{
    FatherView * fatherView = [[FatherView alloc] init];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    FatherView * fatherView = [[FatherView alloc] init];
}

@end

运行打印结果只会打印一次:结果为

father -->  self == FatherView, functionString == +[FatherView initialize]

2、 -- > 超类在它们的子类之前收到这个消息。

@implementation FatherView

+ (void)initialize
{
    NSLog(@"father -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end


@implementation BodyView

+ (void)initialize
{
    NSLog(@"body -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end


- (void)viewDidLoad {
    [super viewDidLoad];
    BodyView * bodyView = [[BodyView alloc] init];
}

运行打印结果为:

father -->  self == FatherView, functionString == +[FatherView initialize]
body -->  self == BodyView, functionString == +[BodyView initialize]

3、 -- > 如果子类不实现+(void)initialize方法或者如果子类显式调用[super initialize],超类的实现可能会被多次调用 。

// 如上面2、中示例所示,将BodyView.m中的 + (void)initialize方法注释掉再运行。
// 结果为:
/**
father -->  self == FatherView, functionString == +[FatherView initialize]
father -->  self == BodyView, functionString == +[FatherView initialize]
*/

4、--> 如果想使类本身+(void) initialize方法保护自己部分内容不被多次运行,可以按照以下方式构建实现:

@implementation FatherView

+ (void)initialize
{
    NSLog(@"会调用好几次~~");
    if (self == [FatherView self]) {
        NSLog(@"father -->  self == %@, functionString == %s", [self class], __FUNCTION__);
    }
}

@end


@implementation BodyView

// 将该方法注销掉
//+ (void)initialize
//{
//    NSLog(@"body -->  self == %@, functionString == %s", [self class], __FUNCTION__);
//}

@end


@implementation OneViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    BodyView * bodyView = [[BodyView alloc] init];
}

@end

运行结果:

会调用好几次~~
father -->  self == FatherView, functionString == +[FatherView initialize]
会调用好几次~~

可以看到在第二次调用父类的initialize方法时if内部代码没有再次运行。

5、-- > 如果类包含分类,且分类重写initialize方法,那么则会调用分类的 initialize 实现,而原类的该方法实现不会被调用,这个机制同 NSObject 的其他方法一样(除 + (void)load 方法外) 。在这不在举例演示。

  • + (void)load;每当将类或类别添加到Objective-C runtime时调用。该方法在+(void)initialize方法后调用。

    • 超类+(void)load方法先于子类方法的调用;

    • 如果分类也实现了+(void)load方法,那么该方法也会调用,但是是在类本身的+(void)load方法调用之后!

@implementation BodyView

+ (void)load
{
    NSLog(@"body_load -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end


@implementation BodyView (ImageView)

+(void)load
{
    NSLog(@"BodyView+ImageView_load -->  self == %@, functionString == %s", [self class], __FUNCTION__);
}

@end

打印结果:

body_load -->  self == BodyView, functionString == +[BodyView load]
BodyView+ImageView_load -->  self == BodyView, functionString == +[BodyView(ImageView) load]


重点注意:无论是 +(void)initialize方法还是 +(void)load方法,均是在系统运行时执行的,调用会发生在-(BOOL)application:willFinishLaunchingWithOptions: 调用之前调用。可以依此对该方法进行瘦身 -- > 可以看一下Sunny的这篇文章Notification Once,在此表示感谢!。

  • + (instancetype)alloc/ + (instancetype)allocWithZone:(struct _NSZone *)zone--在使用时参数传nil /- (instancetype)init+ (instancetype)new:初始化方法

  • - (id)copy:返回NSCopying协议方法- (id)copyWithZone:(nullable NSZone *)zone;返回的对象。NSObject本身不支持NSCopying协议,其子类若想实现该方法就必须实现NSCopying协议并实现- (nonnull id)copyWithZone:(nullable NSZone *)zone方法,否者会报错:-[子类 copyWithZone:]: unrecognized selector sent to instance ........

  • - (id)mutableCopy;:返回NSMutableCopying协议方法- (id)mutableCopyWithZone:(nullable NSZone *)zone在参数为nil情况下返回的对象。其子类若想实现该方法就必须实现NSMutableCopying协议并实现- (id)mutableCopyWithZone:(nullable NSZone *)zone方法,否者会报错:-[子类 mutabelCopyWithZone:]: unrecognized selector sent to instance .......

    • 在这里引申出深拷贝和浅拷贝含义:

      • 浅拷贝:就是对指针的拷贝,但是目标对象指针和源对象指针指向同一片内存空间。
      • 深拷贝:是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象的值虽然是相同的,但是内存地址不一样,两个对象互不影响,互不干涉。
      • 主要常见的涉及深拷贝浅拷贝的类有:NSStringNSMutableStringNSArrayNSMutableArrayNSDictionaryNSMutableDictionary。它们都已经遵循了NSCopying或者NSMutableCopying协议,对于不可变类(NS····)copy即浅拷贝,mutableCopy即为深拷贝;对于可变类(NSMutable····)无论copy还是mutableCopy都是深拷贝。
      • 其实对于所有(NS *)和(NSMutable *)上述结论都适用。对于所有父类是NSObject的自定义类而言,无论copy还是mutableCopy都是深拷贝。
  • - (void)dealloc:释放系统无法释放的对象占用的资源。常见关于dealloc的问题:

    • 现在ARC下不需要调用[super dealloc];系统会自动帮你调用;

    • 可以用来释放通知或者KVO的观察者:通知中心是系统的单例,当在注册通知的观察者时,实际上是在通知中心注册的,这样在界面dismiss后即使ARC下系统帮我们释放了对象,但是在通知中心的观察还是没有移除,那么当有该通知时,依然会尝试调用该对象的接受通知的方法,这可能会导致一些问题。在控制器中也可以在- (void)viewDidDisappear:(BOOL)animated中释放。

    • 在控制器dismiss或者pop后dealloc不调用问题:

      • 可能是使用了NSTimer导致的,需要在- (void)viewWillDisappear:(BOOL)animated;或者- (void)viewDidDisappear:(BOOL)animated;方法中调用[timer invalidate]即可。
      • 代理属性设置为strong的情况下可能发生循环引用问题导致无法释放,自然也不会调用该方法。
      • 控制器中存在Block的循环引用问题。也会造成dealloc方法不调用问题。常见block中使用self的问题 --- >__weak Viewcontroller * weakSelf = self;
  • + (BOOL)isSubclassOfClass:(Class)aClass:返回一个布尔值,该值指示接收类是否是给定类的子类或完全相同。

  • + (NSUInteger)hash:类对象的哈希值。

  • + (Class)superclass:返回接收者超类的类对象。

  • + (Class)class:返回类对象。


下面是与函数调用和runtime相关方法:OC是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IMP)组成的。Selector相当于索引,而Implement才是真正的函数实现。

  • + (BOOL)instancesRespondToSelector:(SEL)aSelector;:返回一个布尔值,指示类本身是否能够响应给定的选择器。注意与- (BOOL)respondsToSelector:(SEL)aSelector;方法的区别!!!用法参考上面respondsToSelector用法。

  • + (BOOL)conformsToProtocol:(Protocol *)protocol是否遵循了某一协议。详细见上面object协议中方法描述,唯一不同即这是一个类方法。

  • - (IMP)methodForSelector:(SEL)aSelector;+ (IMP)instanceMethodForSelector:(SEL)aSelector; 定位并返回接收方执行方法的地址,以便将其作为函数调用。

    • IMP是真正的函数指针,SEL只是索引。

    • 参数SEL必须是有效的,所以可以在调用此方法之前使用- (BOOL)respondsToSelector:(SEL)aSelector;或者+ (BOOL)instancesRespondToSelector:(SEL)aSelector进行检验。

    • 如果调用者是一个实例,应该参考一个实例方法; 如果调动者是一个类,它应该引用一个类的方法!!!

为了方便了解下面的方法,下面先给出选择器执行函数的过程图:

添加:
  • + (BOOL)resolveClassMethod:(SEL)sel:解析类方法,动态地为类方法的给定选择器提供实现。

  • + (BOOL)resolveInstanceMethod:(SEL)sel:解析实例方法,动态地为实例方法的给定选择器提供实现。

     对于上面两个方法而言:

    • 如果参数方法被发现并且被添加到接收者,则返回YES,否则,返回NO;
    • 这个函数在运行时(runtime)如果没有找到SEL的IML,就会执行。这个函数是给类利用class_addMethod添加函数的机会。
    • 如果类重写了该函数,并使用class_addMethod()添加了相应的selector,并返回YES,那么后面forwardingTargetForSelector等方法就不会被调用,如果在该函数中没有添加相应的selector,那么不管返回什么,后面都会继续调用相关方法或者爆出异常。
#import <objc/runtime.h>

void addNewMethod()
{
    NSLog(@"method == %s", __FUNCTION__);
}

@implementation BodyView

   + (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"解析实例方法 == %s", __FUNCTION__);
//    if (![self respondsToSelector:sel]) { //该方法中不可使用respondsToSelector:
//    或者instancesRespondToSelector:方法,这两个方法参数SEL在类中如果没有实现,
//    也会引起resolveInstanceMethod方法的调用!!!
    if (sel == @selector(forwardMethod)) {
        class_addMethod([self class], sel, (IMP)addNewMethod, "v@:");
        NSLog(@"记录次数");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end
转发:
  • - (id)forwardingTargetForSelector:(SEL)aSelector返回无法识别的消息首先被指向的对象。

    • 当某个对象不能接受某个selector时,将对该selector的调用转发给另一个对象即返回的对象。但是该方法不建议返回nil(若返回nil则当消息无法识别时会抛出异常)或者self(会造成死循环)。这个方法在这一消息被一个更昂贵的forwardInvocation:方法接管之前,给了一个对象一个机会来重定向发送给它的未知消息。

    • 如果你在一个非root类中实现这个方法,如果你的类对于给定的选择器没有任何返回值,那么你应该返回调用super的实现的结果。

    • 仅支持一个对象的返回,也就是说消息只能被转发给一个对象。

@interface FatherView : UIView

- (void)forwardMethod;

@end

@implementation FatherView

- (void)forwardMethod
{
    NSLog(@"fatherView ---- forwardMethod");
}

@end


@implementation BodyView

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    FatherView * aView = [FatherView new];
    if ([aView respondsToSelector:@selector(forwardMethod)]) {
        return aView;
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end


@implementation OneViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    BodyView * bodyView = [[BodyView alloc] init];
    [bodyView performSelector:@selector(forwardMethod)];
}

@end

如果上述例子中将BodyView中的- (id)forwardingTargetForSelector:(SEL)aSelector方法注释掉,再次运行会抛出异常-[BodyView forwardMethod]: unrecognized selector sent to instance *******

  • - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;方法签名。返回一个包含由给定选择器方法标识的描述的NSMethodSignature对象,如果aSelector没找到就返回nil。一般复写用于runtime提供方法签名。

  • + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;同样是方法签名,但是一般不复写,只是调用,用于获取类方法签名。

  • - (void)forwardInvocation:(NSInvocation *)anInvocation:由子类覆盖以将消息转发给其他对象。

    • 为了响应你的对象本身不能识别的方法,除了forwardInvocation:方法外,还必须重写- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法,转发消息的机制是使用从methodSignatureForSelector:方法返回的信息创建要转发的NSInvocation对象。

    • forwardInvocation:方法的实现有两个任务:

      • 查找可以响应参数anInvocation中编码的消息的对象。
      • 向使用参数anInvocation的对象发送消息。参数anInvocation将会保存结果,运行时系统将提取并将此结果传递给原始发件人。
      • 可以实现向多个目标转发消息。
@implementation FatherView

- (void)forwardMethod
{
    NSLog(@"fatherView ---- forwardMethod");
}

@end


@implementation BodyView

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s", __FUNCTION__);
    if (aSelector == @selector(forwardMethod)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"执行转换对象的方法 == %s", __FUNCTION__);
    SEL aSelector = [anInvocation selector];
    FatherView * aView = [FatherView new];
    if ([aView respondsToSelector:aSelector]){
        [anInvocation invokeWithTarget:aView];
    }else{
        [super forwardInvocation:anInvocation];
    }
//    if (anInvocation.selector == @selector(forwardMethod)) {
//        FatherView * aView = [FatherView new];
//        [aView performSelector:@selector(forwardMethod)];
//    }else{
//        [super forwardInvocation:anInvocation];
//    }
}

@end

// 调用
BodyView * bodyView = [[BodyView alloc] init];
[bodyView performSelector:@selector(forwardMethod)];

打印结果:

-[BodyView methodSignatureForSelector:]
执行转换对象的方法 == -[BodyView forwardInvocation:]
fatherView ---- forwardMethod
  • - (void)doesNotRecognizeSelector:(SEL)aSelector;处理接收器无法识别的消息,一般不要重写!!!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容