Runtime实战使用篇

概述

Objective-C Runtime是一个运行时库,它为Objective-C语言的动态属性提供支持,所有Objective-C应用程序都链接到该库。Objective-C运行时库支持功能是在共享库中实现。
详见:苹果开发者文档

废话少说,直接上代码!

应用举例

1.动态创建一个类,并创建成员变量和方法

//自定义方法
void PlayMethod(id self, SEL _cmd, id some)
{
    NSLog(@"%@岁的%@玩:%@", object_getIvar(self, class_getInstanceVariable([self class], "_age")),[self valueForKey:@"name"],some);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //动态创建对象 ,继承于NSObject
        Class Animal=objc_allocateClassPair([NSObject class], "Annial", 0);
        
        // 为该类添加NSString *_name成员变量
        class_addIvar(Animal, "_name", sizeof(NSString*), sizeof(NSString*), @encode(NSString*));
        
        class_addIvar(Animal, "_age", sizeof(int), sizeof(int), @encode(int));
        
        // 注册方法名为play的方法
        SEL s=sel_registerName("_play:");
        
        // 为该类增加名为play的方法
        class_addMethod(Animal, s, (IMP)PlayMethod, "�v@:@");
        
        // 注册该类
        objc_registerClassPair(Animal);
        
        //创建一个实例
        id catAnimal=[[Animal alloc]init];
        
        // KVC 动态改变 对象peopleInstance 中的实例变量
        [catAnimal setValue:@"小猫" forKey:@"name"];
        
        // 从类中获取成员变量Ivar
        Ivar ageIvar=class_getInstanceVariable(Animal, "_age");
        
        object_setIvar(catAnimal, ageIvar, @2);
        
        ((void (*)(id, SEL, id))objc_msgSend)(catAnimal, s, @"篮球");
        
        //因此这里要先销毁实例对象后才能销毁类;
        catAnimal = nil;
        
        // 销毁类
        objc_disposeClassPair(Animal);
         
        // insert code here...

    }
    return 0;
}

打印可得到

2017-07-20 15:15:32.115435+0800 RuntimeTest1[1721:166720] 2岁的小猫玩:篮球

2.获取所有成员变量

.h文件
#import <Foundation/Foundation.h>

@interface Annimal : NSObject
{
    NSString *_color;
    NSString *_type;
    NSString *_test;
    
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;
- (NSDictionary *)getallProperties;
- (NSDictionary *)getallIvars;
- (NSDictionary *)getallMethods;
@end
.m文件
#import "Annimal.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif

@implementation Annimal

//获取所有属性
- (NSDictionary *)getallProperties
{
    unsigned int count = 0;
    
    // 获取类的所有属性,如果没有属性count就为0
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    NSMutableDictionary *resultDict = [@{} mutableCopy];
    
    for (NSUInteger i = 0; i < count; i ++) {
        
        // 获取属性的名称和值
        const char *propertyName = property_getName(properties[i]);
        NSString *name = [NSString stringWithUTF8String:propertyName];
        id propertyValue = [self valueForKey:name];
        
        if (propertyValue) {
            resultDict[name] = propertyValue;
        } else {
            resultDict[name] = @"字典value不能为nil";
        }
    }
    
    // 这里properties是一个数组指针,我们需要使用free函数来释放内存。
    free(properties);
    
    return resultDict;
}

//获取所有成员变量
- (NSDictionary *)getallIvars
{
    unsigned int count = 0;
    
    NSMutableDictionary *resultDict = [@{} mutableCopy];
    
    Ivar *ivars = class_copyIvarList([self class], &count);
    
    for (NSUInteger i = 0; i < count; i ++) {
        
        const char *varName = ivar_getName(ivars[i]);
        NSString *name = [NSString stringWithUTF8String:varName];
        id varValue = [self valueForKey:name];
        
        if (varValue) {
            resultDict[name] = varValue;
        } else {
            resultDict[name] = @"字典的value不能为nil";
        }
        
    }
    
    free(ivars);
    
    return resultDict;
}
//获取所有方法
- (NSDictionary *)getallMethods
{
    unsigned int count = 0;
    
    NSMutableDictionary *resultDict = [@{} mutableCopy];
    
    // 获取类的所有方法,如果没有方法count就为0
    Method *methods = class_copyMethodList([self class], &count);
    
    for (NSUInteger i = 0; i < count; i ++) {
        
        // 获取方法名称
        SEL methodSEL = method_getName(methods[i]);
        const char *methodName = sel_getName(methodSEL);
        NSString *name = [NSString stringWithUTF8String:methodName];
        
        // 获取方法的参数列表
        int arguments = method_getNumberOfArguments(methods[i]);
        
        resultDict[name] = @(arguments-2);
    }
    
    free(methods);
    
    return resultDict;
}

@end

打印出来结果为
2017-07-20 15:28:11.357512+0800 RuntimeTest2[1767:178185] propertyName:block, propertyValue:<__NSGlobalBlock__: 0x100002110>
2017-07-20 15:28:11.357761+0800 RuntimeTest2[1767:178185] propertyName:name, propertyValue:狗
2017-07-20 15:28:11.357807+0800 RuntimeTest2[1767:178185] propertyName:age, propertyValue:2
2017-07-20 15:28:11.358441+0800 RuntimeTest2[1767:178185] ivarName:_type, ivarValue:哈士奇
2017-07-20 15:28:11.358488+0800 RuntimeTest2[1767:178185] ivarName:_color, ivarValue:白色
2017-07-20 15:28:11.358510+0800 RuntimeTest2[1767:178185] ivarName:_test, ivarValue:字典的value不能为nil
2017-07-20 15:28:11.358556+0800 RuntimeTest2[1767:178185] ivarName:_name, ivarValue:狗
2017-07-20 15:28:11.358602+0800 RuntimeTest2[1767:178185] ivarName:_age, ivarValue:2
2017-07-20 15:28:11.358726+0800 RuntimeTest2[1767:178185] methodName:setName:, argumentsCount:1
2017-07-20 15:28:11.358762+0800 RuntimeTest2[1767:178185] methodName:block, argumentsCount:0
2017-07-20 15:28:11.358822+0800 RuntimeTest2[1767:178185] methodName:getallProperties, argumentsCount:0
2017-07-20 15:28:11.358844+0800 RuntimeTest2[1767:178185] methodName:getallMethods, argumentsCount:0
2017-07-20 15:28:11.358857+0800 RuntimeTest2[1767:178185] methodName:getallIvars, argumentsCount:0
2017-07-20 15:28:11.358883+0800 RuntimeTest2[1767:178185] methodName:setBlock:, argumentsCount:1
2017-07-20 15:28:11.359527+0800 RuntimeTest2[1767:178185] methodName:age, argumentsCount:0
2017-07-20 15:28:11.359552+0800 RuntimeTest2[1767:178185] methodName:setAge:, argumentsCount:1
2017-07-20 15:28:11.359568+0800 RuntimeTest2[1767:178185] methodName:.cxx_destruct, argumentsCount:0
2017-07-20 15:28:11.359581+0800 RuntimeTest2[1767:178185] methodName:name, argumentsCount:0
Program ended with exit code: 0

这一看很方便吧,有什么用呢?不急,后面的数据与模型转换,归档解档就用上了,慢慢来!

3.动态生成方法

// 判断对象方法有没有实现
 +(BOOL)resolveInstanceMethod:(SEL)sel
 
 // 判断类方法有没有实现
 + (BOOL)resolveClassMethod:(SEL)sel
 
 
 3, 进入苹果系统内部查看如何动态添加方法(这是官方文档中的方法)
 // dynamicMethodIMP方法
 // 动态添加这个dynamicMethodIMP方法
 void dynamicMethodIMP(id self, SEL _cmd) {
 // implementation ....
 }
 
 // 苹果内部的动态添加方法
 @implementation MyClass
 + (BOOL)resolveInstanceMethod:(SEL)aSEL
 {
 if (aSEL == @selector(resolveThisMethodDynamically)) {
 class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
 return YES;
 }
 return [super resolveInstanceMethod:aSEL];
 }

#import <Foundation/Foundation.h>
@interface Student : NSObject
@end
void play(id self,SEL sel)
{
    
    NSLog(@"%@,%@",self,NSStringFromSelector(sel));
    
}

//默认方法都有两个隐式参数
/*
2, C函数中两个隐式参数的意思 :
1, self:方法调用者
_cmd:当前调用方法编号
方法的隐式参数即: 没有暴露出来参数.
*/

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    
    if (sel==NSSelectorFromString(@"play")) {
        
        class_addMethod(self, sel,(IMP)play,"v@:");
    }
    
    return [super resolveInstanceMethod:sel];
}
Student *student=[Student new];
[student performSelector:@selector(play) withObject:nil afterDelay:1];
2017-07-20 15:39:21.737 BobRunTimeDemo[1997:188750] <Student: 0x60800000a4c0>,play

如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。

4.关联对象

这个就用的比较多了,通过这个方法可以给类别添加属性,我们使用的上下拉刷新就是通过这种方式实现的。
具体不多说,直接上代码。

#import <UIKit/UIKit.h>
@interface UIViewController (addProperty)
@property(nonatomic,copy)NSString*name;
@end
#import "UIViewController+addProperty.h"
#import <objc/runtime.h>
static const char name_key;

-(NSString*)name{
   return objc_getAssociatedObject(self, &name_key);
}
-(void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, &name_key, name, OBJC_ASSOCIATION_COPY);
}

 self.name=@"888";
            
 NSLog(@"%@",self.name);
2017-07-20 16:03:00.557 BobRunTimeDemo[2152:206973] 888

5.归档解档

通过获取所有属性的key和value值进行了归档和解档,避免了一个个属性去操作,那样太麻烦了。通过runtime可以一步到位。

-(void)encodeWithCoder:(NSCoder *)aCoder
{
    unsigned int count=0;
    
    Ivar *ivars=class_copyIvarList([self class], &count);
    
    NSLog(@"%d",count);
    
    for (NSUInteger i = 0; i < count; i ++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
    free(ivars);
}

-(id)initWithCoder:(NSCoder *)aDecoder
{
    
    unsigned int count=0;
    
    Ivar *ivars=class_copyIvarList([self class], &count);
    
    for (NSUInteger i = 0; i < count; i ++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [aDecoder decodeObjectForKey:key];
        [self setValue:value forKey:key];
        
    }
    free(ivars);
    
    return self;
    
}

#import "Animal.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Animal *anni=[[Animal alloc]init];
        anni.name=@"狗";
        anni.age=@2;
        anni.color=@"黄色";
        anni.type=@"阿拉斯加";
        NSString *path=NSTemporaryDirectory();
        path=[path stringByAppendingPathComponent:@"dog.dog"];
        NSLog(@"%@",path);
        [NSKeyedArchiver archiveRootObject:anni toFile:path];
        Animal *newAnimal=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
        NSLog(@"%@-%@-%@-%@",newAnimal.name,newAnimal.age,newAnimal.type,newAnimal.color);

    }
    return 0;
}

2017-07-20 15:32:45.444895+0800 RuntimeTest3[1810:182292] /var/folders/5d/43zfbw550rvc1cjsq_0bt4h80000gn/T/dog.dog
2017-07-20 15:32:45.446739+0800 RuntimeTest3[1810:182292] 4
2017-07-20 15:32:45.448192+0800 RuntimeTest3[1810:182292] 狗-2-阿拉斯加-黄色

个人封装了一下,只要类继承下,就能调用方法进行归档和解档了,有兴趣的可以看一下,demo在后面。

6.方法交换

如果开发中,某些系统的方法不符合我们的要求,而我们有不想一个个作判断,runtime的方法交换就能实现。列举了2个例子:

1.图片为空时,加载默认图

实例一
#import "UIImageView+PlaceHolderImage.h"
#import <objc/runtime.h>
@implementation UIImageView (PlaceHolderImage)

+ (void)load
{
    // 获取 UIImage 方法 -imageNamed: 的 Method
    Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
    
    // 获取 UIImage+PlaceHolderImage 方法 -replaced_imageNamed: 的 Method
    Method replaced_imageNamedMethod = class_getClassMethod(self, @selector(replaced_imageNamed:));
    
    // 将两个方法进行交换,现在如果调用 -imageNamed: 则调用的是下方 +replaced_imageNamed: 的实现
    method_exchangeImplementations(imageNameMethod, replaced_imageNamedMethod);
}

+ (UIImage *)replaced_imageNamed:(NSString *)imageName
{
    // 这里是递归调用吗?不是。因为现在调用 +replaced_imageNamed: 实现则是苹果框架内的  -imageNamed: 的实现。
    UIImage *image = [[UIImage class] replaced_imageNamed:imageName];
    if (!image)
    {
        image = [[UIImage class] replaced_imageNamed:@"placeImage"];
    }
    return image;
}

@end

2.数组可加入nil,而不崩溃

实例二
#import "NSMutableArray+safe.h"
#import <objc/runtime.h>
@implementation NSMutableArray (safe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        id obj = [[self alloc] init];
        [obj swizzleMethod:@selector(addObject:) withMethod:@selector(safeAddObject:)];
        [obj swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(safeObjectAtIndex:)];

    });
}

- (void)safeAddObject:(id)anObject
{
    if (anObject) {
        [self safeAddObject:anObject];
    }else{
        NSLog(@"obj is nil");
        
    }
}

- (id)safeObjectAtIndex:(NSInteger)index
{
    if(index<[self count]){
        return [self safeObjectAtIndex:index];
    }else{
        NSLog(@"index is beyond bounds ");
    }
    return nil;
}


- (void)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
    Class class = [self class];
    
    Method originalMethod = class_getInstanceMethod(class, origSelector);
    Method swizzledMethod = class_getInstanceMethod(class, newSelector);
    
    BOOL didAddMethod = class_addMethod(class,
                                        origSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(class,
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end

 NSMutableArray *arr=[NSMutableArray array];
            
 [arr addObject:nil];
2017-07-20 16:06:01.881 BobRunTimeDemo[2166:209480] obj is nil

哈哈体会到Runtime的强大了吧! 当然方法有很多,大家按需求去实现就好了。

7.JSON与Model互转

对于数据模型转换,大家都用过比如jsonmodel,MJExtension,等第三方库,如果自己写,怎么写呢?我这里就写了个简单的字典转换,复杂的大家去研究吧!只是提供这种思想而已,不重复造轮子了。

#import <Foundation/Foundation.h>

@interface Animal : NSObject
@property (nonatomic, copy) NSString *name; // 姓名
@property (nonatomic, strong) NSNumber *age; // 年龄
@property (nonatomic, copy) NSString *color; //颜色
@property (nonatomic, copy) NSString *type; //品种

// 生成model
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;

// 转换成字典
- (NSDictionary *)ModelToDictionary;

@end

#import "Animal.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation Animal
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
    self = [super init];
    
    if (self) {
        for (NSString *key in dictionary.allKeys) {
            
            id value = dictionary[key];
            
            SEL setter = [self propertySetterByKey:key];
            
            if (setter) {
                
                ((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);
            }
        }
    }
    return self;
}

- (NSDictionary *)ModelToDictionary
{
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    if (count != 0) {
        NSMutableDictionary *resultDict = [@{} mutableCopy];
        
        for (NSUInteger i = 0; i < count; i ++) {
            const void *propertyName = property_getName(properties[i]);
            NSString *name = [NSString stringWithUTF8String:propertyName];
            
            SEL getter = [self propertyGetterByKey:name];
            if (getter) {
                id value = ((id (*)(id, SEL))objc_msgSend)(self, getter);
                if (value) {
                    resultDict[name] = value;
                } else {
                    resultDict[name] = @"字典的value不能为nil";
                }
                
            }
        }
        
        free(properties);
        
        return resultDict;
    }
    
    free(properties);
    
    return nil;
}

#pragma mark - private methods

// 生成setter方法
- (SEL)propertySetterByKey:(NSString *)key
{
    // 首字母大写
    NSString *propertySetterName = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
    
    SEL setter = NSSelectorFromString(propertySetterName);
    if ([self respondsToSelector:setter]) {
        return setter;
    }
    return nil;
}
// 生成getter方法
- (SEL)propertyGetterByKey:(NSString *)key
{
    SEL getter = NSSelectorFromString(key);
    if ([self respondsToSelector:getter]) {
        return getter;
    }
    return nil;
}

@end

//使用
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
   
        NSDictionary *dict = @{
                               @"name" : @"猫",
                               @"age"  : @1,
                               @"color" : @"白色",
                               @"type" : @"波斯猫"
                               };
        
        Animal *dog=[[Animal alloc]initWithDictionary:dict];
        
        NSLog(@"%@,%@,%@,%@",dog.name,dog.age,dog.color,dog.type);
        
        NSDictionary *newdict=[dog ModelToDictionary];
        
        NSLog(@"%@",newdict);

    }
    return 0;
}

2017-07-20 16:27:09.165359+0800 RuntimeTest4[596:9548] 猫,1,白色,波斯猫
2017-07-20 16:27:09.165845+0800 RuntimeTest4[596:9548] {
    age = 1;
    color = "\U767d\U8272";
    name = "\U732b";
    type = "\U6ce2\U65af\U732b";
}
Program ended with exit code: 0

8.block回调

大家肯定用过BlocksKit,这个库呢,让代码非常简洁,有些原理也是用Runtime实现的,大家可以去研究下,下面就拿个例子来说吧!

1.手势回调

#import <UIKit/UIKit.h>

typedef void(^BobTapGestureBlock)(id gestureRecongnizer);

@interface UIGestureRecognizer (BobGestureBlock)

+(instancetype)TapgestureWithBlock:(BobTapGestureBlock)block;
@end
#import "UIGestureRecognizer+BobGestureBlock.h"
#import <objc/runtime.h>

static const char target_key;

@implementation UIGestureRecognizer (BobGestureBlock)

+(instancetype)TapgestureWithBlock:(BobTapGestureBlock)block
{
    
    return [[self alloc]initWithActionBlock:block];
    
}


-(instancetype)initWithActionBlock:(BobTapGestureBlock)block
{
    
    self=[self init];

        if (block) {
            
            objc_setAssociatedObject(self, &target_key, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
   
                  }
    
    [self addTarget:self action:@selector(tapAction:)];

   
    return self;
}
-(void)tapAction:(id)sender
{
    BobTapGestureBlock block=objc_getAssociatedObject(self, &target_key);
    if (block) {
        block(sender);
    }
}

@end
//直接一句代码就解决了
  [self.view addGestureRecognizer:[UITapGestureRecognizer TapgestureWithBlock:^(id gestureRecongnizer) {
        NSLog(@"点击");
    }]];
2017-07-20 16:14:59.074 BobRunTimeDemo[2166:209480] 点击

终于写完了,写篇文章真不容易啊!

最后附上demo下载地址RuntimeDemo下载,如果觉得写的不错,就给个star吧!

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 对于从事 iOS 开发人员来说,所有的人都会答出【runtime 是运行时】什么情况下用runtime?大部分人能...
    梦夜繁星阅读 3,697评论 7 64
  • 1 如果一个人老去,当然我指的是已经很老很老,老到手脚不便、行动困难,那么对他来讲,最奢侈的事莫过于有尊严的活着了...
    止水之鱼阅读 500评论 0 3
  • 原本以为考上大学是一件快乐的事情,来到这里后,寝室旁边住着的大叔让我慌张,垃圾桶溢出的垃圾让我恶心,到处可见的...
    一次两次zlt阅读 204评论 0 1