iOS源码解析:runtime<四> runtime的API

runtime的API有很多,内容很丰富,下面按照类方法,成员变量相关方法,属性相关方法,方法相关等进行说明。

Runtime API01 - 类

与类相关的API有下列这些:

    //动态创建一个类(参数:父类,类名,额外的内存空间)
    Class objc_allocateClassPair(Class  _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes);
    
    //注册一个类(要在类注册之前添加成员变量)
    void objc_registerClassPair(Class  _Nonnull __unsafe_unretained cls);
    
    //销毁一个类
    void objc_disposeClassPair(Class  _Nonnull __unsafe_unretained cls);
    
    //获取isa指向的Class
    Class object_getClass(id  _Nullable obj);
    
    //设置isa指向的Class
    Class object_setClass(id  _Nullable obj, Class  _Nonnull __unsafe_unretained cls);
    
    //判断一个OC对象是否为Class
    BOOL object_isClass(id  _Nullable obj);
    
    //判断一个Class是否为元类
    BOOL class_isMetaClass(Class  _Nullable __unsafe_unretained cls);
    
    //获取父类
    CLass class_getSuperclass(Class  _Nullable __unsafe_unretained cls);

首先看后面几个API。

    //获取isa指向的Class
    Class object_getClass(id  _Nullable obj);

这个API是获取传入对象的isa指针指向的对象,传入的是实例对象则返回类对象,传入的是类对象则返回元类对象。

    //设置isa指向的Class
    Class object_setClass(id  _Nullable obj, Class  _Nonnull __unsafe_unretained cls);

这个API是改变一个对象的isa指针的指向,比如下面代码:

    Person *girl = [[Person alloc] init];
    object_setClass(girl, [Student class]);
    NSLog(@"%@", [girl class]);

调用object_setClass()使girl这个实例对象的isa指针指向Student类对象,所以打印的结果是:

Student
    //判断一个OC对象是否为Class
    BOOL object_isClass(id  _Nullable obj);

这个API用来判断传入的对象是否为类对象,由于元类对象也是类对象的一种,所以这里实际是判断传入的对象是否为类对象或元类对象,实例代码如下:

    Person *girl = [[Person alloc] init];
    Class girlClass = [girl class];
    Class girlMetaClass = object_getClass(girlClass);
    NSLog(@"%d, %d, %d", object_isClass(girl), object_isClass(girlClass), object_isClass(girlMetaClass));

打印结果如下:

0, 1, 1
    //判断一个Class是否为元类
    BOOL class_isMetaClass(Class  _Nullable __unsafe_unretained cls);

这个API用来判断传入的类对象是否是元类对象,实例代码如下:

    Person *girl = [[Person alloc] init];
    Class girlClass = [girl class];
    Class girlMetaClass = object_getClass(girlClass);
    NSLog(@"%d, %d", class_isMetaClass(girlClass), class_isMetaClass(girlMetaClass));

打印结果:

0, 1
    //获取父类
    CLass class_getSuperclass(Class  _Nullable __unsafe_unretained cls);

这个API是传入一个类对象,获取一个父类对象,例如下面的代码:

    Person *girl = [[Person alloc] init];
    Class girlClass = [girl class];
    NSLog(@"%@", class_getSuperclass(girlClass));

打印结果是:

NSObject

接下里再来看前面的三个API:

    //动态创建一个类(参数:父类,类名,额外的内存空间)
    Class objc_allocateClassPair(Class  _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes);
    
    //注册一个类(要在类注册之前添加成员变量)
    void objc_registerClassPair(Class  _Nonnull __unsafe_unretained cls);
    
    //销毁一个类
    void objc_disposeClassPair(Class  _Nonnull __unsafe_unretained cls);

这三个API结合起来动态创建和销毁一个类。
第一个API中的参数分别是要创建的这个类要继承自哪个父类,类名,额外的内存空间(一般传0)。
所以可以像下面这样动态创建一个类:

Class teacherClass = objc_allocateClassPair([Person class], "Teacher", 0);

这就是创建了一个继承自Person类的子类,名称为Teacher。
然后我们可以使用下列API给新创建的类添加成员变量:

BOOL
class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types)

这里的参数中,第一个参数是要传入类对象,第二个参数是成员变量名,第三个是成员变量占的内存空间,第四个是内存对齐,第五个是成员变量的类型,我们可以这样添加成员变量:

//创建了_age和_weight这两个成员变量
    class_addIvar(teacherClass, "_age", 4, 1, @encode(int));
    class_addIvar(teacherClass, "_weight", 4, 1, @encode(int));

我们还可以使用下列API给新创建的类添加方法:

BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types)

可以这样来添加:

    Method method = class_getInstanceMethod([self class], @selector(teach));
    IMP imp = method_getImplementation(method);
    class_addMethod(teacherClass, @selector(teach), imp, "v16@0:8");

这样一来就为新创建的Teacher类添加了成员变量和方法。
添加完成员变量和方法后,我们再去注册这个类:

objc_registerClassPair(teacherClass);

那么我们怎么去给成员变量赋值和取值呢?由于没有set和get方法,所以肯定是不能直接取值和赋值,需要通过KVC来取值和赋值:

    id teacher = [[teacherClass alloc] init];
    [teacher setValue:@10 forKey:@"_age"];
    [teacher setValue:@20 forKey:@"_weight"];

    [teacher teach];
    NSLog(@"%@, %@", [teacher valueForKey:@"_age"], [teacher valueForKey:@"_weight"]);

Runtime API02 - 成员变量

这一部分主要讲与成员变量相关的API,主要有下列这些:

    //获取一个成员变量
    Ivar class_getInstanceVariable(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);
    
    //拷贝成员变量列表
    Ivar *class_copyIvarList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
    
    //设置和获取成员变量的值
    void object_setIvar(id  _Nullable obj, Ivar  _Nonnull ivar, id  _Nullable value);
    id object_getIvar(id  _Nullable obj, Ivar  _Nonnull ivar);
    
    //动态添加成员变量(已经注册的类不能动态添加成员变量)
    BOOL class_addIvar(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types);
    
    //获取成员变量的相关信息
    const char *ivar_getName(Ivar  _Nonnull v);
    const char *ivar_getTypeEncoding(Ivar  _Nonnull v);

下面我们一个一个来分析:

    //获取一个成员变量
    Ivar class_getInstanceVariable(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);

这个就是传入类对象和成员变量的名字,返回一个Ivar类型的成员变量:

Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
    //拷贝成员变量列表
    Ivar *class_copyIvarList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);

这个API是返回这个类的所有成员变量的一个列表。这个API可以和下面这个API结合使用来打印类的所以成员变量:

    //获取成员变量的相关信息
    const char *ivar_getName(Ivar  _Nonnull v);
    const char *ivar_getTypeEncoding(Ivar  _Nonnull v);
    unsigned int count;
    Ivar *ivarList = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];
       const char *ivarName = ivar_getName(ivar);
        NSLog(@"%s", ivarName);
    }
    free(ivarList);

这样就会打印类的所有成员变量的值

    //设置和获取成员变量的值
    void object_setIvar(id  _Nullable obj, Ivar  _Nonnull ivar, id  _Nullable value);
    id object_getIvar(id  _Nullable obj, Ivar  _Nonnull ivar);

这是给成员变量设值和取值,用法如下:

    Person *person = [[Person alloc] init];
    Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
    object_setIvar(person, nameIvar, @"xiaoli");
    id name = object_getIvar(person, nameIvar);
    //动态添加成员变量(已经注册的类不能动态添加成员变量)
    BOOL class_addIvar(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types);

给类动态添加成员变量,这个之前已经使用过,只是要说明的是,不能在类注册后去动态添加成员变量,因为成员变量是存放在class_ro_t结构体中的,是不能修改的,因此一旦类注册成功后,class_ro_t结构体就不能修改,也就不能再添加成员变量。

简单应用

下面讲一个利用这类API的实际应用。首先拖拽一个UITextField到Main.storyboard,命名为textField。然后设置这个输入框的占位符:

self.textField.placeholder = @"请输入文字";

然后我们运行代码,发现占位文字显示出来了,但是是灰色,这种占位文字的颜色并不是我想要的,我想要改变占位文字的颜色,但是我在UITextField的API中又没有找到相关的API,怎么办呢?我想到这个占位文字这里的设计应该是用了一个UILabel,如果我真的找到了这个label,那么我就可以利用KVC去修改其textColor属性,我利用runtime的API打印一下其成员变量,看看有没有我要找的这个label:

    unsigned int count;
    Ivar *ivarList = class_copyIvarList([self.textField class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];
       const char *ivarName = ivar_getName(ivar);
        NSLog(@"%s", ivarName);
    }
    free(ivarList);

打印结果:


165569A8-457B-4BB8-A5FA-5ABAA6D15E10.png

果然找到了这个成员变量,这个成员变量的名字是_placeholderLabel,那么我们就可以尝试修改其textColor试试:

    [self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];

运行代码,发现占位文字的颜色真的变成了红色,非常神奇。

Runtime API03 - 属性

和属性相关的runtime的API有下面这些:

    //获取一个属性
    objc_property_t class_getProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);
    
    //拷贝属性列表(最后需要调用free释放)
    objc_property_t *class_copyPropertyList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
    
    //动态添加属性
    BOOL class_addProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount);
    
    //动态替换属性
    void class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
    
    //获取属性的一些信息
    const char *property_getName(objc_property_t  _Nonnull property);
    const char *property_getAttributes(objc_property_t  _Nonnull property);

下面我们一个一个来看:

    //获取一个属性
    objc_property_t class_getProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);

这个API用来获取一个属性:

    objc_property_t nameProperty = class_getProperty([Person class], "name");
    //拷贝属性列表(最后需要调用free释放)
    objc_property_t *class_copyPropertyList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);

这个和5结合起来可以打印类的所有属性名

    //获取属性的一些信息
    const char *property_getName(objc_property_t  _Nonnull property);
    const char *property_getAttributes(objc_property_t  _Nonnull property);
    unsigned int ivarCount;
    Ivar *ivarLists = class_copyIvarList([Person class], &ivarCount);
    for (int i =0; i < ivarCount; i++) {
        Ivar ivar = ivarLists[i];
        const char *name = ivar_getName(ivar);
        const char *typeencoding = ivar_getTypeEncoding(ivar);
        NSLog(@"%s, %s", name, typeencoding);
    }
    
    free(ivarLists);
    //动态添加属性
    BOOL class_addProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount);

这个API是给类动态添加属性

    //动态替换属性
    void class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);

动态替换属性

Runtime API04 - 方法

与方法相关的runtime的API有下面这些:

    //获得一个实例方法,类方法
    Method class_getInstanceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    Method class_getClassMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    
    //方法实现相关操作
    IMP class_getMethodImplementation(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    IMP method_setImplementation(Method  _Nonnull m, IMP  _Nonnull imp);
    void method_exchangeImplementations(Method  _Nonnull m1, Method  _Nonnull m2);
    
    //拷贝方法列表(最后需要调用free释放)
    Method *class_copyMethodList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
    
    //动态添加方法
    BOOL class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
    
    //动态替换方法
    IMP class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
    
    //获取方法的相关信息
    SEL method_getName(Method  _Nonnull m);
    IMP method_getImplementation(Method  _Nonnull m);
    const char *method_getTypeEncoding(Method  _Nonnull m);
    unsigned int method_getNumberOfArguments(Method  _Nonnull m);
    char *method_copyReturnType(Method  _Nonnull m);
    char *method_copyArgumentType(Method  _Nonnull m, unsigned int index);
    
    //选择器相关
    const char *sel_getName(SEL  _Nonnull sel);
    SEL sel_registerName(const char * _Nonnull str);

获得一个实例方法,类方法:

    //获得一个实例方法,类方法
    Method class_getInstanceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    Method class_getClassMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);

实例代码如下:

    Method instanceMethod = class_getInstanceMethod([Person class], @selector(run));
    Method classMethod = class_getClassMethod([Person class], @selector(doWork));

拷贝方法列表

    //拷贝方法列表(最后需要调用free释放)
    Method *class_copyMethodList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);

这个可以和3结合使用来打印类的所有方法的信息

获取方法的相关信息

    //获取方法的相关信息
    SEL method_getName(Method  _Nonnull m);
    IMP method_getImplementation(Method  _Nonnull m);
    const char *method_getTypeEncoding(Method  _Nonnull m);
    unsigned int method_getNumberOfArguments(Method  _Nonnull m);
    char *method_copyReturnType(Method  _Nonnull m);
    char *method_copyArgumentType(Method  _Nonnull m, unsigned int index);

使用:
打印对象方法:

    unsigned int outCount;
   //需要注意的是,这里我们传入的是类对象,所以得出的也就是实例方法
    Method *methodLists = class_copyMethodList([Person class], &outCount);
    for (int i = 0; i < outCount; i++) {
        Method method = methodLists[i];
        SEL sel = method_getName(method);
        const char *typeEncoding = method_getTypeEncoding(method);
        unsigned int argumentCount = method_getNumberOfArguments(method);
        char *returnType = method_copyReturnType(method);
        char *argumentType = method_copyArgumentType(method, 0);
                              
        NSLog(@"方法名:%@,类型编码:%s, 参数数量:%d, 返回值类型:%s, 参数类型:%s", NSStringFromSelector(sel), typeEncoding, argumentCount, returnType, argumentType);
    }
                              
    free(methodLists);

打印结果:

2018-09-19 14:34:52.295666+0800 TEST[7369:234255] 方法名:test,类型编码:v16@0:8, 参数数量:2, 返回值类型:v, 参数类型:@
2018-09-19 14:34:52.295806+0800 TEST[7369:234255] 方法名:runWithAge:heigh:name:,类型编码:v32@0:8i16i20@24, 参数数量:5, 返回值类型:v, 参数类型:@
2018-09-19 14:34:52.295923+0800 TEST[7369:234255] 方法名:.cxx_destruct,类型编码:v16@0:8, 参数数量:2, 返回值类型:v, 参数类型:@
2018-09-19 14:34:52.296035+0800 TEST[7369:234255] 方法名:name,类型编码:@16@0:8, 参数数量:2, 返回值类型:@, 参数类型:@
2018-09-19 14:34:52.296243+0800 TEST[7369:234255] 方法名:setName:,类型编码:v24@0:8@16, 参数数量:3, 返回值类型:v, 参数类型:@
2018-09-19 14:34:52.296396+0800 TEST[7369:234255] 方法名:setAge:,类型编码:v20@0:8i16, 参数数量:3, 返回值类型:v, 参数类型:@
2018-09-19 14:34:52.296487+0800 TEST[7369:234255] 方法名:age,类型编码:i16@0:8, 参数数量:2, 返回值类型:i, 参数类型:@

打印类方法:

    unsigned int outCount;
    Method *methodLists = class_copyMethodList(object_getClass([Person class]), &outCount);
    for (int i = 0; i < outCount; i++) {
        Method method = methodLists[i];
        SEL sel = method_getName(method);
        const char *typeEncoding = method_getTypeEncoding(method);
        unsigned int argumentCount = method_getNumberOfArguments(method);
        char *returnType = method_copyReturnType(method);
        char *argumentType = method_copyArgumentType(method, 0);
                              
        NSLog(@"方法名:%@,类型编码:%s, 参数数量:%d, 返回值类型:%s, 参数类型:%s", NSStringFromSelector(sel), typeEncoding, argumentCount, returnType, argumentType);
    }
                              
    free(methodLists);

打印结果:

2018-09-19 14:37:20.093441+0800 TEST[7437:236526] 方法名:test,类型编码:v16@0:8, 参数数量:2, 返回值类型:v, 参数类型:@
2018-09-19 14:37:20.093640+0800 TEST[7437:236526] 方法名:doWork,类型编码:v16@0:8, 参数数量:2, 返回值类型:v, 参数类型:@

动态添加方法

    //动态添加方法
    BOOL class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);

这个API是用来给类动态添加方法的
添加对象方法,从类对象中获取对象方法的实现,然后添加到类对象中,再用实例对象去调用。

- (void)read{
    NSLog(@"读书");
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    IMP imp = class_getMethodImplementation([self class], @selector(read));
    class_addMethod([Person class], @selector(read), imp, "v16@0:8");
    Person *person = [[Person alloc] init];
    [person performSelector:@selector(read)];
}

这样就成功为Person类添加了一个对象方法read。
添加类方法,要从元类对象中去获取类方法的实现,然后添加到元类对象中去,再用类对象去调用:

    IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(read));
    class_addMethod(object_getClass([Person class]), @selector(read), imp, "v16@0:8");
    [[Person class] performSelector:@selector(read)];
}

这样就为Person类添加了一个类方法read。

和选择器SEL相关的API

    //选择器相关
    const char *sel_getName(SEL  _Nonnull sel);
    SEL sel_registerName(const char * _Nonnull str);

使用:

    const char *name = sel_getName(@selector(read));
    SEL sel = sel_registerName("read");

替换方法实现

//动态替换方法
/**
* @param cls 想要修改的这个类
* @param name 我们想要替换掉的方法实现
* @param imp 用来替换旧方法实现的新方法实现
* @param types 新的方法的类型,例如"v16@0:8"
* @return 替换后的这个新实现
*/
 IMP class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);

实例如下:

- (void)read{
    NSLog(@"读书");
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    IMP imp = class_getMethodImplementation([self class], @selector(read));
    class_replaceMethod([Person class], @selector(runWithAge:heigh:name:), imp, "v16@0:8");
    Person *person = [[Person alloc] init];
    [person runWithAge:10 heigh:1 name:@"dongdong"];
}

方法实现相关操作

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

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,032评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,041评论 1 32
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,470评论 33 467
  • 一个人划着小船在大海上,像电影里的少年派一样漂流在海面上。漂了很久,最后来到距离大陆很远的一个小岛上。发现岛上有个...
    Geshi阅读 218评论 0 7
  • 1.桃心贺卡:将卡纸对折;粉色纸剪成两个大小相等的桃心,对折;其中两瓣粘在一起,另外两个面粘在贺卡表面。 2.蜂巢...
    l杂货铺l阅读 1,720评论 0 0