OC类的探索 对类结构进行了整体大概流程的分析。今天我们对类结构进行些补充,以及过程中出现的疑问进行说明。
LLVM
Advancements in the Objective-C runtime
自己研究都是对代码产生了好奇,然后进行摸索,加上不断的总结规律,才能加以验证,但是WWDC 这个可以直接给开发者提供思路、代码讲解的地方,其重要性不言而喻,比较懒,直接上干货:
dirty memory要比clean memory昂贵的多,只要进行运行它就必须一直存在,通过分离出那些不会被改变的数据,可以把大部分的类数据存储为clean memory,这是苹果追求的
所有的类都会链接成一个树状结构这是通过firstSubclass和nextSiblingClass指针实现的,这样运行时会遍历当前使用的所有类。
问题:为什么方法,属性在class_ro_t中时,class_rw_t还要有方法,属性呢?
问题:class_rw_t结构在苹果手机中,占用很多的内存,那么如何去缩小这些结构呢?
因此我们可以拆除那些我们平时不常用的部分。图解如下:
结果:这样class_rw_t的大小会减少一半
对那些需要修改内存的,需要额外信息的类,我们可以分配这些扩展记录中的一个,并把它滑到类中供其使用。图解如下:
class_rw_t优化,其实就是对class_rw_t不常用的部分进行了剥离。如果需要用到这部分就从扩展记录中分配一个,滑到类中供其使用。现在大家对类应该有个更清楚的认识。
在Objective-C中写在类声明的大括号中的变量称之为成员变量,例如int a,NSObject *obj
成员变量用于类内部,无需与外界接触的变量
变量的数据类型不是基本数据类型且是一个类则称这个变量为实例变量,例如 NSObject *obj
成员变量包含实例变量
成员变量:在底层没有其他操作只是变量的声明
属性:系统会自动在底层添加了_属性名变量,同时生成setter和getter方法
编码链接
也可以通过一下方法获取
打开xcode--> command+shift+0--> 搜索ivar_getTypeEncoding--> 点击Type Encodings
Code | Meaning |
---|---|
c | A char |
i | An int |
s | A short |
l | A long |
l | is treated as a 32-bit quantity on 64-bit programs. |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object (whether statically typed or typed id) |
# | A class object (Class) |
: | A method selector (SEL) |
[array type] | An array |
{name=type…} | A structure |
(name=type…) | A union |
bnum | A bit field of num bits |
^type | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
注意:
runtime系统预备了一些编码,这些编码在协议中声明方法时会用到。编码列表如下:
Code | Meaning |
---|---|
r | const |
n | in |
N | inout |
o | out |
O | bycopy |
R | byref |
V | oneway |
可以通过以下代码进行校验:
NSLog(@"char --> %s",@encode(char));输出结果:char --> c
在探究属性和成员变量的区别时,发现属性setter方法有的是通过objc_setProperty实现的,有的是直接内存偏移获取变量地址,然后赋值
@interface MHPerson : NSObject
{NSString * newName;NSObject * objc;
}@property(nonatomic, copy)NSString * name;
@property(nonatomic,strong)NSString * hobby;
@property(nonatomic,assign)NSInteger age;
@end@implementation MHPerson@end
通过编译命令 编译main.m 生成cpp文件
clang -rewrite-objc main.m -o main.cpp
找到代码
static NSString * _I_MHPerson_name(MHPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_MHPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);static void _I_MHPerson_setName_(MHPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MHPerson, _name), (id)name, 0, 1); }static NSString * _I_MHPerson_hobby(MHPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_MHPerson$_hobby)); }
static void _I_MHPerson_setHobby_(MHPerson * self, SEL _cmd, NSString *hobby) { (*(NSString **)((char *)self + OBJC_IVAR_$_MHPerson$_hobby)) = hobby; }static NSInteger _I_MHPerson_age(MHPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_MHPerson$_age)); }
static void _I_MHPerson_setAge_(MHPerson * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_MHPerson$_age)) = age; }
然后添加了几个属性,修改了几次顺序 , 引用objc_setProperty函数:
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
然后copy属性会通过objc_setProperty实现的:
static void _I_MHPerson_setName_(MHPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MHPerson, _name), (id)name, 0, 1); }
其他属性是通过内存偏移实现的。
那么setter和getter方法在编译期函数地址就已经确定,怎么确定是编译期呢?通过烂苹果查看可执行文件的函数表:
没有体现,那么只好在oc源码里找一下,发现了这么多东西:
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);bool mutableCopy = (shouldCopy == MUTABLE_COPY);reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}
哇,好像调用的都是一个方法reallySetProperty,里面有offset、atomic、copy、mutablecopy,代码如下:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{if (offset == 0) {object_setClass(self, newValue);return;}id oldValue;id *slot = (id*) ((char*)self + offset);if (copy) {newValue = [newValue copyWithZone:nil];} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];} else {if (*slot == newValue) return;newValue = objc_retain(newValue);}if (!atomic) {oldValue = *slot;*slot = newValue;} else {spinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();oldValue = *slot;*slot = newValue; slotlock.unlock();}objc_release(oldValue);
}
其原理就是新值retain,旧值release
这里好像找不出什么,那么去llvm里面看一下:
找到了这个,有点懵,搜getSetPropertyFn,然后:
llvm::FunctionCallee CGObjCMac::GetPropertySetFunction() {SetPropertyFn();
}
然后:
根据switch条件PropertyImplStrategy类型调用GetPropertySetFunction(),PropertyImplStrategy类型有两种GetSetProperty或者SetPropertyAndExpressionGet,下一步只要知道什么时候给策略赋值:
/// Pick an implementation strategy for the given property synthesis.
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,const ObjCPropertyImplDecl *propImpl) {const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();IsCopy = (setterKind == ObjCPropertyDecl::Copy);IsAtomic = prop->isAtomic();HasStrong = false; // doesn't matter here.// Evaluate the ivar's size and alignment.ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();QualType ivarType = ivar->getType();auto TInfo = Context().getTypeInfoInChars(ivarType);IvarSize = TInfo.Width;IvarAlignment = TInfo.Align;// If we have a copy property, we always have to use getProperty/setProperty.// TODO: we could actually use setProperty and an expression for non-atomics.if (IsCopy) {Kind = GetSetProperty;return;}// Handle retain.if (setterKind == ObjCPropertyDecl::Retain) {// In GC-only, there's nothing special that needs to be done.if (LangOpts().getGC() == LangOptions::GCOnly) {// fallthrough// In ARC, if the property is non-atomic, use expression emission,// which translates to objc_storeStrong. This isn't required, but// it's slightly nicer.} else if (LangOpts().ObjCAutoRefCount && !IsAtomic) {// Using standard expression emission for the setter is only// acceptable if the ivar is __strong, which won't be true if// the property is annotated with __attribute__((NSObject)).// TODO: falling all the way back to objc_setProperty here is// just laziness, though; we could still use objc_storeStrong// if we hacked it right.if (ObjCLifetime() == Qualifiers::OCL_Strong)Kind = Expression;elseKind = SetPropertyAndExpressionGet;return;// Otherwise, we need to at least use setProperty. However, if// the property isn't atomic, we can use normal expression// emission for the getter.} else if (!IsAtomic) {Kind = SetPropertyAndExpressionGet;return;// Otherwise, we have to use both setProperty and getProperty.} else {Kind = GetSetProperty;return;}}// If we're not atomic, just use expression accesses.if (!IsAtomic) {Kind = Expression;return;}// Properties on bitfield ivars need to be emitted using expression// accesses even if they're nominally atomic.if (ivar->isBitField()) {Kind = Expression;return;}// GC-qualified or ARC-qualified ivars need to be emitted as// expressions. This actually works out to being atomic anyway,// except for ARC __strong, but that should trigger the above code.if (ivarType.hasNonTrivialObjCLifetime() ||(LangOpts().getGC() &&Context().getObjCGCAttrKind(ivarType))) {Kind = Expression;return;}// Compute whether the ivar has strong members.if (LangOpts().getGC())if (const RecordType *recordType = ivarType->getAs<RecordType>())HasStrong = recordType->getDecl()->hasObjectMember();// We can never access structs with object members with a native// access, because we need to use write barriers. This is what// objc_copyStruct is for.if (HasStrong) {Kind = CopyStruct;return;}// Otherwise, this is target-dependent and based on the size and// alignment of the ivar.// If the size of the ivar is not a power of two, give up. We don't// want to get into the business of doing compare-and-swaps.if (!IvarSize.isPowerOfTwo()) {Kind = CopyStruct;return;}llvm::Triple::ArchType arch Target().getTriple().getArch();// Most architectures require memory to fit within a single cache// line, so the alignment has to be at least the size of the access.// Otherwise we have to grab a lock.if (IvarAlignment < IvarSize && !hasUnalignedAtomics(arch)) {Kind = CopyStruct;return;}// If the ivar's size exceeds the architecture's maximum atomic// access size, we have to use CopyStruct.if (IvarSize > getMaxAtomicAccessSize(CGM, arch)) {Kind = CopyStruct;return;}// Otherwise, we can use native loads and stores.Kind = Native;
}
印象中setter方法和getter方法是同时存在的,那么既然objc_setProperty存在,objc_getProperty是不是也存在呢?经过一系列操作发现:
static NSString * _I_MHPerson_name(MHPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct MHPerson, _name), 1); }
源码改成了这样
@property(atomic, copy)NSString * name;
日常搜代码,发现就在set方法的正上方
所以按照刚刚找objc_setProperty的思路,发现也是在上方1109-1284,此处省略 太长了,等等,突然想起来点什么:
// If we have a copy property, we always have to use getProperty/setProperty.// TODO: we could actually use setProperty and an expression for non-atomics.if (IsCopy) {Kind = GetSetProperty;return;}
这里对set/get都适用
仰之弥高,钻之弥坚,谨以此共勉。
比较笨,如果有什么疏漏或好的学习思路,欢迎指点交流。
本文发布于:2024-02-01 18:57:21,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170678504338754.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |