runtime介绍及基本使用

1. 概念

runtime(运行时系统),是一套基于C语言API,包含在 <objc/runtime.h>和<objc/message.h>中,运行时系统的功能是在运行期间(而不是编译期或其他时机)通过代码去动态的操作类(获取类的内部信息和动态操作类的成员),如创建一个新类、为某个类添加一个新的方法或者为某个类添加实例变量、属性,或者交换两个方法的实现、获取类的属性列表、方法列表等和Java中的反射技术类似。

2. 探索

程序最终运行的是二进制的可执行文件,编译器需要将OC代码转换为运行时代码,再将运行时代码经过一些处理成最终的二进制可执行文件

mian.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [[NSObject alloc] init];
    }
    return 0;
}

使用终端命令行切换到mian.m文件所在的目录下并执行: clang -rewrite-objc main.m

在mian.m文件目录所在的位置会有一个 main.cpp文件,打开文件可以看到OC代码都被转换成运行时runtime代码了

runtime.h和message.h中的方法一般以objc_、 class_、 method_、 property_、 ivar_、 protocol_、object_、  sel_等作为前缀,用前缀表明操作的对象

3.常用功能

1.动态交换两个方法的实现

2.动态添加对象的成本变量和成员方法

3.获取某个类的所有成员变量和成员方法

4.实现NSCoding的自动归档和自动解档

5.实现字典和模型的自动转换

6.为类别添加属性(我们知道类别是不能扩展属性的,只能扩展方法,但可以运行时可以实现,通过为类增加属性)

4.runtime常用的数据类型

OC源代码最终会翻译成运行时代码,而runtime是一套C语言API,也就是说OC的数据类型最终也会翻译成C语言中的数据类型

Objective-C ------->  runtime

类(Class)              objc_class*

id                            objc_object*

方法(Method)       objc_method

变量(Ivar)              objc_ivar*

struct objc_class {

Class isa;

Class super_class

const char *name

long version

long info

long instance_size

struct objc_ivar_list *ivars

struct objc_method_list **methodLists

struct objc_cache *cache

struct objc_protocol_list *protocols

};

typedef struct objc_class* Class;

struct objc_object {

Class isa;

};

typedef struct objc_object* id;

struct objc_method {

SEL method_name,       // 方法名

char* method_types,    // 方法的参数类型

IMP method_imp         // 方法实现代码的指针

};

typedef objc_method Method;

struct objc_ivar {

char *ivar_name

char *ivar_type

int ivar_offset

int space

}

typedef struct objc_ivar* Ivar;

5.runtime 常用API

// 创建类对

Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) 

// 添加方法

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

// 添加属性

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

// 注册类对

void  objc_registerClassPair(Class cls) 

// 向某个对象发送某个消息

id objc_msgSend(id self, SEL op, ...)

// 获取某个类的类方法

Method class_getClassMethod(Class cls, SEL name)

// 获取某个类的实例方法

Method class_getInstanceMethod(Class cls, SEL name)

// 为类添加一个属性

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

// 获取属性对应的值

id objc_getAssociatedObject(id object, const void *key)

// 获取实例变量列表

Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

// 获取方法的类型(方法的签名,返回值类型,参数类型)

const char *method_getTypeEncoding(Method m)

6.使用runtime

示例1:交换两个方法的实现

<span style="font-weight: normal;">#import <Foundation/Foundation.h>
@interface Person : NSObject

@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *address;
@property (assign, nonatomic)int age;
@property (assign, nonatomic)double hight;
@property (assign, nonatomic)BOOL gender;

+ (void)run;
- (void)study;
@end
//--------------------------------------------------------------------------------------
#import "Person.h"
@implementation Person

+ (void)run {
    NSLog(@"run。。。");
}

- (void)study {
    NSLog(@"study...");
}
@end

//--------------------------------------------------------------------------------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Method runMethod = class_getClassMethod([Person class], @selector(run));
        Method studyMethod = class_getInstanceMethod([Person class], @selector(study));
        method_exchangeImplementations(runMethod, studyMethod);

        [Person run];  // 打印 study...
        [[[Person alloc] init] study];   // 打印 run。。。
    }
    return 0;
}</span>

实例2:为类添加属性

分类(类别)是用来扩展方法的,不能扩展属性,但并不是说类别中不能写 @proprty, 如果类别中有 @proprty,意思是说为该属性生产getter&&setter方法,但是不生成带下划线的实例变量。

新建一个类别为列表添加属性,并重写getter&&setter

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface Person (AddProperty)

@property (copy, nonatomic) NSString *name;

@end

#import "Person+AddProperty.h"
@implementation Person (AddProperty)
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, "name");
}
@end

//------------------------------------------------------------------------------
#import <Foundation/Foundation.h>
#import "Person+AddProperty.h"
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.name = @"小红";

        NSLog(@"使用类别(分类)间接为类添加属性, person.name = %@", person.name);
    }
    return 0;
}

程序解释:当程序通过点语法调用 person.name = @"小红" 的时候实际上是执行的[person setName:@"小红"];

即调用相应的Set方法,该方法使用runtime动态的为类关联一个属性并赋值;当执行person.name 的时候,

实际上是执行[person name]; 即调用相应的Get方法,使用runtime获取关联的属性值。

使用类别也是可以做到为类添加属性的。(当会的知识增多时,就会发现之前认为对的可能都是错的)

示例3:获取实例变量列表

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([Person class], &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            const char* name = ivar_getName(ivar);
            const char* type = ivar_getTypeEncoding(ivar);

            NSLog(@"%s :%s", name, type);
        }
        free(ivars);
    }
    return 0;
}

2016-07-19 17:48:28.788 Runtime[13864:3404989] _gender :c
2016-07-19 17:48:28.789 Runtime[13864:3404989] _age :i
2016-07-19 17:48:28.789 Runtime[13864:3404989] _address :@"NSString"
2016-07-19 17:48:28.789 Runtime[13864:3404989] _hight :d
Program ended with exit code: 0

示例4:动态创建类并添加方法

使用运行时系统API以动态方式创建类的步骤:

1.新建一个类及元类

2.向这个类添加方法和实例变量

3.注册新建的类

static void display(id self, SEL _cmd){
    NSLog(@"invoke method with selector %@ on %@ instance", NSStringFromSelector(_cmd), [self class]);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    // 1. 创建一个类对
    Class WidgetClass = objc_allocateClassPair([NSObject class], "Widget", 0);

    // 2. 为该类添加一个方法
    class_addMethod(WidgetClass, @selector(display), (IMP)display, "[email protected]:");

    // 3. 为该类添加一个实例变量
    class_addIvar(WidgetClass, "height", sizeof(id), rint(log2(sizeof(id))), @encode(id));

    // 4. 注册类对
    objc_registerClassPair(WidgetClass);

    // 5. 创建实例变量并赋值、调用方法
    id widge = [[WidgetClass alloc] init];
    [widge setValue:@(15) forKey:[NSString stringWithUTF8String:"height"]];
    NSLog(@"Widge instance height = %@", [widge valueForKey:[NSString stringWithUTF8String:"height"]]);
    objc_msgSend(widge, NSSelectorFromString(@"display"));

    // 6. 动态方式添加一个属性
    objc_setAssociatedObject(widge, @"width", @(10), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    // 7. 获取
    id result = objc_getAssociatedObject(widge, @"width");
    NSLog(@"Widget instance width = %@", result);
    }
    return 0;
}

示例5:归档解档

先来看一个常用的写法:

#import <Foundation/Foundation.h>
@interface Student : NSObject <NSCoding>

@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic)int age;
@property (assign, nonatomic)double weight;
@property (copy, nonatomic)NSArray *hobby;
@property (copy, nonatomic)NSDictionary *others;

@end

#import "Student.h"

#define knameKey @"name"
#define kageKey @"age"
#define kweightKey @"weight"
#define khobbyKey @"hobby"
#define kothersKey @"others"

@implementation Student
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:_name forKey:knameKey];
    [aCoder encodeInt:_age forKey:kageKey];
    [aCoder encodeDouble:_weight forKey:kweightKey];
    [aCoder encodeObject:_hobby forKey:khobbyKey];
    [aCoder encodeObject:_others forKey:kothersKey];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        _name = [aDecoder decodeObjectForKey:knameKey];
        _age = [aDecoder decodeIntForKey:kageKey];
        _weight = [aDecoder decodeDoubleForKey:kweightKey];
        _hobby = [aDecoder decodeObjectForKey:khobbyKey];
        _others = [aDecoder decodeObjectForKey:kothersKey];
    }

    return self;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Student *student = [[Student alloc] init];
    student.name = @"小红";
    student.age = 25;
    student.weight = 100.5;
    student.hobby = @[@"吃", @"喝", @"玩", @"乐"];
    student.others = @{@"phone": @"1234567890", @"wechat": @"123456"};

    NSString *path = @"/Users/macmini/Documents/Test/Student.plist";
    [NSKeyedArchiver archiveRootObject:student toFile:path];

    Student *xiaoming = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    NSLog(@"xiaoming:%@", xiaoming);

    }
    return 0;
}	

普通的写法每个类如果要归档解档的话都要实现NSCoding的协议方法,该方法的实现都很类似,可以使用runtime进行简化操作,尽可能的达到至简

#import "Student.h"
#import <objc/runtime.h>

@implementation Student

// 获取所有成员变量进行循环编码
- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i++) {
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
        id value = [self valueForKey:key];

        [aCoder encodeObject:value forKey:key];
    }

    free(ivars);
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i++) {
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
            id value = [self valueForKey:key];

            [self setValue:value forKey:key];
        }

        free(ivars);
    }

    return self;
}

@end

普通写法每个类都要写一次,使用runtime虽然每个类都要写一次,但是代码都是完全一样的,可以直接粘贴复制,

既然都是一样的,我们可以使用类别(分类)给NSObject增加这两个方法,这样就能简化代码

//该实现也实现了对父类属性进行归档解档的实现
#import "NSObject+Archive.h"
#import <objc/runtime.h>

@implementation NSObject (Archive)

// 先对当前类进行编码,然后对父类进行编码,如果父类是NSObject就结束编码
- (void)encode:(NSCoder *)aCoder {
    Class clazz = self.class;
    while (clazz && clazz != [NSObject class]) {
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList(clazz, &outCount);
        for (int i = 0; i < outCount; i++) {
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
            id value = [self valueForKey:key];

            [aCoder encodeObject:value forKey:key];
        }

        free(ivars);
        clazz = [clazz superclass];
    }
}

- (void)decode:(NSCoder *)aDecoder {
    Class clazz = self.class;
    while (clazz && clazz != [NSObject class]) {
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList(clazz, &outCount);
        for (int i = 0; i < outCount; i++) {
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
            id value = [aDecoder decodeObjectForKey:key];

            [self setValue:value forKey:key];
        }

        free(ivars);
        clazz = [clazz superclass];
    }
}

@end
#import <Foundation/Foundation.h>
#import "Person.h"

// 继承Person类
@interface Student : Person <NSCoding>

@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic)double weight;
@property (copy, nonatomic)NSArray *hobby;
@property (copy, nonatomic)NSDictionary *others;

@end

#import "Student.h"
#import "NSObject+Archive.h"
#import <objc/runtime.h>

@implementation Student
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        [self decode:aDecoder];
    }

    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [self encode:aCoder];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Student *student = [[Student alloc] init];
    student.name = @"小红";
    student.age = 25;
    student.weight = 100.5;
    student.hobby = @[@"吃", @"喝", @"玩", @"乐"];
    student.others = @{@"phone": @"1234567890", @"wechat": @"123456"};

    student.address = @"父类属性address";
    student.hight = 180.5;
    student.gender = YES;

    NSString *path = @"/Users/macmini/Documents/Test/Student.plist";
    [NSKeyedArchiver archiveRootObject:student toFile:path];

    Student *xiaoming = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    NSLog(@"xiaoming:%@", xiaoming);
    }
    return 0;
}	

该方式可以简化每个类中归档和解档的代码量,但仍可以进行再简化,就是将NSCoding的实现定义成宏

创建一个.h文件

#ifndef Coding_h
#define Coding_h

#import "NSObject+Archive.h"

#define CodingImplemention - (instancetype)initWithCoder:(NSCoder *)aDecoder {if (self = [super init]) {    [self decode:aDecoder];}    return self;}- (void)encodeWithCoder:(NSCoder *)aCoder {    [self encode:aCoder];}

#endif /* Coding_h */
#import "Student.h"
#import "Coding.h"
@implementation Student

CodingImplemention

@end

在Student.m 文件中只需一个单词即可实现归档解档,可以看到已经达到至简了

示例6:使用runtime将字典转为模型

字典转模型需要考虑三种特殊情况

1.当字典中的key和模型的属性匹配不上

2.模型中嵌套模型

3.数组中的元素是模型

第一种情况分:当字典中字段多个类中的属性时,不用做任何处理,因为runtime是获取类中的所有 属性并循环的,字典中多的就不用管了。当类的字段多于字典中的字段,根据该字段去属性中获取值如果获取不到就继续下次循环;

第二种情况:利用runtime的ivar_getTypeEncoding方法获取实例变量的数据类型,如果是自定义的类也需要调用转换

第三种情况:第二种情况能够判断数据类型,就可以知道是否为数组,但是我们不知道数组里面的数据类型,我们可以提供一个函数,让用户指定数组元素的数据类型

@interface School : NSObject

@property (copy, nonatomic)NSString * ID;
@property (copy, nonatomic)NSString * name;

@end

#import "School.h"

@implementation School

@end
#import <Foundation/Foundation.h>

@interface Address : NSObject

@property (copy, nonatomic)NSString * ID;
@property (copy, nonatomic)NSString * address;

@end

#import "Address.h"

@implementation Address

@end
#import <Foundation/Foundation.h>
#import "School.h"

@interface User : NSObject

@property (copy, nonatomic)NSString * ID;
@property (copy, nonatomic)NSString * name;
@property (assign, nonatomic)int age;
@property (strong, nonatomic)School *school;
@property (copy, nonatomic)NSArray *address;

@end

#import "User.h"

@implementation User

- (NSDictionary *)eleTypeForArray {
    return @{@"address": @"Address"};
}

@end
#import <Foundation/Foundation.h>

@interface NSObject (Dict2Model)

- (void)setDict:(NSDictionary *)dict;
+ (instancetype)initWithDict:(NSDictionary *)dict;
- (NSDictionary *)eleTypeForArray;

@end

#import "NSObject+Dict2Model.h"
#import <objc/runtime.h>

@implementation NSObject (Dict2Model)

- (void)setDict:(NSDictionary *)dict {
    Class clazz = self.class;
    while (clazz && clazz != [NSObject class]) {
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList(clazz, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            key = [key substringFromIndex:1];  // 去掉实例变量中的下划线

            id value = dict[key];
            NSLog(@"%d: key:%@  value:%@", i, key, value);

            // 1. 如果类的实例变量多于字典中key
            if (value == nil) {
                continue;
            }

            // 2. 实例变量类型以@开头并且前缀不是NS开头的(排除系统类) "@Student"
            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            NSRange range = [type rangeOfString:@"@"];
            if (range.location != NSNotFound) {
                type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
                if (![type hasPrefix:@"NS"]) {
                    Class class = NSClassFromString(type);
                    value = [class initWithDict:value];
                } else if ([type isEqualToString:@"NSArray"]) {
                    NSArray *array = (NSArray *)value;
                    NSMutableArray *mArray = [NSMutableArray array];

                    id class;
                    if ([self respondsToSelector:@selector(eleTypeForArray)]) {
                        NSString *eleTypeStr = [[self eleTypeForArray] objectForKey:key];
                        class = NSClassFromString(eleTypeStr);
                    } else {
                        NSLog(@"数组类型不明确!");
                        return;
                    }

                    for (int i = 0; i < array.count; i++) {
                        id obj = [class initWithDict:value[i]];
                        [mArray addObject:obj];
                    }
                    value = mArray;
                }
            }

            [self setValue:value forKeyPath:key];
        }

        free(ivars);
        clazz = [clazz superclass];
    }
}

+ (instancetype)initWithDict:(NSDictionary *)dict {
    NSObject *obj = [[self alloc] init];
    [obj setDict:dict];

    return obj;
}
@end
Student.json
{
    "name" : "Tom",
    "age" : 20,
    "weight" : "181",
    "school":{
        "ID":1,
        "name":"北京大学"
    },
    "address" : [
                  {
                   "ID":1,
                   "address":"上海市"
                  },
                  {
                     "ID":2,
                     "address":"北京市"
                  }
               ]
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSData *jsonData = [NSData dataWithContentsOfFile:@"/Users/macmini/Documents/Test/RuntimeWidget/RuntimeWidget/Student.json"];
	NSDictionary *userDict = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL];
	User *user = (User *)[User initWithDict:userDict];
	School *school = user.school;
	Address *address = user.address[0];
	NSLog(@"User: %@, %d, %@, %@", user.name, user.age, school.name, address.address);
    }
    return 0;
}

2016-07-21 08:57:26.089 Runtime[15421:3827096] User: Tom, 20, 北京大学, 上海市

Program ended with exit code: 0

本文是在 http://www.jianshu.com/p/ab966e8a82e2 的基础上加上自己的理解和完整的示例写作而成

时间: 2024-08-05 09:48:26

runtime介绍及基本使用的相关文章

Runtime 介绍

1.简介 Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时.也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码.这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石. 1>OC 是一个全动态语言,OC 的一切都是基于 Runtime 实现的平时编写的OC代码, 在程序运行过程中, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者比如:OC

利用runtime,避免UIButton 重复点击, 可变数组和可变字典为nil,或者数组越界导致的崩溃

Demo链接: https://github.com/ShaoWenLe/Runtimer-Demo.git 参考文章: http://www.jianshu.com/p/080a238c62b9 相关Runtime介绍: http://www.cocoachina.com/ios/20160523/16386.html http://www.cocoachina.com/ios/20160628/16843.html 1 #import <Foundation/Foundation.h> 2

Runtime的初步认识——结构体与类

Runtime的初步认识 Runtime的初步认识 Runtime介绍 类与结构体的关系 结构体解析 结构体的作用 Runtime介绍 学习一个东西至少要先知道它是个啥,你一定听说过"运行时是 Objective-C 的一个特色",这里的"运行时"就是指 runtime 了. runtime是在自 iOS 平台开放并基于 Objective-C 语言开发后的一个编程语言上的高级技术. 学习runtime的目的并不是为了开发,而是让你更好的理解 Objective-C

MJExtension和JSONModel的使用

1.使用目的:实现JSON 与model之间的转换. 我们经常要从服务器上获取JSON数据,将其转化为Model. 这个过程是无疑是痛苦的.对于JSON数据量相对较少,或者Model里面的属性值较少的情况,处理起来不大费劲.但上架的应用大多是数据量巨大,与后台交互频繁的.更糟的是,后台接口频繁变化,那么维护起来就相当费劲了,因为你每次都要根据新的接口文档来逐一解释数据.往往每次要花你半天时间去修改.调试代码. 2.JSONModel JSON -> Dictionary -> Model 以下

RunTime.getRuntime().exec()运行脚本命令介绍和阻塞

java在企业级项目开发中,无论是强制性的功能需要,还是为了简便java的实现,需要调用服务器命令脚本来执行.在java中,RunTime.getRuntime().exec()就实现了这个功能. 用法:         public Process exec(String command)-----在单独的进程中执行指定的字符串命令. public Process exec(String [] cmdArray)---在单独的进程中执行指定命令和变量 public Process exec(S

Object,String,StringBuffer,StringBuilder,System,Runtime,Date,Math介绍及用法(API)

1       Object对象 面向对象的核心思想:“找合适的对象,做适合的事情”. 合适的对象: 自己描述类,自己创建对象. sun已经描述了好多常用的类,可以使用这些类创建对象. API(Application Program Interface) sun定义的那么多类的终极父类是Object.Object描述的是所有类的通用属性与方法. 1.1   toString方法 toString() 返回对象的描述信息   [email protected]   类名@哈希码值的十六进制形式.

Linux Runtime PM介绍

一.Runtime PM引言 1. 背景 (1)display的需求 (2)系统整机动态功耗优化的需求 (3)upstream 2. 解决方案 (1)引入debounce (2)使用统一的workqueue来管理任务 (3)实时地关闭不需要工作的device (4)当device作为parent时,所有的child不工作时,关闭该device (5)引入pm_rutime 3. 性能指标 (1)快速开关屏场景,亮屏速度提升 (2)动态功耗,更为稳定:唤醒而不亮屏的场景,功耗更低 (3)有助于降低

Java中RunTime类介绍

Runtime 类代表着Java程序的运行时环境,每个Java程序都有一个Runtime实例,该类会被自动创建,我们可以通过Runtime.getRuntime() 方法来获取当前程序的Runtime实例. 获取当前Jvm的内存信息 /* * 获取当前jvm的内存信息,返回的值是 字节为单位 * */ public static void getFreeMemory() { //获取可用内存 long value = Runtime.getRuntime().freeMemory(); Syst

Objective-C runtime初识

Objective-C Runtime Describes the macOS Objective-C runtime library support functions and data structures. Overview(概述) 以下是官方文档中对Runtime给出的定义 The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objecti