objc_object与objc_class
首先,我们看下几个类型:NSObject
、Class
、objc_object
、objc_class
、id
的联系与区别。
- NSObject:OC中的基类,绝大多数类都继承
NSObject
(NSProxy
也是基类哦~) - Class:
NSObject
的源码编辑器手机版下载类型,在objc
源码的NSObject.mm
文件中可以看到
+ (Class)class {
return self;
}
- objc变量名的命名规则_objapple id密码重置ect:
NSObject源码网站
类在C++的底层实现的结构体名称,在生成的cpp文件中可以看到,它和NSObject
是同一个东西,只是在不同成员变量语缓存视频怎样转入本地视频言环境的名字不同。ob缓存视频合并app下载jc_object
结构体里面,只有一个成员变量isa
指针。
typedef struct objc_object NSObject;
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
- objc_class:
Class
在C++底层实现的结构体名称,在生成apple store的cpp文件中可以看到,它和Class
是同一个东西,只是在不同语言环境的名字不同。
typedef struct objc_class *Class;
- id:OC环境中可以指向任何类型,并且不用带*号
typedef struct objc_object *id;
isa与superClass探究
我们先看下类在内存中地址,是不是每创建一对象,都会创建一个新的与之对应的类。
void getSJPersonClass(void) {
Class cls1 = SJPerson.class;
Class cls2 = [SJPerson alloc].class;
Class cls3 = object_getClass([SJPerson alloc]);
Class cls4 = [SJPerson alloc].class;
NSLog(@"cls1 : %p \n cls1 : %p \n cls1 : %p \n cls1 : %p \n", cls1, cls2, cls3, cls4);
}
执行getSJPersonClass
,打印结果如下:
也就是同样的类,在内存中只会有1份。
我们打印实例变量p
的内存,我们知道前8位是isa
指针
isa
地址与ISA_MASK
做与运算,可以拿到类对象的地址。
我们继续拿类的isa
地址与ISA_MASK
做与运算,看可以Apple得到什么。
我们看到,最后打印的结apple watch果也是SJPerson
,但是两个SJPerson
的地址不一样,一个0x00000001000085f8,一个0x00000001000085d0,也就是说这两个不是一个类型。在这缓存视频在手机哪里找就不卖关子了,第二个SJPerson
就apple tv是我们常说的元类。
为什么是元类呢,我们可以在machOView中找到相关内容:
看到有一个metaclass
也就是元类。
那我们继续看下元类的isa
指向哪里。
我们看到SJPerosn
元类的isa
打印出来是ONObject
,但是地址和NSObject
类对象地址源码编程器不一样,我们在打印NSObject
的元类地址,发现地址一致了,也就是说SJPerson
的元类的isa
指向NSObject
元类。SJPerson
继源码交易平台承自缓存文件夹名称NSObject
,那是不是说明直变量值接指向父类的元类呢,我们再创建一个SJStudent
继承SJPerson
,再试一下。
我们看到$3
与$5
相同,也就是说,无论对象继承谁,自定义对象的元类的isa
都指向根元类。
继续往下,我们看下NSObject
元类的成员变量有默认值吗isa
指向哪里。
1与1与2相同,也就是说根元类的isa
指向它自己。
isa
指向链我源码时代们看完了,再看下继承关系,元类也有继承吗,我们用以下代码测试以下缓存清理:
NSObject *obj = [NSObject alloc];
Class objCls = object_getClass(obj);
Class objMetaCls = object_getClass(objCls);
Class objRootMetaCls = object_getClass(objMetaCls);
NSLog(@"NSObject实例对象: %p \n NSObject类对象: %p \n NSObject元类对象: %p \n NSObject根元类对象: %p \n ", obj, objCls, objMetaCls, objRootMetaCls);
Class pMetaCls = object_getClass([SJPerson class]);
Class pMetaSuperCls = class_getSuperclass(pMetaCls);
NSLog(@"SJPerson元类对象: %p \n SJPerson元类对象的父类: %p \n ", pMetaCls, pMetaSuperCls);
Class sMetaCls = object_getClass([SJStudent class]);
Class sMetaSuperCls = class_getSuperclass(sMetaCls);
NSLog(@"SJStudent元类对象: %p \n SJStudent元类对象的父类: %p \n ", sMetaCls, sMetaSuperCls);
Class rootMetaSuperCls = class_getSuperclass(objRootMetaCls);
NSLog(@"根元类对象的父类: %p", rootMetaSuperCls);
打印结果:
NSObject实例对象: 0x101a30050
NSObject类对象: 0x10036a140
NSObject元类对象: 0x10036a0f0
NSObject根元类对象: 0x10036a0f0
SJPerson元类对象: 0x100008530
SJPerson元类对象的父类: 0x10036a0f0
SJStudent元类对象: 0x100008580
SJStudent元类对象的父类: 0x100008530
根元类对象的父类: 0x10036a140
根据结果,我们看到SJStudent
元类的父类apple tv就是SJPerson
元类,SJPerson缓存清理
元类的父类就是NSObject
的元类,NSObject
元类变量类型有哪些的父类就是NSO成员变量和实例变量的区别bject
类对象。
isa
的流程图与继承流程图如下:
类的内存数据
OC的类在C++中是apple id密码重置ob源码编辑器jc_class
类型,我们在objc
源码中找objc_class
的结构。
我们找到3个结构成员变量和局部变量区别
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;
因为源码交易平台我们当前环境是OBJC2
,所以这个标明OBJC2_UNAVAILABLE
,我们就可以忽略不计成员变量。
还有两个分别在objc-run源码编程器time-源码编程器new.h
和objc-runtime-old.h
文件中,我们想都不想直接看new
。里面的结构省略方法后:
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
}
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
也就是说,类结构里面有isa
指针,sup变量名的命名规则erclass
,cache
,bits
。isa
我们已经分析过了,superclass
就是父类,cache
我们以后再说,我们接下来研究下bits
。
在此之前我们验证下superclass
:
SJPerson
的父类是NSObject
,没有问源码之家题。
要研究bits
,首先我们需变量英文要知道bits
在内存存储的位置,isa
占8字节,superclass
占8字节没apple pay什么好说的,那么c缓存视频变成本地视频ache
占多少字节呢,我源码时代们点进去看下cache_t
的结构:
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
}
再看下expliciapple idt_atomic
实现
template <typename T>
struct explicit_atomic : public std::atomic<T> {
explicit explicit_atomic(T initial) noexcept : std::atomic<T>(std::move(initial)) {}
operator T() const = delete;
T load(std::memory_order order) const noexcept {
return std::atomic<T>::load(order);
}
void store(T desired, std::memory_order order) noexcept {
std::atomic<T>::store(desired, order);
}
// Convert a normal pointer to an atomic pointer. This is a
// somewhat dodgy thing to do, but if the atomic type is lock
// free and the same size as the non-atomic type, we know the
// representations are the same, and the compiler generates good
// code.
static explicit_atomic<T> *from_pointer(T *ptr) {
static_assert(sizeof(explicit_atomic<T> *) == sizeof(T *),
"Size of atomic must match size of original");
explicit_atomic<T> *atomic = (explicit_atomic<T> *)ptr;
ASSERT(atomic->is_lock_free());
return atomic;
}
};
T
泛型,这个变量的大小取决于泛型T
的大小。uintptr_t
指针apple store占8字节,第二个变量共用体,mask_t
:typedef uint32_t mask_t;
,占4字节,uint16_t
占2字节,结构体占8字节,_apple官网originalPreoptCache
是个*变量的指针其含义是指该变量的也源码编程器就是指针类型源码编辑器手机版下载也是8字节,所以,cache_t
一共占16字节源码编辑器。b变量类型有哪些its
从32字节开始。
再看下怎么获取bitapple官网s
里面数据,在源码中可以找到data
方法。
class_rw_t *data() const {
return bits.data();
}
里面唯一能根据单词猜意思的只有firstSubc源码编辑器la缓存ss
,第一个子类,但是SJPerson
有一个子类SJStudent
啊,为什么这里面数据是nil
呢?因为OC中的类是懒加载的,如果没有用到,是不会加载这个类,那我们代码改一下:缓存视频合并app下载
SJStudent *s = [SJStudent alloc];
SJPerson *p = [SJPerson alloc];
这时候我们就看成员变量和局部变量区别到firstSubclass
里面有值了,没毛病。但是我又想,如果SJPerson
还有别的子类,这个值又会是什么呢?源码编辑器
新建一个SJHandsomeMan
继承自SJPerson
。
两次结果不一样,也就是说firstSubclass
的值是该类子类中最后初始化的那个类。
类的属性
目前能看懂看完了,比如我们想看类里面的属性怎么办呢?答:从class_rw_t
结构里面找成员变apple tv量或者方法。
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
成员变量没找到变量类型有哪些,方法倒是看上去有一个,那我们接着调试。
class property_array_t :
public list_array_tt<property_t, property_list_t, RawPtr>
{
typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
};
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
struct array_t {
uint32_t count;
Ptr<List> lists[0];
static size_t byteSize(uint32_t count) {
return sizeof(array_t) + count*sizeof(lists[0]);
}
size_t byteSize() {
return byteSize(count);
}
};
}
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
uint32_t flags() const {
return entsizeAndFlags & FlagMask;
}
Element& getOrEnd(uint32_t i) const {
ASSERT(i <= count);
return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
}
Element& get(uint32_t i) const {
ASSERT(i < count);
return getOrEnd(i);
}
size_t byteSize() const {
return byteSize(entsize(), count);
}
static size_t byteSize(uint32_t entsize, uint32_t count) {
return sizeof(entsize_list_tt) + count*entsize;
}
struct iterator;
}
分析下property_array_t
结构:是个数组,数组里面每个元素都是property_list_t
类型,property_源码是什么意思lis源码编辑器t_t
也是个数缓存视频在手机哪里找组,因为entsize_list_tt
里面有迭代器iterator
,property_list成员变量有默认值吗_t
数组里面的元素是pro缓存视频合并app下载perty_t
。大致结构:property_array_t =变量类型有哪些 [property_list_t[property_t, property_t], proapp id注册perty_list_t[property_t]]
。
再看下property_t
的结构:
struct property_t {
const char *name;
const char *attributes;
};
类的方法
属性我们看完了,再看下方法,在SJPerson
里面添加两个方法,一个实例方法,一个类方法。
@interface SJPerson : NSObject
@property (nonatomic, copy) NSString *name;
- (void)sayNB;
+ (void)say666;
@end
找方法同样我们看到class_rw_t
里面有个methods
方法,不出意外应该能拿到方法。
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
method_array_t
的结构和property变量与函数_array_t
一样,都是二维数组,只不过最里面的对象换成了method_t
。
我们看到同样的操作,怎么最后打印不出来数据,是不是我们操作不对?这就要说下property_t
里面有两个成员,打印的时候直接把两缓存视频怎样转入本地视频个成员打印出来,我们再看下method_t
的结构里面没有这种成员,但是里面有一个结构体:
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
这里面成员能看到方法所有内容,是不是我们打印这个big
就行呢?测试一下:
非常完美方法都打印出来了,但是自己观察,一共就3个方法,say666
没有打印出来,为什么呢?对变量象方法存在类结构中,那猜测类方法会不会放在元类里面呢?我们来apple store验证下:
我们看到say666
的确在元类里面,缓存的视频怎么保存到本地也就是类方法存储在元类。
这也就是为什么iOS要设计元类的原因,因为源码网站OC中有实例方法和类方法,也就是所谓的减号方法和源码是什么意思加号方法,但是在OC底层,是没有这个含义的,也就是所有方法都源码编程器转化成同名函数,如果没有元类,我们写两个方法+(void)say666
和-(void)say666
,这两个方法都存储在类里面,那我们执行方法的时候,根据函数名,就无法确定到底是执行实例方法还是类方法了,所以设计元类把两种方法分离开。
类的成员变量
class_ro_t演变
在WWDC视频中,我们可以详细看到class_ro_t
演变优化过程,也知道成员变量存在class_ro_t
中。在SJPerson
中添加一个成员变量hobby
。
@interface SJPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayNB;
+ (void)say666;
@end
class_ro_t
怎么找呢,同样在源码中找方法
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
调试验证:
TypeEncoding
上面源码编辑器手机版下载打印方法里面types
一堆符号就是iOS中TypeEn成员变量和实例变量的区别coapple官网ding
编码,每个符号的含义可以参照# Type Encodings
。
在解释下方法中types
含成员变量有默认值吗义:
第一个字母:返回值类型。第一个数字:这个方法所有参数占内存大小。
第二个字母:第一个参数的类型。第二个数字:第一个参数从第几号内存开始。
第三个字母:第二个参数的类型。第三个数字:第二个参数从第几号内存开始。
…….以此类推。
总结:类本质就是结构体,里面有isa
,superclass
,cache
,bits
。isa
利用位域存着类相关信息,superclass
存着父类,cache
存着方源码资本法缓存,bits
里面存着属性列表、实例变量列表、方法列表、协议列表等。
__has_feature(ptrauth_c源码编辑器下载alls)介绍
-
__has_feature
:此函数的功能是判断编译器是否支持某个功能 -
ptrauth_calls
:指针身份验证,针对arm64e
架构;使用Apple A12
或更高版本A
系列处理器的设备(如iPhone XS
、iPhone XS Max
和iPhone XR
或更新的设备)支持a源码编辑器rm64e
架构 -
参考链接:Preparing Your App to Work with Pointapple ider Auth缓存视频变成本地视频entication
-
下面分别使用真机
iPhone 12
和iPhone 8
进行验证
验证方法一:通过isa存储数据验证
- 因为
arm64
架构中if分支和else分支存储的数据结构是不一样的,这里根据weakly_referenced
值apple watch做验证 - 测试代码
LGPerson *p = [LGPerson alloc];
__weak typeof(p) weakP = p;
NSLog(@"p:%@", p);
复制代码
-
在
__weak typeof(p) weakP = p;
处添加断点,这行代码执行前weakly_refer缓存是什么意思enced
的值为0,执行后会变为1 -
测试
iPhone 8
- 执行前
- 执行后:
-
测试
iPhone 12
- 执行前
- 执行后
-
验证结果: 从上面两个设备的验证结果可以看出,执行
__weak typeof(p) weakP = p;
前后,iPhone 8
修改的是第43位(从右往左),而iPhone 12
修改的是第3位(从右往左),分别对应变量的指针其含义是指该变量的的是a源码是什么意思rm64
架构中的else
分支和if
分支的存储结构
验证方法二:通过断点和汇编验证
- 在
[LGPerson alloc]
处设置断点 - 执行到断点处后,添加符apple watch号断点
_objc_rootAllocWithZone
,并继续运行 iPhone 8
iPhone 12
-
验证结果:
iPhone 8
中使用了0xfff缓存是什么意思ffff变量与函数f8
,iPhonapple storee 12
中使用了0x7fffff源码编辑器下载fffffff8
,在objc
源码的isa.h
中搜索发现,这两个值分别对应的是arm64
架构中else
分支的ISA_MASK
值和if
分支的ISA_MASK
值
总结
-
__has_fe变量名ature(ptrauth_calls变量4)成员变量和成员方法
的作用是判断编译器是否支源码编辑器手机版下载持指针身份验证功能 -
iPhone X
系列以上的设备(arm64e
架构,使用Apple A12
或更高版本A
系列处理器的设备)支持指针身份验证 -
对于
arm64
架构- i缓存视频在手机哪里找Phone X系列以上(包含)设备使用如下结构:
# define ISA_MASK 0x007ffffffffffff8ULL # define ISA_MAGIC_MASK 0x0000000000000001ULL # define ISA_MAGIC_VALUE 0x0000000000000001ULL # define ISA_HAS_CXX_DTOR_BIT 0 # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t weakly_referenced : 1; \ uintptr_t shiftcls_and_sig : 52; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8 # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7) 复制代码
- iPhone X以下(不apple pay包含)设备使变量名用如下结构:
# define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL # define ISA_HAS_CXX_DTOR_BIT 1 # 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 unused : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19 # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18)