先上一内存布局图 c++
其余面试
地址开头数组
答:有。全局变量存在全局区(bbs区/data区),局部变量存在栈区安全
static NSString *K_Name = @"lala";
- (void)test {
[UIView animateWithDuration:1 animations:^{
K_Name = @"dingding";
}];
}
复制代码
答:能够,全局变量能够全局访问bash
##### Person
//注意personNum是定义在Person.h文件中的静态变量
static int personNum = 100;
@interface Person : NSObject
- (void)run;
+ (void)eat;
@end
@implementation Person
- (void)run{
personNum ++;
NSLog(@"Person内部:%@-%p--%d",self,&personNum,personNum);
}
+ (void)eat{
personNum ++;
NSLog(@"Person内部:%@-%p--%d",self,&personNum,personNum);
}
@end
#### 分类 Person (ca)
#import "Person.h"
@interface Person (ca)
- (void)cate_method;
@end
@implementation Person (ca)
- (void)cate_method{
NSLog(@"Person内部:%@-%p--%d",self,&personNum,personNum);
}
@end
复制代码
题目数据结构
#### ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"vc:%p--%d",&personNum,personNum); // 100
personNum = 10000;
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[Person new] run]; // 100 + 1 = 101
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[Person eat]; // 102
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[Person alloc] cate_method];
}
复制代码
打印:多线程
vc:0x102730484--100
vc:0x102730484--10000
Person内部:-0x102730460--101
vc:0x102730484--10000
Person内部:LGPerson-0x102730460--102
vc:0x102730484--10000
Person内部:-0x102730488--100
复制代码
答:首先static变量时能够边修改的;static变量的做用域与对象、类、分类不要紧,只与文件有关系;app
apple在内存管理方面提供了三种方案(TaggetPointer、NONPOINTER_ISA、散列表),严谨的说应该是三种方案共同管理内存。less
首先理解一个操做:对同一个数值^(异或)操做两次,获得的仍是原来的数值。eg:async
1000 0001
^ 0001 1000
------------
1001 1001
1001 1001
^ 0001 1000
------------
1000 0001
复制代码
下面是TaggedPointer 对象的源码,能够看到,也是对同一个数值(objc_debug_taggedpointer_obfuscator)进行^异或操做
// 存值
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) {
//objc_tag_index_t 是一个枚举值 用于标定不一样tag类型,根据不一样的类型的tag值进行不一样的左右移和MASK操做
if (tag <= OBJC_TAG_Last60BitPayload) {
//将类型tag和值Value进行打包(左右移和MASK操做),获得一个result
uintptr_t result =
(_OBJC_TAG_MASK |
((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) |
((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
// 用 result进行encode ^ 或操做,返回一个指针
return _objc_encodeTaggedPointer(result);
} else {//下面操做同样
uintptr_t result =
(_OBJC_TAG_EXT_MASK |
((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer(result);
}
}
//取值
static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr) {
//将指针ptr先decode ^ 操做取出value(就是上面的result)
uintptr_t value = _objc_decodeTaggedPointer(ptr);
//value解包(左右移和MASK操做)获得value
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
} else {
return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
}
}
extern uintptr_t objc_debug_taggedpointer_obfuscator;
//encode ^ 操做
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr) {
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
//decode ^ 操做
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr) {
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
//objc_tag_index_t 是一个枚举值 用于标定不一样tag类型
enum objc_tag_index_t : uint16_t {
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2, //NSString类的tag为2
OBJC_TAG_NSNumber = 3, //NSNumber类的tag为2
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
复制代码
源码结论
TaggedPointer对象
在存值时,先将对象类型tag
和值value
打包(左右移和MASK操做)处理获得一个result
,而后result
和一个随机的常量值objc_debug_taggedpointer_obfuscator
进行^异或
操做返回一个包装过的TaggedPointer指针
。TaggedPointer指针
和一个随机的常量值objc_debug_taggedpointer_obfuscator
进行^异或
操做返回result
,而后result
解包获得值value
。//MARK: - taggedPointer 面试题
@property (nonatomic, strong) NSString *nameStr;
- (void)taggedPointer_NOCrash {
dispatch_queue_t queue = dispatch_queue_create("com.fun.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(queue, ^{
self.nameStr = [NSString stringWithFormat:@"fun"];
NSLog(@"%@",self.nameStr);
});
}
}
- (void)taggedPointer_Crash {
dispatch_queue_t queue = dispatch_queue_create("com.fun1.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(queue, ^{
self.nameStr = [NSString stringWithFormat:@"fun 好好学习.每天向上。发展体育运动,加强国民体质"];
NSLog(@"%@",self.nameStr);
});
}
}
复制代码
答:首先 这两句代码self.nameStr = [NSString stringWithFormat:@"fun 好好学习.每天向上。发展体育运动,加强国民体质"]; NSLog(@"%@",self.nameStr);
表明着get/set
方法;对象的get/set
方法内部操做就是先新值 retian,后旧值 release
;让咱们看下retian/release
源码实现;
void
objc_storeStrong(id *location, id obj) {
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj) {
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj) {
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
复制代码
很明显正常对象的get/set
方法内部操做就是先新值 retian,后旧值 release
,在多线程访问的状况下,新值 retian,旧值 release
调用错乱,致使野指针或者空,因此崩溃; 可是若是是对象是taggedPointer对象时,对象不会进行retian/release
操做,因此不会崩溃;而self.nameStr = [NSString stringWithFormat:@"fun"];
nameStr是 NSTaggedPointerString
类型;self.nameStr = [NSString stringWithFormat:@"fun 好好学习.每天向上。发展体育运动,加强国民体质"];
nameStr是——NSCFString
类型。
在最新的objc2源码中可知万物皆objc_object
对象,在objc_object
对象内部有一个isa
属性;这个isa有多是纯指针,也有可能除包含指针外还包含其余信息,例如对象的引用计数、是否被弱引用...这时这个isa
就是NONPOINTER_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 {
// __arm64__ defined in isa.h
//这里把 ISA_BITFIELD 内部的宏直接写进来了 以arm64构架说明
/*表示是否对isa开启指针优化 。0表明是纯isa指针,1表明除了地址外,还包含了类的一些信息、对象的引用计数等。*/
uintptr_t nonpointer : 1;
/*关联对象标志位*/
uintptr_t has_assoc : 1;
/*该对象是否有C++或Objc的析构器,若是有析构函数,则须要作一些析构的逻辑处理,若是没有,则能够更快的释放对象*/
uintptr_t has_cxx_dtor : 1;
/*存在类指针的值,开启指针优化的状况下,arm64位中有33位来存储类的指针*/
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
/*判断当前对象是真的对象仍是一段没有初始化的空间*/
uintptr_t magic : 6;
/*是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象释放的更快*/
uintptr_t weakly_referenced : 1;
/*是否正在释放*/
uintptr_t deallocating : 1;
/*是否有辅助的引用计数散列表*/
uintptr_t has_sidetable_rc : 1;
/*表示该对象的引用计数值,满了就会存在sidetable 中*/
uintptr_t extra_rc : 19;
};
#endif
};
复制代码
源码中的extra_rc
就是用来存储引用计数的,具体原理放到下面的引用计数部分说明。
系统维护了一张全局的Hash表,里面存了一张张SideTable散列表,而这个散列表中就储存了对象的引用计数以及弱引用状况。
SideTable
内部结构
struct SideTable {
spinlock_t slock;//锁,用于控制数据访问安全
RefcountMap refcnts;//引用计数表s
weak_table_t weak_table;//弱引用计数表s
...
...
};
复制代码
结构解释
在isa_t
中extra_rc
位引用计数满了后会把一半的引用计数放到某个散列表SideTable
中的引用计数表中;具体操做以下:
由于一个对象可能拥有多个弱应用属性
咱们把系统维护的Hash表当成是一个男生宿舍楼
对象:学生
对象的内存地址:学生的姓名
hash表:一栋宿舍楼
SideTable:寝室
spinlock_t:寝室门上的锁,管理学生出入
RefcountMap:某一个寝室
引用计数:寝室某个学生拥有的书本数
weak_table_t:另外一个寝室
weak_table:学生s
弱引用状况就是学生拥有的女友数量的状况
复制代码
因此,不一样的对象(学生)可能在同一个SideTable(寝室)中 -不一样点:引用计数相似于一维数组,而弱引用状况是一个二维数组
在上面内存管理方案中已经介绍了TaggetPointer、NONPOINTER_ISA、散列表。下面说下引用计数具体如何工做的。引用计数的核心就是对象的retain、release方法
直接看源码,内有详细的解释,就再也不赘述
-(id) retain {
return _objc_rootRetain(self);
}
NEVER_INLINE id
_objc_rootRetain(id obj) {
ASSERT(obj);
return obj->rootRetain();
}
ALWAYS_INLINE id
objc_object::rootRetain() {
return rootRetain(false, false);
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow) {
//判断是不是TaggedPointer,是的话直接返回
if (isTaggedPointer()) return (id)this;
//用于记录锁状态
bool sideTableLocked = false;
bool transcribeToSideTable = false;
//初始化isa_t 用于后面赋值
isa_t oldisa;
isa_t newisa;
// 真正 retain 引用计数处理
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//是否用到了sidetable辅助处理引用计数
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
//尝试处理
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
//sidetable内部处理引用计数
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides //判断对象是否正在dealloc if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } //其实就是对isa的extra_rc变量进行+1,前面说到isa会存不少东西 uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ //判断是否须要引用计数迁移 if (slowpath(carry)) { // newisa.extra_rc++ overflowed if (!handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. //留一半的引用计数 //准备复制另外一半引用计数到sideTable if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); //是否将引用计数迁移到sidetable中 if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. //从newisa.extra_rc复制一半的引用计数到sidetable中 sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; } id objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA ASSERT(!isa.nonpointer); #endif //经过this内存地址拿到对应的SideTable SideTable& table = SideTables()[this]; //加锁 table.lock(); //又经过this内存地址拿到对象的refcntStorage(内存存有引用计数) size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { //引用计数 +1 refcntStorage += SIDE_TABLE_RC_ONE; } //解锁 table.unlock(); //返回对象 return (id)this; } 复制代码
源码简介 1.判断是否是TaggedPointer对象,若是是直接返回,不作引用计数处理 2.若是是NONPOINTER_ISA对象,那就对isa.extra_rc进行+1; 3.若是isa.extra_rc满了,就取一半复制到sideTable中辅助储存
release方法内部就是引用引用计数减一,就再也不解释,基本和retain差很少,本身看源码
AutoReleasePool 是ARC引入的,用于管理对象的引用计数。
Autorelease pool implementation
翻译:
AutoreleasePoolPage
内部结构
class AutoreleasePoolPage;
struct AutoreleasePoolPageData {
magic_t const magic; // 16
__unsafe_unretained id *next; //8
pthread_t const thread; // 8
//证实了双向链表结构
AutoreleasePoolPage * const parent; //8
AutoreleasePoolPage *child; //8
uint32_t const depth; // 4
uint32_t hiwat; // 4
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
复制代码
AutoreleasePoolPage
是个继承于AutoreleasePoolPageData
结构体的类AutoreleasePoolPage
的结构是否完整begin()
话外
AutoreleasePoolPage
最多能放504个对象指针+一个特殊指针(边界)AutoreleasePoolPage
能放505个对象指针结构图
示例代码
int main(int argc, char * argv[]) {
@autoreleasepool {
}
return 0;
}
复制代码
转成cpp文件查看
clang -rewrite-objc main.m -o mian.cpp
复制代码
mian.cpp。这里只展现部分重点代码。
struct __AtAutoreleasePool {
//构造函数
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
//析构函数
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool; //__AtAutoreleasePool实例化
}
return 0;
}
复制代码
能够看到原来的代码被自动加上了__AtAutoreleasePool
实例化代码,而且调用了构造和析构函数。
这是个构造函数,继续查看源码
//返回一个 AutoreleasePoolPage 对象
void *
objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
//这里讨论快速方法
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
######### autoreleaseFast()
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
//是否有hotPage 且没有满
if (page && !page->full()) {
//没满就添加
return page->add(obj);
} else if (page) {//有hotPage,但满了
满了就开分页;
return autoreleaseFullPage(obj, page);
} else {//没有hotPage,内部也是新的page
return autoreleaseNoPage(obj);
}
}
######### add()
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;//将next指针指向当前对象指针
protect();
return ret;
}
######### autoreleaseFullPage()
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
do {//内部递归寻找最后一页(判断是否有page->child),找到后开新的page
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
//将新page置为HotPage
setHotPage(page);
return page->add(obj);
}
复制代码
push简单总结
depth ++
深度增长这里的ctxt 实际上是AutoreleasePoolPage
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
############ pop()
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;//用于保存释放中止的标记
//判断 token(对象指针)是不是空的标识指针,是的话就表明没有对象被放入这个池子里
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
//找到对象指针所在的page
page = pageForPointer(token);
}
//将对象指针标记为中止指针
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
// 第一个节点 - 没有父节点,越界保护判断
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
//开始pop
return popPage<false>(token, page, stop);
}
######## popPage()
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
//内部进行对象release
page->releaseUntil(stop);
// 杀page,即删除空的child page,
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
} else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
######### releaseUntil() //释放对象操做
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage //循环遍历,直到stop对象 while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); //若是page空了,就拿parent page while (page->empty()) { page = page->parent; setHotPage(page); } page->unprotect(); //next 指针 -- id obj = *--page->next; memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); //不等于边界指针 if (obj != POOL_BOUNDARY) { //对象s释放 objc_release(obj); } } //将当前page设置为HotPage setHotPage(this); #if DEBUG // we expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { ASSERT(page->empty()); } #endif } 复制代码
pop简单总结
//再来看看在ARC环境下,这个被隐藏的autorelease()方法作了什么
- (id)autorelease {
//将self放到_objc_rootAutorelease()
return _objc_rootAutorelease(self);
}
_objc_rootAutorelease(id obj) {
ASSERT(obj);
//调用rootAutorelease()
return obj->rootAutorelease();
}
id objc_object::rootAutorelease() {
//TaggedPointer 不须要管理引用计数
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
id
objc_object::rootAutorelease2() {
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
//重点
static inline id autorelease(id obj) {
ASSERT(obj);
ASSERT(!obj->isTaggedPointer());
//仍是会调用autoreleaseFast()方法
id *dest __unused = autoreleaseFast(obj);
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
复制代码
autorelease简单总结
autorelease方法其实实现和push方法同样,不一样的是在push前会判断对象是否能够是TaggedPointer对象;
timer