iOS runtime 之稀里糊涂到有所收获(一)

这是开头:
由于这段时间公司空闲,准备抓抓手上的技术。由于runtime一直以来都没咋个弄明白过,所以想自己琢磨琢磨,弄弄明白。不会runtime和不咋个了解的老铁们希望我写的东西会对你们有所帮助,文章里边肯定有不足之处,还望技术大大们不吝赐教

一、runtime是什么
首先第一步小白的我要知道runtime是什么,稀里糊涂的我遇到这个问题不知道怎么解释于是乞求度娘解答。。。
度娘给出以下解答:

image.png

个人的理解就是oc代码在被执行的过程我们就把他叫做运行时。
但从度娘的口气来看没有单单针对ios来解释runtime,因此我指名道姓的询问度娘“ios runtime”
image.png

度娘不想回答我,于是丢出了一篇大神的博客,于是我点进去,津津有味的啃了起来。大神的文章很详细,我接下来的思路又清除明朗了许多。。[开心到吐舌头🐶]
到此,相信大家对runtime已经有了一个初步的概念。但是,在ios中具体是怎么操作,我们要怎么撸代码呢,那么接着上路吧。

二 、从头到脚认识runtime.h
相信用过runtime的都知道,在调用runtime的方法之前,首先,在所要编写代码的文件下引入runtime.h,如下:

#import <objc/runtime.h>

那么引入后我们要调用啥方法呢(其实我也不知道你要调用啥方法,就我而言,应为目前的工作项目要求的不深且个人能力有限,并没有在实际项目中运用到runtime的东西),那就随便的调用玩一玩。

在此我新建了一个工程,导入#import <objc/runtime.h>后,进入到runtime.h文件。进入一看,可能有些小白要懵逼了,全特么的音哥离席还是c的接口。兄弟们,稳住不要慌,看我三级英语带你们撸过去(装逼不易,还请勿喷😛)
(一)、头部


image.png

就着我的英语水平和开发经验,上面的这一坨申明版权及版本号啥的,让我们能大体的对此文件有个初步认识。但中间出现了一个链接,链接后还附带了一句and read it before using this file. 这绝逼就是开发文档了。于是我开开心心的点开了,我****,居然报错了
image.png

不知道你们打开是啥样,反正我*****。。。
不给我文档,那就凭着一身本事撸下去吧。
在版权声明的下边,导入了一些c库文件,无法进入,但是就是管不住自己的好奇心,于是度娘了一波。结果如下:

*1.stdarg.h是C语言中C标准函数库的头文件,stdarg是由standard(标准) arguments(参数)简化而来,主要目的为让函数能够接收可变参数
2.stddef.h定义了一些标准宏以及类型.
3.Availability.h,这个头文件的作用是判断iOS系统的版本可用性。
4.TargetConditionals.h 作用:会自动配置编译器所要编译的代码将要使用的微处理器指令集、运行系统以及运行时环境。
5.sys/types.h则是在OS_MAC开发中用到的,名称为基本系统数据类型

接下来就是头部重点:

/* Types */

#if !OBJC_TYPES_DEFINED
以下都是结构体类型(不透明类型的个人理解:就是在运行时才会生成,代码内看不见。。)
/// An opaque type that represents a method in a class definition.
(表示类定义中的方法的不透明类型。)(方法)

typedef struct objc_method *Method;

/// An opaque type that represents an instance variable.
(表示实例变量的不透明类型。)(变量)
typedef struct objc_ivar *Ivar;

/// An opaque type that represents a category.
(代表类别的不透明类型。)(类别)
typedef struct objc_category *Category;

/// An opaque type that represents an Objective-C declared property.
(代表Objective-C声明属性的不透明类型。)(属性)
typedef struct objc_property *objc_property_t;

类
struct objc_class {
指向父类或原类
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
父类
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
类名
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
版本
    long version                                             OBJC2_UNAVAILABLE;
信息
    long info                                                OBJC2_UNAVAILABLE;
实例大小
    long instance_size                                       OBJC2_UNAVAILABLE;
变量列表
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
方法列表
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
缓存
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
协议列表
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

#endif

#ifdef __OBJC__
协议
@class Protocol;
#else
typedef struct objc_object Protocol;
#endif

/// Defines a method
定义方法时的要素
struct objc_method_description {
名称
    SEL _Nullable name;               /**< The name of the method */
参数类型
    char * _Nullable types;           /**< The types of the method arguments */
};

/// Defines a property attribute
定义一个属性的要素
typedef struct {
名称
    const char * _Nonnull name;           /**< The name of the attribute */
属性值
    const char * _Nonnull value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;


至此是不是一个,眉清目秀的runtime姑娘已经呈现在你的面前了,你是不是想迫不及待的看看她的曼妙身姿了呢(yy下。。)

(二)、方法接口(身体)
以下方法我将结合代码研究下,篇幅肯定会很长,光说不练不就是假把式么。还得撸撸,不是么。。

object_copy

/** 
 * Returns a copy of a given object.
 * 
 * @param obj An Objective-C object.
 * @param size The size of the object \e obj.
 * 
 * @return A copy of \e obj.
 */
返回给定对象的副本。
 *
  * @param obj一个Objective-C对象。
  * @param size对象的大小\ e obj。
 *
  * @return \ e obj的副本。

OBJC_EXPORT id _Nullable object_copy(id _Nullable obj, size_t size)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;

实例运用:在工程中新建了一个person类继承NSObject,定义了两个属性name,与age。如下:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property(nonatomic, copy)NSString *name;

@property(nonatomic, assign)NSInteger age;

@end

现在就用Person类来操作下runtime下的方法。
注:以上方法由于是在arc模式不可用,需关掉arc

image.png

选项改为NO
或者将单一的类兼容MRC(arc模式下找到文件加入-fno-objc-arc,MRC模式下找到加入-fobjc-arc将会支持arc的类)


image.png
image.png

由以上方法调用可见object_copy的方法调用后会生成一个新的对象。

object_dispose(非ARC下)

/** 
 * Frees the memory occupied by a given object.
 * 
 * @param obj An Objective-C object.
 * 
 * @return nil
 */

释放给定对象占用的内存。
 *
  * @param obj一个Objective-C对象。
 *
  * @返回nil

OBJC_EXPORT id _Nullable
object_dispose(id _Nullable obj)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;

示例:

image.png

此时这里会报错,报错原因已经很明确了,应为per已经被释放,per成为了僵尸对象。
报错解决小技巧:这个报错也很常见,通常是一个对象没被初始化或已经释放后调用方法。但是这个报错不会打印日志会让程序猿头疼,怎样才能定位到是那个地方的问题呢,为大家找到了大神的操作

object_getClass

/** 
 * Returns the class of an object.
 * 
 * @param obj The object you want to inspect.
 * 
 * @return The class object of which \e object is an instance, 
 *  or \c Nil if \e object is \c nil.
 */

返回一个对象的类。
 *
  * @param obj要检查的对象。
 *
  * @return其中\ e对象是一个实例的类对象,
  *或\ c如果\ e对象是\ n,则为nil。

OBJC_EXPORT Class _Nullable
object_getClass(id _Nullable obj) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

示例:


image.png

object_setClass

/** 
 * Sets the class of an object.
 * 
 * @param obj The object to modify.
 * @param cls A class object.
 * 
 * @return The previous value of \e object's class, or \c Nil if \e object is \c nil.
 */

设置对象的类别。
 *
  * @param obj要修改的对象。
  * @参数cls一个类对象。
 *
  * @return \ e对象的类的前一个值,或者\ c如果\ e对象是\ c nil,则返回Nil。

OBJC_EXPORT Class _Nullable
object_setClass(id _Nullable obj, Class _Nonnull cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

示例:


image.png

可见per对象所属类被改变,(猜想:oc类型强转时调用了此方法)

object_isClass

/** 
 * Returns whether an object is a class object.
 * 
 * @param obj An Objective-C object.
 * 
 * @return true if the object is a class or metaclass, false otherwise.

 */

返回一个对象是否是一个类对象。
 *
  * @param obj一个Objective-C对象。
 *
  * @如果对象是类或元类,则返回true,否则返回false。

OBJC_EXPORT BOOL
object_isClass(id _Nullable obj)
    OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0, 2.0);

示例:


image.png

object_getIvar,
object_setIvar,
object_setIvarWithStrongDefault,
object_setInstanceVariable,
object_setInstanceVariableWithStrongDefault,
object_getInstanceVariable
获取类的实例变量的值和设置实例变量的值(如果Ivar实例变量已知的时候object_getIvar,object_setIvar的效率更高)

/** 
 * Reads the value of an instance variable in an object.
 * 
 * @param obj The object containing the instance variable whose value you want to read.
 * @param ivar The Ivar describing the instance variable whose value you want to read.
 * 
 * @return The value of the instance variable specified by \e ivar, or \c nil if \e object is \c nil.
 * 
 * @note \c object_getIvar is faster than \c object_getInstanceVariable if the Ivar
 *  for the instance variable is already known.
 */

读取对象中实例变量的值。
 *
  * @param obj包含要读取其值的实例变量的对象。
  * @param ivar Ivar描述了您想要读取其值的实例变量。
 *
  * @return如果\ e对象是\ c nil,则由\ e ivar指定的实例变量的值或\ c nil。
 *
  * @note \ c object_getIvar比\ c object_getInstanceVariable快,如果Ivar
  *为实例变量已知。

OBJC_EXPORT id _Nullable
object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

/** 
 * Sets the value of an instance variable in an object.
 * 
 * @param obj The object containing the instance variable whose value you want to set.
 * @param ivar The Ivar describing the instance variable whose value you want to set.
 * @param value The new value for the instance variable.
 * 
 * @note Instance variables with known memory management (such as ARC strong and weak)
 *  use that memory management. Instance variables with unknown memory management 
 *  are assigned as if they were unsafe_unretained.
 * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar
 *  for the instance variable is already known.
 */

设置对象中实例变量的值。
 *
  * @param obj包含要设置其值的实例变量的对象。
  * @param ivar Ivar描述了你想要设置其值的实例变量。
  * @param value实例变量的新值。
 *
  * @note具有已知内存管理的实例变量(例如ARC强和弱)
  *使用内存管理。 具有未知内存管理的实例变量
  *被分配为不安全_未保留。
  * @note \ c object_setIvar比\ c object_setInstanceVariable快,如果Ivar
  *为实例变量已知。

OBJC_EXPORT void
object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

/** 
 * Sets the value of an instance variable in an object.
 * 
 * @param obj The object containing the instance variable whose value you want to set.
 * @param ivar The Ivar describing the instance variable whose value you want to set.
 * @param value The new value for the instance variable.
 * 
 * @note Instance variables with known memory management (such as ARC strong and weak)
 *  use that memory management. Instance variables with unknown memory management 
 *  are assigned as if they were strong.
 * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar
 *  for the instance variable is already known.
 */

设置对象中实例变量的值。
 *
  * @param obj包含要设置其值的实例变量的对象。
  * @param ivar Ivar描述了你想要设置其值的实例变量。
  * @param value实例变量的新值。
 *
  * @note具有已知内存管理的实例变量(例如ARC强和弱)
  *使用内存管理。 具有未知内存管理的实例变量
  *被分配,如果他们是Strong。
  * @note \ c object_setIvar比\ c object_setInstanceVariable快,如果Ivar
  *为实例变量已知。

OBJC_EXPORT void
object_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar,
                                id _Nullable value) 
    OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0);

/** 
 * Changes the value of an instance variable of a class instance.
 * 
 * @param obj A pointer to an instance of a class. Pass the object containing
 *  the instance variable whose value you wish to modify.
 * @param name A C string. Pass the name of the instance variable whose value you wish to modify.
 * @param value The new value for the instance variable.
 * 
 * @return A pointer to the \c Ivar data structure that defines the type and 
 *  name of the instance variable specified by \e name.
 *
 * @note Instance variables with known memory management (such as ARC strong and weak)
 *  use that memory management. Instance variables with unknown memory management 
 *  are assigned as if they were unsafe_unretained.
 */

更改类实例的实例变量的值。
 *
  * @param obj指向一个类的实例的指针。 传递包含的对象
  *您希望修改其值的实例变量。
  * @param name一个C字符串。 传递您希望修改其值的实例变量的名称。
  * @param value实例变量的新值。
 *
  * @return指向定义类型和的Ivar数据结构的指针
  *由\ e名称指定的实例变量的名称。
 *
  * @note具有已知内存管理的实例变量(例如ARC强和弱)
  *使用内存管理。 具有未知内存管理的实例变量
  *被分配为unsafe_unretained。

OBJC_EXPORT Ivar _Nullable
object_setInstanceVariable(id _Nullable obj, const char * _Nonnull name,
                           void * _Nullable value)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;

/** 
 * Changes the value of an instance variable of a class instance.
 * 
 * @param obj A pointer to an instance of a class. Pass the object containing
 *  the instance variable whose value you wish to modify.
 * @param name A C string. Pass the name of the instance variable whose value you wish to modify.
 * @param value The new value for the instance variable.
 * 
 * @return A pointer to the \c Ivar data structure that defines the type and 
 *  name of the instance variable specified by \e name.
 *
 * @note Instance variables with known memory management (such as ARC strong and weak)
 *  use that memory management. Instance variables with unknown memory management 
 *  are assigned as if they were strong.
 */

更改类实例的实例变量的值。
 *
  * @param obj指向一个类的实例的指针。 传递包含的对象
  *您希望修改其值的实例变量。
  * @param name一个C字符串。 传递您希望修改其值的实例变量的名称。
  * @param value实例变量的新值。
 *
  * @return指向定义类型和的Ivar数据结构的指针
  *由\ e名称指定的实例变量的名称。
 *
  * @note具有已知内存管理的实例变量(例如ARC强和弱)
  *使用内存管理。 具有未知内存管理的实例变量
  *被分配,如果他们是Strong。

OBJC_EXPORT Ivar _Nullable
object_setInstanceVariableWithStrongDefault(id _Nullable obj,
                                            const char * _Nonnull name,
                                            void * _Nullable value)
    OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0)
    OBJC_ARC_UNAVAILABLE;

/** 
 * Obtains the value of an instance variable of a class instance.
 * 
 * @param obj A pointer to an instance of a class. Pass the object containing
 *  the instance variable whose value you wish to obtain.
 * @param name A C string. Pass the name of the instance variable whose value you wish to obtain.
 * @param outValue On return, contains a pointer to the value of the instance variable.
 * 
 * @return A pointer to the \c Ivar data structure that defines the type and name of
 *  the instance variable specified by \e name.
 */

获取类实例的实例变量的值。
 *
  * @param obj指向一个类的实例的指针。 传递包含的对象
  *您希望获取其值的实例变量。
  * @param name一个C字符串。 传递您希望获取其值的实例变量的名称。
  * @param outValue返回时,包含一个指向实例变量值的指针。
 *
  * @return指向定义类型和名称的\ c Ivar数据结构的指针
  *由\ e名称指定的实例变量。

OBJC_EXPORT Ivar _Nullable
object_getInstanceVariable(id _Nullable obj, const char * _Nonnull name,
                           void * _Nullable * _Nullable outValue)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;

示例如下:
image.png

objc_getClass
objc_getMetaClass
objc_lookUpClass
objc_getRequiredClass
objc_getClassList
objc_copyClassList

返回指定类的类。( objc_getClass与 objc_lookUpClass不同,因为如果类未注册, objc_getClass调用类处理程序回调,然后检查
  第二次查看class是否已注册。 objc_lookUpClass不调用类处理程序回调。)
OBJC_EXPORT Class _Nullable
objc_getClass(const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
返回指定类的元类。
OBJC_EXPORT Class _Nullable
objc_getMetaClass(const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
返回指定类的类
OBJC_EXPORT Class _Nullable
objc_lookUpClass(const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
与objc_getClass相同,但如果未找到该类,则会终止该进程。ZeroLink使用该函数,如果没有找到类,则会出现无ZeroLink的编译时链接错误(崩溃)
OBJC_EXPORT Class _Nonnull
objc_getRequiredClass(const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

使用:https://www.jianshu.com/p/bf6c81fc2434
可用于获取工程注册的所有类的总和
OBJC_EXPORT int
objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT Class _Nonnull * _Nullable
objc_copyClassList(unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0, 2.0);

class_getName
class_isMetaClass
class_getSuperclass
class_setSuperclass
class_getVersion
class_setVersion
class_getInstanceSize

获取类名
OBJC_EXPORT const char * _Nonnull
class_getName(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
判断是否为元类
OBJC_EXPORT BOOL
class_isMetaClass(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取其父类
OBJC_EXPORT Class _Nullable
class_getSuperclass(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
设置其父类
OBJC_EXPORT Class _Nonnull
class_setSuperclass(Class _Nonnull cls, Class _Nonnull newSuper) 
    __OSX_DEPRECATED(10.5, 10.5, "not recommended") 
    __IOS_DEPRECATED(2.0, 2.0, "not recommended") 
    __TVOS_DEPRECATED(9.0, 9.0, "not recommended") 
    __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended")
    __BRIDGEOS_DEPRECATED(2.0, 2.0, "not recommended");
获取版本号
OBJC_EXPORT int
class_getVersion(Class _Nullable cls)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
设置版本号
OBJC_EXPORT void
class_setVersion(Class _Nullable cls, int version)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
获取类实例的大小
OBJC_EXPORT size_t
class_getInstanceSize(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

简单调用示例如下:


image.png

class_getInstanceVariable
class_getClassVariable
class_copyIvarList
class_getInstanceMethod
class_getClassMethod
class_getMethodImplementation
class_getMethodImplementation_stret
class_respondsToSelector
class_copyMethodList

获取指定实例变量的Ivar
OBJC_EXPORT Ivar _Nullable
class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
获取给定类的指定类变量的Ivar
OBJC_EXPORT Ivar _Nullable
class_getClassVariable(Class _Nullable cls, const char * _Nonnull name) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
描述由类声明的实例变量。
OBJC_EXPORT Ivar _Nonnull * _Nullable
class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取实例方法
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
获取类方法
OBJC_EXPORT Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
获取调用函数指针
OBJC_EXPORT IMP _Nullable
class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取如果特定的函数将被调用的指针
OBJC_EXPORT IMP _Nullable
class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0)
    OBJC_ARM64_UNAVAILABLE;
获取选择器是否响应
OBJC_EXPORT BOOL
class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取类方法列表
OBJC_EXPORT Method _Nonnull * _Nullable
class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

简单示例:
image.png
//获取选择器是否响应
    NSLog(@"work is responds = %@",@(class_respondsToSelector([Person class], @selector(work))));
    NSLog(@"fight is responds = %@",@(class_respondsToSelector([Person class], @selector(fight))));
    //获取方法列表
    Method *methods =  class_copyMethodList([Person class], &count);
    for (int i = 0; i < count; i ++) {
        NSLog(@"%s", method_getName(methods[i]));
    }

打印结果(关于.cxx_destruct方法探究

Person类添加了fight(未实现)和work的实例方法以及sleep的类方法,class_copyMethodList会找到实现的实例方法以及属性的setter与getter方法,以及ARC下隐藏的释放方法.cxx_destruct
RuntimeTest[2395:86765] work is responds = 1
2018-06-05 10:53:19.226531+0800 RuntimeTest[2395:86765] fight is responds = 0
2018-06-05 10:53:19.226952+0800 RuntimeTest[2395:86765] work
2018-06-05 10:53:19.227174+0800 RuntimeTest[2395:86765] .cxx_destruct
2018-06-05 10:53:19.227507+0800 RuntimeTest[2395:86765] name
2018-06-05 10:53:19.227803+0800 RuntimeTest[2395:86765] setName:
2018-06-05 10:53:19.228017+0800 RuntimeTest[2395:86765] setAge:
2018-06-05 10:53:19.228248+0800 RuntimeTest[2395:86765] age

class_conformsToProtocol
class_copyProtocolList

是否遵守协议
OBJC_EXPORT BOOL
class_conformsToProtocol(Class _Nullable cls, Protocol * _Nullable protocol) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
协议列表
OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable 
class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

简单示例:
image.png

class_getProperty
class_copyPropertyList
class_getIvarLayout
class_getWeakIvarLayout

获取属性
OBJC_EXPORT objc_property_t _Nullable
class_getProperty(Class _Nullable cls, const char * _Nonnull name)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取属性列表
OBJC_EXPORT objc_property_t _Nonnull * _Nullable
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取IvarLayout
OBJC_EXPORT const uint8_t * _Nullable
class_getIvarLayout(Class _Nullable cls)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取week Ivar Lyout
OBJC_EXPORT const uint8_t * _Nullable
class_getWeakIvarLayout(Class _Nullable cls)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

简单示例:
image.png

class_addMethod
class_replaceMethod

方法添加
OBJC_EXPORT BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
方法替换
OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

简单示例:
image.png

tips:由以上可以看出,方法主要由SEL(选择器) + IMP(函数指针)构成

class_addIvar
class_addProtocol
class_addProperty 例子
class_replaceProperty
class_setIvarLayout
class_setWeakIvarLayout
class_createInstance

添加实例变量(此函数只能在objc_allocateClassPair之后和objc_registerClassPair之前调用。类不能是元类。 不支持将实例变量添加到元类。实例变量的最小对齐字节数为1 << align。 实例的最小对齐)
OBJC_EXPORT BOOL
class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
添加协议
OBJC_EXPORT BOOL
class_addProtocol(Class _Nullable cls, Protocol * _Nonnull protocol) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
添加属性
OBJC_EXPORT BOOL
class_addProperty(Class _Nullable cls, const char * _Nonnull name,
                  const objc_property_attribute_t * _Nullable attributes,
                  unsigned int attributeCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
属性替换
OBJC_EXPORT void
class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,
                      const objc_property_attribute_t * _Nullable attributes,
                      unsigned int attributeCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

OBJC_EXPORT void
class_setIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void
class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
创建实例
OBJC_EXPORT id _Nullable
class_createInstance(Class _Nullable cls, size_t extraBytes)
    OBJC_RETURNS_RETAINED
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

objc_constructInstance
objc_destructInstance
objc_allocateClassPair
objc_registerClassPair
objc_duplicateClass
objc_disposeClassPair

创建实例
OBJC_EXPORT id _Nullable
objc_constructInstance(Class _Nullable cls, void * _Nullable bytes) 
    OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;
销毁实例
OBJC_EXPORT void * _Nullable objc_destructInstance(id _Nullable obj) 
    OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;
创建一个类
OBJC_EXPORT Class _Nullable
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
类注册
OBJC_EXPORT void
objc_registerClassPair(Class _Nonnull cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
kvo使用 不要自己调用
OBJC_EXPORT Class _Nonnull
objc_duplicateClass(Class _Nonnull original, const char * _Nonnull name,
                    size_t extraBytes)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
销毁一个类及元类
OBJC_EXPORT void
objc_disposeClassPair(Class _Nonnull cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

method_getName
method_getImplementation
method_getTypeEncoding
method_getNumberOfArguments
method_copyReturnType
method_copyArgumentType
method_getReturnType
method_setImplementation
method_exchangeImplementations

获取方法名
OBJC_EXPORT SEL _Nonnull
method_getName(Method _Nonnull m) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取方法的调用的函数指针
OBJC_EXPORT IMP _Nonnull
method_getImplementation(Method _Nonnull m) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取方法参数类型
method_getTypeEncoding(Method _Nonnull m) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取参数个数
OBJC_EXPORT unsigned int
method_getNumberOfArguments(Method _Nonnull m)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
返回类型描述
OBJC_EXPORT char * _Nonnull
method_copyReturnType(Method _Nonnull m) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
参数类型
OBJC_EXPORT char * _Nullable
method_copyArgumentType(Method _Nonnull m, unsigned int index) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
通过返回描述获取返回类型
OBJC_EXPORT void
method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取方法描述
OBJC_EXPORT struct objc_method_description * _Nonnull
method_getDescription(Method _Nonnull m) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
设置方法的实现
OBJC_EXPORT IMP _Nonnull
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
交换方法的实现
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

简单示例:
image.png

ivar_getName
ivar_getTypeEncoding
ivar_getOffset
property_getName
property_getAttributes
property_copyAttributeList
property_copyAttributeValue
protocol_copyProtocolList

获取实例变量名
OBJC_EXPORT const char * _Nullable
ivar_getName(Ivar _Nonnull v) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

获取实例变量的类型
OBJC_EXPORT const char * _Nullable
ivar_getTypeEncoding(Ivar _Nonnull v) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

获取实例变量偏移
OBJC_EXPORT ptrdiff_t
ivar_getOffset(Ivar _Nonnull v) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取属性名
OBJC_EXPORT const char * _Nonnull
property_getName(objc_property_t _Nonnull property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取属性的属性字符串
OBJC_EXPORT const char * _Nullable
property_getAttributes(objc_property_t _Nonnull property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取属性列表
OBJC_EXPORT objc_property_attribute_t * _Nullable
property_copyAttributeList(objc_property_t _Nonnull property,
                           unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
属性值
OBJC_EXPORT char * _Nullable
property_copyAttributeValue(objc_property_t _Nonnull property,
                            const char * _Nonnull attributeName)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

objc_getProtocol
objc_copyProtocolList
protocol_conformsToProtocol
protocol_isEqual
protocol_getName
protocol_getMethodDescription
protocol_copyPropertyList
protocol_copyPropertyList2
objc_allocateProtocol
objc_registerProtocol
protocol_addMethodDescription


通过协议名获取协议
OBJC_EXPORT Protocol * _Nullable
objc_getProtocol(const char * _Nonnull name)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
协议列表
OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable
objc_copyProtocolList(unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
判断一个协议是否遵循另一个协议
OBJC_EXPORT BOOL
protocol_conformsToProtocol(Protocol * _Nullable proto,
                            Protocol * _Nullable other)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
判断两个协议是否相等
OBJC_EXPORT BOOL
protocol_isEqual(Protocol * _Nullable proto, Protocol * _Nullable other)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取协议的名称
OBJC_EXPORT const char * _Nonnull
protocol_getName(Protocol * _Nonnull proto)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取协议方法
OBJC_EXPORT struct objc_method_description
protocol_getMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull aSel,
                              BOOL isRequiredMethod, BOOL isInstanceMethod)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取协议方法列表
OBJC_EXPORT struct objc_method_description * _Nullable
protocol_copyMethodDescriptionList(Protocol * _Nonnull proto,
                                   BOOL isRequiredMethod,
                                   BOOL isInstanceMethod,
                                   unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取协议属性
OBJC_EXPORT objc_property_t _Nullable
protocol_getProperty(Protocol * _Nonnull proto,
                     const char * _Nonnull name,
                     BOOL isRequiredProperty, BOOL isInstanceProperty)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
协议属性列表
OBJC_EXPORT objc_property_t _Nonnull * _Nullable
protocol_copyPropertyList(Protocol * _Nonnull proto,
                          unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

OBJC_EXPORT objc_property_t _Nonnull * _Nullable
protocol_copyPropertyList2(Protocol * _Nonnull proto,
                           unsigned int * _Nullable outCount,
                           BOOL isRequiredProperty, BOOL isInstanceProperty)
    OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0);
协议所遵守的协议
OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable
protocol_copyProtocolList(Protocol * _Nonnull proto,
                          unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
创建协议
OBJC_EXPORT Protocol * _Nullable
objc_allocateProtocol(const char * _Nonnull name) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
注册协议
OBJC_EXPORT void
objc_registerProtocol(Protocol * _Nonnull proto) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
添加协议方法
OBJC_EXPORT void
protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name,
                              const char * _Nullable types,
                              BOOL isRequiredMethod, BOOL isInstanceMethod) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
协议添加
OBJC_EXPORT void
protocol_addProtocol(Protocol * _Nonnull proto, Protocol * _Nonnull addition) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
添加属性
OBJC_EXPORT void
protocol_addProperty(Protocol * _Nonnull proto, const char * _Nonnull name,
                     const objc_property_attribute_t * _Nullable attributes,
                     unsigned int attributeCount,
                     BOOL isRequiredProperty, BOOL isInstanceProperty)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

objc_copyImageNames
class_getImageName
objc_copyClassNamesForImage

获取加载的框架和动态库名
OBJC_EXPORT const char * _Nonnull * _Nonnull
objc_copyImageNames(unsigned int * _Nullable outCount) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
获取动态库名
OBJC_EXPORT const char * _Nullable
class_getImageName(Class _Nullable cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
返回库中所有类名
OBJC_EXPORT const char * _Nonnull * _Nullable
objc_copyClassNamesForImage(const char * _Nonnull image,
                            unsigned int * _Nullable outCount) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

sel_getName
sel_registerName
sel_isEqual

获取选择器指定的方法名
OBJC_EXPORT const char * _Nonnull
sel_getName(SEL _Nonnull sel)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
注册方法并映射到选择器
OBJC_EXPORT SEL _Nonnull
sel_registerName(const char * _Nonnull str)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
判断两个选择器是否相等
OBJC_EXPORT BOOL
sel_isEqual(SEL _Nonnull lhs, SEL _Nonnull rhs) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

_ objc_enumerationMutation
objc_setEnumerationMutationHandler
objc_setForwardHandler
imp_implementationWithBlock
imp_getBlock
imp_removeBlock
objc_loadWeak
objc_storeWeak_

突变
OBJC_EXPORT void
objc_enumerationMutation(id _Nonnull obj) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
设置突变处理函数
OBJC_EXPORT void
objc_setEnumerationMutationHandler(void (*_Nullable handler)(id _Nonnull )) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
设置由objc_msgForward调用的函数。
OBJC_EXPORT void
objc_setForwardHandler(void * _Nonnull fwd, void * _Nonnull fwd_stret) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
创建一个指向该块的函数指针
OBJC_EXPORT IMP _Nonnull
imp_implementationWithBlock(id _Nonnull block)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
与使用创建的IMP关联的块
OBJC_EXPORT id _Nullable
imp_getBlock(IMP _Nonnull anImp)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
块移除
OBJC_EXPORT BOOL
imp_removeBlock(IMP _Nonnull anImp)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);
加载由弱指针引用的对象(__week时会调用)
OBJC_EXPORT id _Nullable
objc_loadWeak(id _Nullable * _Nonnull location)
    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
存储新值到一个__week修饰的变量中
OBJC_EXPORT id _Nullable
objc_storeWeak(id _Nullable * _Nonnull location, id _Nullable obj) 
    OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);

objc_setAssociatedObject
objc_getAssociatedObject

使用给定的键和关联策略设置给定对象的关联值。
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
返回给定键的给定对象的值。
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
移除对象的关联
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

至此runtime.h中的方法基本上都已简单做了介绍(再往下就是一些参数结构体的定义和过时方法),看了这么久我想大家可能心里已经有一些想法,和一些oc底层实现流程的模糊猜想,

小结:
1.不管是类对象还是类都有一个isa指针,指向其所属的类,例如:Person类继承NSObject其实例对象person的isa指向Person,Person指向Person元类(元类相当于制造类的模子其中包含属性列表和方法列表等)Person元类的isa指向Person父元类(Person元类是Person父元类的实例),直至根元类(根元类的isa指向本身)。
2.属性可以看作是:Ivar+set方法+get方法
3.方法的主要构成为:选择器SEL + 函数指针(IMP)方法的实现主要体现在函数指针所指向的函数

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 从画面整体看,比较偏上方,作者对未来充满期待。四肢的部分,腿有点短。画的树,树干也比较细,可能行动力比较多。 整体...
    vicky_cd04阅读 241评论 0 0
  • 1、概念 活动图用来描述活动流程。活动用圆角矩形表示,箭头表示从一个活动转移到下一个活动,起点与终点与状态图相同。...
    IvanHung阅读 2,844评论 0 2
  • 总裁深度爱(完) 微博/门朦 许曦开车到赵天耀指定的公寓楼下,上到18层,走到18034门前,按响了门铃。 ...
    门朦阅读 836评论 0 1
  • “三十岁才开始学编程靠谱吗?”“种一棵树最好的时间是十年前,其次是现在。” ——源自知乎上的一段经典问答。 前段时...
    鹿鸣以念阅读 446评论 0 1