Objective-C 面向对象(一)——对象的本质

栏目: Objective-C · 发布时间: 5年前

内容简介:对于一门编程语言而言,当初学者掌握了其基本语法和标准库的使用以后,如果能够继续了解该语言的核心思想与底层实现,则会渐入佳境,窥探语言实现的本质技术,能够为以后的性能优化以及规避技术陷阱等复杂工作提供思路。 了解Objective-C语言的面向对象的本质,有益于程序员更加深刻理解该语言是如何实践OOP思想,并使得在构建面向对象程序的时候更加游刃有余。Objective-C是C语言的超集,也就是说,C语言的语法全部被Objective-C兼容,而面向对象的特性则是建立在C语言的基础之上,当熟悉过C语言的指针、

对于一门编程语言而言,当初学者掌握了其基本语法和标准库的使用以后,如果能够继续了解该语言的核心思想与底层实现,则会渐入佳境,窥探语言实现的本质技术,能够为以后的性能优化以及规避技术陷阱等复杂工作提供思路。 了解Objective-C语言的面向对象的本质,有益于 程序员 更加深刻理解该语言是如何实践OOP思想,并使得在构建面向对象程序的时候更加游刃有余。

背景知识

Objective-C是 C语言 的超集,也就是说,C语言的语法全部被Objective-C兼容,而面向对象的特性则是建立在C语言的基础之上,当熟悉过C语言的指针、内存管理、自定义数据-结构体等一系列知识以后,对于Objective-C的面向对象实现的理解,就容易多了,因为本质上,Objective-C的面向对象,就是使用这些东西构建出来的。 我们需要了解的是,对于C语言来说,除了语言本身定义的数据类型,程序员想要自定义数据类型以供编程使用,结构体是必然选择,基于这样的事实,那么理应能够猜到,Objective-C中的一切面向对象概念,诸如类、对象等,都是基于C语言的结构体之上构建的,而如何进行对象方法的调用、类方法调用等等,则通过Objective-C从smalltalk借鉴过来的消息调用思想而实现的Runtime思想,后者是消息调用思想的鼻祖。

什么是类和对象

C语言是没有面向对象概念的,只有基本数据类型、指针、结构体等等。那么如何通过这些概念构建面向对象的概念,要明白这个的前提是大体总结一下对象和类有什么特点。

类是描述一个对象规格的模板,即它说明了一个对象有什么样的属性,有什么样的方法。对象的构建,通过指定类,并且初始化,从而得到类的实例-对象,那么也就是说类是一种描述实例对象的数据结构。 在Objective-C中,标准库为Foundation,事实上几乎所有的类都继承与NSObject,那么类具体有如下表现

  1. 类具有方法和类方法的声明,描述对象实例有什么方法和类有什么方法
  2. 类具有属性的声明,描述对象实例有什么样的属性
  3. 类可以被集成或集成其他类,从而给他人提供或从父类获取对象描述的规格信息

对象

对象是一个根据类实例化出来的数据结构。具有实例方法,实例变量,对象没有继承概念,只有持有其他对象或被其他对象持有,具有以下特点。

  1. 对象具有实例方法
  2. 对象具有实例属性
  3. 对象可以被其他对象持有或持有其他对象

类和对象的实现

既然类和对象只不过是特点不同的自定义数据类型,那么类和对象必然要使用结构体实现,Objetcive-C也是这样设计的,我们看一下NSObject的定义:

*@interface* NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
+ (void)load;
+ (void)initialize;
- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
    NS_DESIGNATED_INITIALIZER
#endif
    ;
+ (instancetype) new  OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
+ (instancetype)allocWithZone:(struct _NSZone )zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

*@end*
复制代码

NSObject的定义中,有类方法定义、属性定义、实例方法定义,如何使用C语言的结构体来表达和存储这样的自定义数据结构呢?NSObject是一个Class也就是一个类,在描述中有一个Class isa的变量,按图索骥查找到Class的数据结构:

typedef struct objc_class *Class; //class是一个objc_class结构体的指针
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE; //objc_class是一个结构体,描述了一个类的信息
复制代码

从上面定义可以看出来,一个类包含哪些信息,是通过objc_class 结构体来表示的,NSObject的定义中,有一个Class isa属性,而Class是一个指向objc_class结构体的指针,也就是说,NSObject通过isa指针来寻找到其类的信息所在的结构体。 该结构体中有几个比较重要的变量:

  • Class isa OBJC_ISA_AVAILABILITY; 又是一个指向objc_class结构体的指针,指向另外一个类信息的结构体,那么一个类指向一个说明其规格的类结构体,其意义是来描述类的信息,一般称描述类的结构的数据类型称之为元类,即meta-class,以为着使用元类来描述类的规格,那么从对象与类的关系类比中,可以将类看作是元类的实例,也就说,元类是类对象的类。
  • super_class 是该类父类的信息,使用super_class指针,找到父类信息的结构体。NSObject的实例对象的superclass为null
  • long info OBJC2_UNAVAILABLE; 类信息,供运行期使用的一些位标识
  • long instance_size OBJC2_UNAVAILABLE; /该类的实例变量大小
  • struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; 类的成员变量链表
  • struct objc_method_list methodLists OBJC2_UNAVAILABLE; 方法定义的链表
  • struct objc_cache *cache OBJC2_UNAVAILABLE; 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在methodLists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
  • struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; 协议链表

类的实例->对象也是通过一个objc_class结构体描述其结构。如下:

struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};

typedef struct objc_object *id;
复制代码

id 类型,即对象,其为一个指向objc_object结构体的指针,意味着任意一个Objective-C的对象,其本质是一个指向objc_object的结构体指针,而objc_object结构体中,有一个isa指针,指向objc_class结构体,来描述其属于哪个类,也就是上面的类信息结构体。 当定义一个类的时候,如下:

//main.m
@interface ClassA : NSObject
@property(nonatomic,copy)NSString * name;
-(void)sayHello;
+(void)SayHello;
@end
@implementation ClassA
-(void)sayHello{
    NSLog(@"object say hello");
}
+(void)SayHello{
    NSLog(@"class say hello");
}

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
        NSObject * obj1 = [[NSObject alloc]init];
        ClassA * obj2 = [[ClassA alloc]init];
        [obj2 sayHello];
        [ClassA SayHello];
        NSLog(@"%@", NSStringFromClass([obj1 superclass]));
    }
    return 0;
}
复制代码

从上面的定义中,按照之前的说明,将该文件转换为C++代码,将在C++代码中得到确切的信息。

# 得到main.cpp文件
clang -rewrite-objc main.m 
复制代码

对于NSObject这个类,可以得到:

#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif
复制代码

NSObject是一个objc_object结构体。 ClassA的结构如下:

typedef struct objc_object ClassA;
复制代码

ClassA是一个指向objc_object结构体,其相关的其他部分为:

#ifndef _REWRITER_typedef_ClassA
#define _REWRITER_typedef_ClassA
typedef struct objc_object ClassA;
typedef struct {} _objc_exc_ClassA;
#endif

extern "C" unsigned long OBJC_IVAR_$_ClassA$_name;
struct ClassA_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};

// @property(nonatomic,copy)NSString * name;
// -(void)sayHello;
// +(void)SayHello;
/* @end */

// @implementation ClassA

static void _I_ClassA_sayHello(ClassA * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_dn_6w6g4h112csgf73k_bz07xpr0000gn_T_main_08dee3_mi_0);
}

static void _C_ClassA_SayHello(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_dn_6w6g4h112csgf73k_bz07xpr0000gn_T_main_08dee3_mi_1);
}

static NSString * _I_ClassA_name(ClassA * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_ClassA$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_ClassA_setName_(ClassA * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ClassA, _name), (id)name, 0, 1); }
// @end
复制代码

这个结构中,清楚地描述出了ClassA类中的实例变量、类方法、实例方法的结构和实现。 ClassA是一个objc_object结构体,其类方法和静态方法在声明以后,被使用在如下:

extern "C" unsigned long int OBJC_IVAR_$_ClassA$_name __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct ClassA, _name);

static struct /*_ivar_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count;
	struct _ivar_t ivar_list[1];
} _OBJC_$_INSTANCE_VARIABLES_ClassA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_ivar_t),
	1,
	{{(unsigned long int *)&OBJC_IVAR_$_ClassA$_name, "_name", "@\"NSString\"", 3, 8}}
};

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[3];
} _OBJC_$_INSTANCE_METHODS_ClassA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	3,
	{{(struct objc_selector *)"sayHello", "v16@0:8", (void *)_I_ClassA_sayHello},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_ClassA_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_ClassA_setName_}}
};

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_ClassA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"SayHello", "v16@0:8", (void *)_C_ClassA_SayHello}}
};

static struct /*_prop_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_ClassA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	1,
	{{"name","T@\"NSString\",C,N,V_name"}}
};
复制代码

OBJC _INSTANCE_METHODS_ClassA , OBJC _PROP_LIST_ClassA为该类的类方法、属性、实例方法等定义的结构体,这些结构体的被用到在:

static struct _class_ro_t _OBJC_CLASS_RO_$_ClassA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	0, __OFFSETOFIVAR__(struct ClassA, _name), sizeof(struct ClassA_IMPL), 
	(unsigned int)0, 
	0, 
	"ClassA",
	(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_ClassA,
	0, 
	(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_ClassA,
	0, 
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_ClassA,
};
复制代码

OBJC_CLASS_RO $_ClassA 结构体变量将类方法、属性等结构体进行包装,其数据类型_class_ro_t如下:

struct _class_ro_t {
	unsigned int flags;
	unsigned int instanceStart;
	unsigned int instanceSize;
	unsigned int reserved;
	const unsigned char *ivarLayout;
	const char *name;
	const struct _method_list_t *baseMethods;
	const struct _objc_protocol_list *baseProtocols;
	const struct _ivar_list_t *ivars;
	const unsigned char *weakIvarLayout;
	const struct _prop_list_t *properties;
};
复制代码

该结构体其实就是objc_class结构体的变形,代表根类的结构体类型。 OBJC_CLASS_RO $_ClassA又被另外一个结构体进行包装:

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_ClassA __attribute__ ((used, section ("__DATA,__objc_data"))) = {
	0, // &OBJC_METACLASS_$_ClassA,
	0, // &OBJC_CLASS_$_NSObject,
	0, // (void *)&_objc_empty_cache,
	0, // unused, was (void *)&_objc_empty_vtable,
	&_OBJC_CLASS_RO_$_ClassA,
};
复制代码

OBJC_CLASS_$_ClassA 则就是ClassA这个类的结构体,其结构体是_class_t。

struct _class_t {
	struct _class_t *isa;
	struct _class_t *superclass;
	void *cache;
	void *vtable;
	struct _class_ro_t *ro;
};
复制代码

OBJC_CLASS_ _ClassA两个结构体被用在:

static void OBJC_CLASS_SETUP_$_ClassA(void ) {
	OBJC_METACLASS_$_ClassA.isa = &OBJC_METACLASS_$_NSObject;
	OBJC_METACLASS_$_ClassA.superclass = &OBJC_METACLASS_$_NSObject;
	OBJC_METACLASS_$_ClassA.cache = &_objc_empty_cache;
	OBJC_CLASS_$_ClassA.isa = &OBJC_METACLASS_$_ClassA;
	OBJC_CLASS_$_ClassA.superclass = &OBJC_CLASS_$_NSObject;
	OBJC_CLASS_$_ClassA.cache = &_objc_empty_cache;
}
复制代码

这是一个类初始化函数,从函数中得到明确的信息是:

  1. OBJC_METACLASS_ _NSObject结构体,其类型为_class_ro_t(objc_class)的结构体
  2. OBJC_METACLASS_ _NSObject结构体指针
  3. OBJC_CLASS_ _ClassA
  4. OBJC_CLASS_ _NSObject根类数据结构 至此,可以非常清晰得出以下结论: 如下图所示:
Objective-C 面向对象(一)——对象的本质
  1. ClassA的实例对象,是一个objc_object结构体指针,其isa指针指向ClassA类的objc_object结构体
  2. ClassA,是一个_class_t(即objc_class)的结构体,其isa指针指向MetaClassA结构体
  3. ClassA的父类,是NSObject,其superclass是一个指向NSObject类对象的isa指针
  4. ClassA的元类,是MetaClassA,OBJC_METACLASS_元类结构体表明,其isa指针指向MetaNSObject结构体指针
  5. 一个类所拥有的方法、属性,都会存储在类的元类中,当调用对象的方法的时候,也就是向对象发送消息,runtime会在这个对象所属的类方法列表中查找消息对应的方法,但向类发送消息的时候,runtime就会在这个类的meta class的方法列表中查找。 更加通用和清晰的关系图如下:
Objective-C 面向对象(一)——对象的本质

即:

  1. 对象的isa指针指向类对象
  2. 对象的superclass的指针指向父类类对象
  3. 类对象的isa指针指向元类
  4. 类对象的superclass的指针指向父类元类
  5. 元类的isa指针,指向根类(NSObject)元类
  6. 元类的superclass指针指向父类元类,直接继承根类的类的元类的superclass指向根类元类(NSObject)
  7. 根类(NSObject)的isa,指向根元类
  8. 根类(NSObject)的superclass为nil
  9. 根元类的superclass指向NSObject类
  10. 根元类的isa指针,指向自身

类与对象的相关信息

name

const char * class_getName(Class cls);
复制代码

super_class和meta-class

//获取父类
Class class_getSuperclass(Class cls);
//判定类是否为一个meta class
BOOL class_isMetaClass(Class cls);
复制代码

instance_size

size_t class_gerInstanceSize(Class cls);
复制代码

objc_ivar_list与objc_method

//objc_ivar_list结构体存储objc_ivar数组列表
struct objc_ivar_list {
     int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
     int space OBJC2_UNAVAILABLE;
#endif
     /* variable length structure */
     struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

//objc_method_list结构体存储着objc_method的数组列表
struct objc_method_list {
     struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
     int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
     int space OBJC2_UNAVAILABLE;
#endif
     /* variable length structure */
     struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
复制代码

objc_ivar_list 为成员变量单向链表,其结构体最后一个成员变量是一个objc_ivar结构体数组,该数组为变长,所以objc_ivar_list可以是一个变长结构体,objc_ivar标示一个成员变量:

struct objc_ivar {
    char * _Nullable ivar_name  OBJC2_UNAVAILABLE;//变量名
    char * _Nullable ivar_type  OBJC2_UNAVAILABLE;//变量类型 
    int ivar_offset   OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space    OBJC2_UNAVAILABLE;
#endif
}   OBJC2_UNAVAILABLE;
复制代码

objc_method_list为方法列表

struct objc_method_list {
    struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
    int method_count  OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space    OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]  OBJC2_UNAVAILABLE;
}            
复制代码

objc_method为方法结构体,如下:

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}         
复制代码

IMP与SEL

typedef struct  *SEL;
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif
复制代码

IMP是一个void * ()的函数指针,实际上就是方法的实现,SEL为一个char * 字符串。 每一个objc_class结构体中都有objc_method_list列表,而objc_method_list列表中有objc_method结构体,该结构体为一个SEL对应一个IMP实现。 在runtime运行的时候,加载的每一个类对应有一个virtual table,用来缓存SEL与IMP的对应关系,从而能够通过SEL快速找到其对应实现。

成员变量(ivars)及其属性

//成员变量操作函数
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );

// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );

// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types ); //这个只能够向在runtime时创建的类添加成员变量

// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount ); //必须使用free()来释放这个数组

//属性操作函数
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );

// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );

// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );

// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
复制代码

methodLists

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types ); //和成员变量不同的是可以为类动态添加方法。如果有同名会返回NO,修改的话需要使用method_setImplementation

// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );

// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );

// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );

// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
复制代码

objc_protocol_list与Protocol

objc_protocol_list:

struct objc_protocol_list {
    struct objc_protocol_list * _Nullable next;
    long count;
    __unsafe_unretained Protocol * _Nullable list[1];
};
复制代码

Protocol的定义如下:

#ifdef __OBJC__
@class Protocol;
#else
typedef struct objc_object Protocol;
#endif
复制代码

对protocol的操作为:

// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );

// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );

// 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
复制代码

总结

Objective-C基于C语言的结构体定义面向对象的大部分概念,利用结构体指针与函数指针,来实现面向对象的类定义、类继承、实例化对象、对象成员变量和方法的存储与定义。 由此,Objective-C这本语言是一种在运行时发挥强大能力的语言,而这又归功于runtime这一消息分发系统,在运行时能够对类进行扫描、查找、调用、修改等等,这部分知识被称为rumtime核心技术,消息调用与动态类型的结合,使得Objective-C这门语言能够给予程序员非常大的自由度去享受编程的乐趣。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C语言入门经典

C语言入门经典

霍顿 (Ivor Horton) / 清华大学出版社 / 2008-4-1 / 69.80元

本书是编程语言先驱者Ivor Horton的经典之作,是C语言方面最畅销的图书品种之一。本书集综合性、实用性为一体,是学习C语言的优秀入门教材,在世界范围内广受欢迎,口碑极佳。书中除了讲解C程序设计语言,还广泛介绍了作为一名C程序设计人员应该掌握的必要知识,并提供了大量的实用性很强的编程实例。本书的目标是使你在C语言程序设计方面由一位初学者成为一位称职的程序员。读者基本不需要具备任何编程知识,即可......一起来看看 《C语言入门经典》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换