大水文一篇
大水文一篇
大水文一篇
起因
最近对Block
的一些实现细节又进行了一次复习,主要涉及的是捕捉变量的部分。有一个点我之前一直没太关注:对ivar
变量的直接访问为啥会产生循环引用。
在我原先的理解中,之所以会产生循环引用,绝大多数场景都是由于block
里面涉及了self关键字,比如[self doSomething]
(同理,对于property
的访问本质也是一堆方法),但是为啥对ivar
的访问也会导致循环引用呢?
不是直接采用 *(void *)address = xxx
这样的直接对编译好的静态地址赋值就好了?
当时傻逼了,写完本文后想想就算编译成地址了,基地址从哪算还是要依赖
self
变量。
谈谈ivar的访问是啥形式
还是回到runtime来看看吧,万变不离其宗,从objc_class
结构体看起:
struct objc_class : objc_object {
// Class ISA; // 8byte
Class superclass; // 8byte
cache_t cache; // formerly cache pointer and vtable // 4 + 4 + 8
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
主要的运行时数据都是class_rw_t
表示,继续瞅瞅:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
其中class_ro_t
基本上是从二进制产物中读取的“副本”数据,我们看看:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
看起来ivar_list_t
就是存放ivar
的列表,他的实现是一个模版类,看看具体结构表示:
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
具体对应ivar
,替换掉模版就是:
struct ivar_list_t {
uint32_t entsizeAndFlags;
uint32_t count;
ivar_t first;
其中,ivar_t
表征的就是我们每个ivar
,
int32_t *offset;
const char *name;
const char *type;
嗯,从这里开始offset
是用一个int32_t *
的指针来表示,就开始有意思了。这里我们先暂时忽略
看起来,如果按照这种方式访问ivar
,整个流程要经过好多次指针转移:
class -> class.rw_data -> class.rw_data.ro_data -> class.rw_data.ro_data.ivars ->
-> class.rw_data.ro_data.ivars.first[n]
如果是这样,大量使用ivar
肯定很耗时。那么,对于ivar
的访问究竟是怎么玩的呢?
全局变量
我们用如下这个非常简单的例子来瞅瞅:
typedef void(^MyBlock)(void);
@interface MyObject : NSObject
@property (nonatomic) NSUInteger haha;
@property (nonatomic, copy) MyBlock block;
- (void)inits;
@end
@implementation MyObject
- (void)inits
{
self.block = ^{
_haha = 5;
};
}
@end
int main(int argc, char * argv[]) {
MyObject *object = [MyObject new];
[object inits];
}
重写一把,基本转化成如下的形式:
typedef void(*MyBlock)(void);
#ifndef _REWRITER_typedef_MyObject
#define _REWRITER_typedef_MyObject
typedef struct objc_object MyObject;
typedef struct {} _objc_exc_MyObject;
#endif
// 注意点1!!!!!!!!!!!!!!!!!!!!
extern "C" unsigned long OBJC_IVAR_$_MyObject$_haha;
extern "C" unsigned long OBJC_IVAR_$_MyObject$_block;
struct MyObject_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSUInteger _haha;
MyBlock _block;
};
// @property (nonatomic) NSUInteger haha;
// @property (nonatomic, copy) MyBlock block;
// - (void)inits;
/* @end */
// @implementation MyObject
struct __MyObject__inits_block_impl_0 {
struct __block_impl impl;
struct __MyObject__inits_block_desc_0* Desc;
MyObject *self;
// 注意点2!!!!!!!!!!!!!!!
__MyObject__inits_block_impl_0(void *fp, struct __MyObject__inits_block_desc_0 *desc, MyObject *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 注意点3!!!!!!!!!!!!
static void __MyObject__inits_block_func_0(struct __MyObject__inits_block_impl_0 *__cself) {
MyObject *self = __cself->self; // bound by copy
(*(NSUInteger *)((char *)self + OBJC_IVAR_$_MyObject$_haha)) = 5;
}
static void __MyObject__inits_block_copy_0(struct __MyObject__inits_block_impl_0*dst, struct __MyObject__inits_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __MyObject__inits_block_dispose_0(struct __MyObject__inits_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __MyObject__inits_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __MyObject__inits_block_impl_0*, struct __MyObject__inits_block_impl_0*);
void (*dispose)(struct __MyObject__inits_block_impl_0*);
} __MyObject__inits_block_desc_0_DATA = { 0, sizeof(struct __MyObject__inits_block_impl_0), __MyObject__inits_block_copy_0, __MyObject__inits_block_dispose_0};
static void _I_MyObject_inits(MyObject * self, SEL _cmd) {
((void (*)(id, SEL, MyBlock))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__MyObject__inits_block_impl_0((void *)__MyObject__inits_block_func_0, &__MyObject__inits_block_desc_0_DATA, self, 570425344)));
}
static NSUInteger _I_MyObject_haha(MyObject * self, SEL _cmd) { return (*(NSUInteger *)((char *)self + OBJC_IVAR_$_MyObject$_haha)); }
static void _I_MyObject_setHaha_(MyObject * self, SEL _cmd, NSUInteger haha) { (*(NSUInteger *)((char *)self + OBJC_IVAR_$_MyObject$_haha)) = haha; }
static void(* _I_MyObject_block(MyObject * self, SEL _cmd) )(){ return (*(MyBlock *)((char *)self + OBJC_IVAR_$_MyObject$_block)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_MyObject_setBlock_(MyObject * self, SEL _cmd, MyBlock block) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MyObject, _block), (id)block, 0, 1); }
// @end
int main(int argc, char * argv[]) {
MyObject *object = ((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("new"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("inits"));
}
一大堆东西,没啥特别的地方,我们只要关注几个地方:
对于每个
ivar
,都有对应的全局变量extern "C" unsigned long OBJC_IVAR_$_MyObject$_haha; extern "C" unsigned long OBJC_IVAR_$_MyObject$_block;
block_invoke对应的实现是通过对象自身作为基地址,全局变量作为偏移去对
haha
这个ivar
进行赋值。static void __MyObject__inits_block_func_0(struct __MyObject__inits_block_impl_0 *__cself) { MyObject *self = __cself->self; // bound by copy (*(NSUInteger *)((char *)self + OBJC_IVAR_$_MyObject$_haha)) = 5; }
block的构造函数,确实捕捉了
self
__MyObject__inits_block_impl_0(void *fp, struct __MyObject__inits_block_desc_0 *desc, MyObject *_self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }
由于全局变量的地址是在编译期就确定了,所以这里也就不难解释ivar_t
里面为什么要保存int32_t *
,保存的就是对应的全局变量地址。而全局变量的值则是对应的动态偏移。
结语
水完了,其实虽然runtime的结构体设计的比较绕,但是最后对于变量的访问和很多静态语言设计一样,也不会损失很多性能。
从另外一个角度看,如果声明了巨多的ivar
,看来也会对包大小产生不可忽视的影响。