对象本质

一、NSObject 本质

OC代码的底层实现实质是 C/C++代码 ,继而编译成汇编代码,最终变成机器语言。

① clang C/C++ 编译器
  • Clang 是⼀个 C 语⾔、C++、Objective-C 语⾔的 轻量级编译器 ,源代码发布于 BSD 协议下。
  • Clang 将⽀持其 普通lambda表达式 ,返回类型的简化处理以及更好的 处理constexpr关键字 。
  • Clang 是⼀个由 Apple 主导编写,基于 LLVM的C/C++/Objective-C编译器 。
  • 2013年4⽉,Clang 已经全⾯⽀持 C++11标准 ,并开始实现 C++1y特性 (也就是 C++14,这是 C++ 的下⼀个⼩更新版本)。Clang 将⽀持其 普通lambda表达式 ,返回类型的简化处理以及更好的处理 constexpr关键字 。
  • Clang 是⼀个 C++ 编写,基于 LLVM 发布于 LLVM BSD 许可证下的 C/C++/Objective-C/Objective-C++ 编译器。它与 GNU C 语⾔规范⼏乎完全兼容(当然也有部分不兼容的内容,包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,⽐如 C 函数重载(通过 attribute((overloadable) )来修饰函数),其⽬标(之⼀)就是超越 GCC 。
  • clang -rewrite-objc main.m -o main.cpp 把⽬标⽂件编译成c++⽂件。
  • UIKit报错问题:Xcode安装的时候顺带安装了 xcrun 命令, xcrun 命令在 clang 的基础上进⾏了⼀些封装。
 clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
  • xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o
    main-arm64.cpp (模拟器)
  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mainarm64.cpp (⼿机)
② 运用 clang 将目标文件编译成 cpp(C++文件)
  • 在main.m中添加以下代码:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>@interface YDWHandsomeBoy : NSObject
@property (nonatomic, copy) NSString *name;
@end@implementation YDWHandsomeBoy@endint main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...NSLog(@"Hello, World!");}return 0;
}
  • 打开终端,进入main.m所在的文件夹,通过 clang rewirte-objc main.m -o main.cpp 或 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 命令,生成cpp文件。

  • 然后回到main.m的文件中,打开cpp文件,搜索“YDWHandsomeBoy”,即可看到,对象YDWHandsomeBoy在底层被编译成了一个结构体:
 #ifndef _REWRITER_typedef_YDWHandsomeBoy#define _REWRITER_typedef_YDWHandsomeBoytypedef struct objc_object YDWHandsomeBoy;typedef struct {} _objc_exc_YDWHandsomeBoy;#endifextern "C" unsigned long OBJC_IVAR_$_YDWHandsomeBoy$_name;struct YDWHandsomeBoy_IMPL {struct NSObject_IMPL NSObject_IVARS;NSString *_name;};// @property (nonatomic, copy) NSString *name;/* @end */// @implementation YDWHandsomeBoy
  • 可以看到上面的c++代码中有个NSObject_IMPL NSObject_IVARS这个东西,然后搜索NSObject_IMPL可以发现:
 struct NSObject_IMPL {Class isa;};
  • 由此可以得出:

    • NSObject的底层实现实质是一个 结构体 ,而结构体中的成员 isa是Class类型 ;
    • 通过源码 typedef struct objc_class *Class 可知它是一个指针,在64为环境下指针占8个字节,而在32位机下是占4个字节,因此该结构体占8个字节(因为该结构体只有一个成员)。

二、NSObject 对象内存

  • 初始化一个NSObject并打印:
 NSObject *obj = [[NSObject alloc] init];NSLog(@"%zd",malloc_size((__bridge const void *)obj));
  • 可知NSObject对象占16个字节。那么与上文中NSObject结构体中占8个字节是否冲突?再次打印:
 NSLog(@"%zd",class_getInstanceSize([NSObject class]))
  • 不难发现:获取NSObject类的实例对象的成员变量所占用的(内存对齐之后)大小,显示确实为8个字节。
  • 在objc的源码中找到 class_getInstanceSize方法,发现它返回的是 cls->alignedInstanceSize() ,对它的描述为Class’s ivar size rounded up to a pointer-size boundary意指 返回成员变量占据的大小 。因此创建一个NSObject对象需要分配16个字节,只是真正利用的只有8个字节,即isa这个成员的大小。
  • 查看allocWithZone的源码发现它最底层的创建实例的方法实际上是调用了C语言的 calloc方法 ,在该方法中,规定若 分配的字节不满16将把它分配为16个字节 。

三、若一个YDWHandsomeBoy类继承自NSObject类,那么YDWHandsomeBoy类的对象占多少内存?

  • 新建YDWHandsomeBoy类,添加成员变量,在main中实现以下代码:
 #import <Foundation/Foundation.h>#import <objc/runtime.h>#import <malloc/malloc.h>@interface YDWHandsomeBoy : NSObject@property (nonatomic, copy) NSString *name;@property (nonatomic, copy) NSString *nickName;@property (nonatomic, assign) int age;@property (nonatomic, assign) int address;@property (nonatomic, assign) int number;@end@implementation YDWHandsomeBoy@endint main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...YDWHandsomeBoy *boy = [[YDWHandsomeBoy alloc] init];boy.name = @"Y";boy.nickName = @"D";boy.age = 18;boy.address = 10;boy.number = 11;NSLog(@"%zd",malloc_size((__bridge const void *)boy));}return 0;}打印结果如下:2020-09-02 15:44:27.345192+0800 iOS之对象isa[53982:3621024] 48
  • 通过以上代码可以看出:
  • 若一个类继承自另一个类,则它的底层会 将父类的成员变量放在结构体的最前面,此后依次放置本类的成员变量 。
  • 而从之前的分析可知,NSObject_IMPL的本质就是一个 装有成员变量isa的结构体 ,因此,YDWHandsomeBoy类对象所占的内存为isa的内存8加上YDWHandsomeBoy类成员变量所占的空间,若不满16个字节,会强制分配到16个字节。
  • 由于 内存对齐 的规定,结构体的最终大小必须是 最大成员的倍数 。

isa

一、isa 简介

  • alloc初始化时不仅 创建对象并且分配内存 ,同时 初始化 isa 指针属性。
  • Objective-C 对象在底层本质上是 结构体 ,所有的对象里面都会包含有一个 isa ,isa 的定义是一个 联合体 isa_t ,isa_t 包含了 当前对象指向类的信息 。
 union isa_t {isa_t() { }isa_t(uintptr_t value) : bits(value) { }Class cls;uintptr_t bits;#if defined(ISA_BITFIELD)struct {ISA_BITFIELD;  // defined in isa.h};#endif};
  • isa 是一个 联合体,而这其实是从内存管理层面来设计的,因为联合体是 所有成员共享一个内存,联合体 内存的大小取决于内部成员内存大小最大的那个元素 。
  • 对于 isa 指针来说,就不用额外声明很多的属性,直接在 内部的 ISA_BITFIELD 保存信息 。
  • 由于联合体 属性间互斥 ,所以 cls 和 bits 在 isa 初始化流程时是在 两个分支 中被赋值的。
 union isa_t {isa_t() { }isa_t(uintptr_t value) : bits(value) { }Class cls;uintptr_t bits;#if defined(ISA_BITFIELD)struct {ISA_BITFIELD;  // defined in isa.h};#endif};#   define ISA_MASK        0x00007ffffffffff8ULL#   define ISA_MAGIC_MASK  0x001f800000000001ULL#   define ISA_MAGIC_VALUE 0x001d800000000001ULL#   define ISA_BITFIELD                                                        \uintptr_t nonpointer        : 1;                                         \uintptr_t has_assoc         : 1;                                         \uintptr_t has_cxx_dtor      : 1;                                         \uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \uintptr_t magic             : 6;                                         \uintptr_t weakly_referenced : 1;                                         \uintptr_t deallocating      : 1;                                         \uintptr_t has_sidetable_rc  : 1;                                         \uintptr_t extra_rc          : 8#   define RC_ONE   (1ULL<<56)#   define RC_HALF  (1ULL<<7)
  • isa_t 是一个联合体,联合体的特性就是 内部所有的成员共用一块内存地址空间 ,也就是说isa_t、cls、bits会共用同一块内存地址空间,这块 内存地址空间大小取决于最大长度内部成员的大小 ,即64位8字节,由此可以知道isa 的所占的内存空间大小为8字节。isa_t联合体如下:
 struct {uintptr_t indexed           : 1;uintptr_t has_assoc         : 1;uintptr_t has_cxx_dtor      : 1;uintptr_t shiftcls          : 44;uintptr_t magic             : 6;uintptr_t weakly_referenced : 1;uintptr_t deallocating      : 1;uintptr_t has_sidetable_rc  : 1;uintptr_t extra_rc          : 8;};

二、isa 结构

isa 作为一个联合体,有一个结构体属性为 ISA_BITFIELD ,其大小为 8 个字节,也就是 64 位。基于__arm64__ 和 x86 64 架构如下:

 # if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \uintptr_t nonpointer        : 1;                                       \uintptr_t has_assoc         : 1;                                       \uintptr_t has_cxx_dtor      : 1;                                       \uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \uintptr_t magic             : 6;                                       \uintptr_t weakly_referenced : 1;                                       \uintptr_t deallocating      : 1;                                       \uintptr_t has_sidetable_rc  : 1;                                       \uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \uintptr_t nonpointer        : 1;                                         \uintptr_t has_assoc         : 1;                                         \uintptr_t has_cxx_dtor      : 1;                                         \uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \uintptr_t magic             : 6;                                         \uintptr_t weakly_referenced : 1;                                         \uintptr_t deallocating      : 1;                                         \uintptr_t has_sidetable_rc  : 1;                                         \uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)# else
#   error unknown architecture for packed isa
# endif
  • union-isa_t 存储分配如下:

  • nonpointer: 表示是否对 isa 指针 开启指针优化 :
    0: 纯 isa 指针;
    1: 不止是类对象地址, isa 中包含了类信息、对象的引用计数等。
  • has_assoc: 关联对象标志位 ,0 没有,1 存在。
  • has_cxx_dtor: 该对象 是否有 C++ 或者 Objc 的析构器 ,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
  • shiftcls: 存储类指针的值 ,开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
  • magic: 用于 调试器判断当前对象是真的对象还是没有初始化的空间 。
  • weakly_referenced: 标志对象是否被指向或者曾经 指向一个 ARC 的弱变量 ,没有弱引用的对象可以更快释放。
  • deallocating: 标志对象 是否正在释放内存 。
  • has_sidetable_rc: 当对象引用技术大于 10 时,则需要借用该变量 存储进位 。
  • extra_rc: 当表示该 对象的引用计数值 ,实际上是引用计数值减 1。 例如,如果对象的引用计数为 10,那么 extra_rc 为 9;如果引用计数大于 10, 则需要使用到has_sidetable_rc。

三、isa 初始化

① isa 源码实现
  • 在objc的源码中有isa的初始化方法:
 inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) {assert(!cls->instancesRequireRawIsa());assert(hasCxxDtor == cls->hasCxxDtor());initIsa(cls, true, hasCxxDtor);}inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { assert(!isTaggedPointer()); if (!nonpointer) {isa.cls = cls;} else {assert(!DisableNonpointerIsa);assert(!cls->instancesRequireRawIsa());isa_t newisa(0);#if SUPPORT_INDEXED_ISAassert(cls->classArrayIndex() > 0);newisa.bits = ISA_INDEX_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.indexcls = (uintptr_t)cls->classArrayIndex();#elsenewisa.bits = ISA_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.shiftcls = (uintptr_t)cls >> 3;#endif// This write must be performed in a single store in some cases// (for example when realizing a class because other threads// may simultaneously try to use the class).// fixme use atomics here to guarantee single-store and to// guarantee memory order w.r.t. the class index table// ...but not too atomic because we don't want to hurt instantiationisa = newisa;}}
  • 由于nonpointer传入的是true,SUPPORT_INDEXED_ISA定义为0,所以可以对这段代码简化一下:
 inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { isa_t newisa(0);newisa.bits = ISA_MAGIC_VALUE;newisa.has_cxx_dtor = hasCxxDtor;newisa.shiftcls = (uintptr_t)cls >> 3;isa = newisa;}
② isa 初始化数据
  • 可以看到对bits的赋值ISA_MAGIC_VALUE = 0x001d800000000001ULL,将此转为二进制,在结合isa_t的结构得出如下的isa_t的初始数据图:

  • 对 isa 赋值ISA_MAGIC_VALUE初始化实际上只是设置了indexed和magic两部分的数据:

    • indexed表示 isa_t 的类型 :0表示 raw isa,也就是没有结构体的部分,访问对象的 isa 会直接返回一个指向 cls 的指针,也就是在 iPhone 迁移到 64 位系统之前时 isa 的类型;1则表示当前 isa 不是指针,但是其中也有 cls 的信息,只是其中关于类的指针都是保存在 shiftcls 中。
    • magic 用于 调试器判断当前对象是否有初始化空间 。
  • 在设置indexed和magic的值后会对has_cxx_dtor进行设值。has_cxx_dtor表示该对象是否有 C++ 或者 Objc 的析构器 ,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
 newisa.has_cxx_dtor = hasCxxDtor;
  • 将当前对象的类指针存放在 shiftcls 中:
 newisa.shiftcls = (uintptr_t)cls >> 3;
  • 对cls的地址右移动3位的目的是 为了减少内存的消耗 ,因为类的指针需要按照8字节对齐,也就是说类的指针的大小必定是8的倍数,其二进制后三位为0 ,右移三位抹除后面的3位0并不会产生影响。
③ isa的初始化流程示意

四、isa 关联对象和类

isa 是对象中的第一个属性,这是在继承的时候发生的,要早于对象的成员变量,属性列表,方法列表以及所遵循的协议列表。在 alloc 底层,有一个方法叫做 initIsa ,这个方法的作用就是 初始化 isa 联合体位域 。上文中我们已经看到了这个方法:

 newisa.shiftcls = (uintptr_t)cls >> 3;
① cls 存储到 isa
  • isa 刚初始化时,还没有被赋值,bits 全为空值,p newisa 如下:

  • 继续向下执行,当断点执行到如下位置的时候,bits 会被赋上默认值(nonpointer = 1,magic = 59),继续p newisa如下:

  • 为什么 magic = 59 呢?其实,通过计算器可以转换算出:0x001d800000000001 = 59,上面我们已经 po 0x001d800000000001ULL 了,可以看到这个默认值。
  • 继续输出 bits 二进制、输出 cls 指针二进制可以看到:在未设置 shiftcls 时,bits 从右到左 [3, 46] 位都是0。如下:
 (lldb) p/t 8303511812964353(long) $3 = 0b0000000000011101100000000000000000000000000000000000000000000001(lldb) p/t (uintptr_t)cls(uintptr_t) $4 = 0b0000000000000000000000000000000100000000010001110100000000111000(lldb) p/t (uintptr_t)cls >> 3(uintptr_t) $5 = 0b0000000000000000000000000000000000100000000010001110100000000111(lldb)
  • 为什么要右移三位?在 Objective-C 中,类的指针是按照字节(8 bits)对齐的,也就是说类指针地址转化成十进制后,都是8的倍数,也就是说,类指针地址转化成二进制后,后三位都是0。既然是没有意义的0,那么在存储时就可以省略,用节省下来的空间存储一些其他信息。
  • 当 bits 被赋值之后,如下:

  • 可以看到,现在的 bits 的 [3, 46] 位正好是之前 cls 指针右移三位的内容。
② isa 关联对象和类
  • 通过 LLDB 进行调试打印,就可以知道一个对象的 isa 会关联到这个对象所属的类:

  • LLDB 调试的时候左移右移操作其实很好理解,先观察 isa 的 ISA_BITFIELD 位域的结构:ISA_BITFIELD 的前 3 位是 nonpointer、has_assoc、has_cxx_dtor ,中间 44 位是 shiftcls ,后面 17 位是剩余的内容,同时因为 iOS 是 小端模式 ,那么就需要去掉右边的 3 位和左边的 17位,所以就会采用 >> 3 << 3 然后 << 13 >> 13 的操作。

五、isa 走位分析

① class object(类对象)/ metaclass(元类)
  • Object-C的对象其本质就是结构体,前面也分析了每一个对象都会有一个isa。同时类的本质也是一个结构体,而且是继承自objc_object的。
 struct objc_object {private:isa_t isa;...};struct objc_class : objc_object {// Class ISA;Class superclass;// 方法缓存cache_t cache;             // formerly cache pointer and vtable// 用于获取具体的类信息class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags...};
  • 在objc_class中也有isa:
 struct objc_class : objc_object {isa_t isa;Class superclass;cache_t cache;             // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags...};
  • class_isMetaClass用于判断Class对象是否为元类,object_getClass用于获取对象的isa指针指向的对象。
OBJC_EXPORT BOOL class_isMetaClass(Class cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);OBJC_EXPORT Class object_getClass(id obj) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
  • 我们知道:对象可以创建多个,但是类是否可以创建多个呢?其实答案是否定的,类在内存中只会存在一份。
     Class class1 = [Boy class];Class class2 = [Boy alloc].class;Class class3 = object_getClass([Boy alloc]);Class class4 = [Boy alloc].class;NSLog(@"\n%p-\n%p-\n%p-\n%p",class1, class2, class3, class4);// 打印如下:0x10edbedc80x10edbedc80x10edbedc80x10edbedc8
  • 通过 LLDB 调试打印,其实可以发现:类的内存结构里面的第一个结构打印出来还是 Boy,那么是不是就意味着 对象 ->类->类 这样的死循环呢?这里的第二个类其实是 元类,是由系统创建的,这个元类无法被我们实例化。

  • 一个实例对象通过class方法获取的Class就是它的isa指针指向的类对象,而类对象不是元类,类对象的isa指针指向的对象是元类。关系如下:
② isa 走位
  • 官方的经典 isa 走位图:

    • 实例对象的isa指向的是类;
    • 类的isa指向的元类;
    • 元类指向根元类;
    • 根元类指向自己;
    • NSObject的父类是nil,根元类的父类是NSObject。

  • LLDB 调试打印:

六、对象的本质 isa

  • OC 对象的本质就是一个结构体,在 libObjc 源码的 objc-private.h 源文件中可以看到:
 struct objc_object {private:isa_t isa;public:// ISA() assumes this is NOT a tagged pointer objectClass ISA();// getIsa() allows this to be a tagged pointer objectClass getIsa();......}
  • 对于对象所属的类来说,也可以在 objc-runtime-new.h 源文件中找到(即 objc_class 内存中第一个位置是 isa,第二个位置是 superclass):
 struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache;             // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags......}
  • 对象在底层其实是一个结构体 objc_object ,而Class 在底层也是一个结构体 objc_class 。

iOS之深入解析对象isa的底层原理相关推荐

  1. iOS之深入解析weak关键字的底层原理

    一.weak 关键字 在 iOS 开发过程中,会经常使用到一个修饰词 weak,使用场景大家都比较清晰,避免出现对象之间的强强引用而造成对象不能被正常释放最终导致内存泄露的问题. weak 关键字的作 ...

  2. iOS之深入解析内存对齐的底层原理

    CPU 存取原理 一."存"示例 CPU 并不是以字节为单位存取数据的.CPU 把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此 CPU 在读取内存时是一块一块 ...

  3. iOS之深入解析通知NSNotification的底层原理

    一.概念 ① NSNotification NSNotification 用于描述通知的类,一个 NSNotification 对象就包含了一条通知的信息,NSNotification 对象是不可变的 ...

  4. iOS之深入解析数组遍历的底层原理和性能分析

    一.OC 数组的类体系 当我们创建一个 NSArray 对象时,实际上得到的是 NSArray 的子类 __NSArrayI 对象.同样的,创建 NSMutableArray 对象,得到的同样是其子类 ...

  5. iOS之深入解析分类Category的底层原理

    一.Category 简介 Objective-C 中的 Category 是对装饰模式的一种具体实现.它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法. 分类 Category 可以 ...

  6. iOS之深入解析缓存方法cache_t底层原理

    一.cache_t 原理 Class 内部中有个方法 缓存 cache_t ,用 散列表 来缓存调用过的方法,可以提高访问方法的速度. struct cache_t {#if CACHE_MASK_S ...

  7. iOS之深入解析类Class的底层原理

    内存偏移 定义一个数组并打印数组中的元素地址: int a[4] = {1,2,3,4};int *b = a;NSLog(@"%p - %p -

  8. 有序集合对象 ZSet 的底层原理

    这里写目录标题 ziplist 压缩列表结构 压缩列表结构 压缩列表节点结构 连锁更新 压缩列表在Redis中的用途 skiplist 传统跳表 改进后的跳表 zset中的跳表 redis中如何保证s ...

  9. iOS之深入解析高阶容器的原理和应用

    一.前言 我们都知道 iOS 提供了三种主要的容器类型,它们分别是 Array.Set 和 Dictionary,用来存储一组值: Array:存储一组有序的值: Set:存储一组无序的.不重复的值: ...

最新文章

  1. 如何运用NLP向个性类型客户介绍产品
  2. android中11种常见传感器的使用方法
  3. Ubuntu16.04/18.04 安装配置JDK 1.8 环境( Linux )
  4. 谱聚类、Chameleon聚类、PCCA、SOM、Affinity Propagation
  5. java 注册驱动失败_java – JDBC驱动程序注册死锁?
  6. 20140418--第1讲.开山篇
  7. 重磅!MobileNetV3 来了!
  8. Fragment:关于Avoid non-default constructors in fragments的错误
  9. java有哪些部分要学_java需要学习哪些知识
  10. Spring boot with Hive
  11. matlab 柏林噪声,游戏AI怎么写(一)——高级随机技术
  12. Eclipse安装JD-Eclipse反编译插件
  13. 什么是自动化测试?为什么要自动化测试?怎么做?
  14. STS安装lombok插件
  15. Axure RP8从入门到精通手册
  16. 小技巧(7):WPS批量修改图片尺寸
  17. 2021华为杯D题第一题完整代码
  18. BIDI算法 (Unicode Bidirectional Algorithm)
  19. Java破解9X9数独小游戏
  20. JS中使用bignumber处理高精度小数 失去去精确度运算 bigNumber用法

热门文章

  1. ogg replicat 进程 abend 处理
  2. Red hat下使用automake自动配置wxWidgets的makefile
  3. Docker,Docker-Compose,Docker Swarm,Kubernetes之间的区别
  4. oppo设备怎么样无需root激活XPOSED框架的教程
  5. 用JSON.parse(JSON.stringify(itemData))序列化反序列化实现‘深度复制’
  6. 通过调试对WriteFile()API的钩取
  7. coredata 数据库升级
  8. 编译OpenJDK及JDK题外话
  9. 代码优化Android ListView适配器三级优化详解
  10. Android Studio无法找到tool.jar解决方法!