(FILO)
。主要用于存储函数、方法、指针、临时变量,简单类型的变量。优势是效率高。alloc
生成的对象,或者block
拷贝的对象等。相比栈区效率较低。.bbs
区:静态区域,系统分配和释放,用来存储未初始化的全局变量、静态变量。.data
区:系统分配和释放,用来存储初始化的全局变量、静态变量。.text
区:系统分配和释放,程序加载到内存中存储的区域。- (void)test {
// 存放放在栈区
int a = 10
// &obj存放在栈区
// obj存放在堆区
NSObject *obj = [NSObject alloc];
}
复制代码
栈效率比堆高的缘由以下:c++
cpu
命中率更高,知足局部性原理。在函数内部声明,做用域只在当前{}
以内。 存储位置:自动保存在函数的每次执行的【栈帧】中,并随着函数结束后自动释放。程序员
全局变量是一种在函数外声明、能够跨文件访问的变量。它能够在声明时赋值,也能够在使用的时候赋值,若是在声明是没有赋值,系统会默认分配为空值。声明方式以下:bash
NSString *global;
NSString *global = @"AAA";
复制代码
全局变量保存在内存的全局存储区中,占用静态的存储单元;局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。数据结构
咱们能够在block
内部直接访问全局变量,而不须要借助其余修饰词。多线程
使用全局变量不能在.h
中定义,别的文件导入当前文件的时候会报错,duplicate symbols for architecture x86_64
。由于在编译阶段,头文件的信息都会copy
到文件中,这样的话会出现全部引入的头文件的都会有这么一个全局变量,因此会出现重名的状况。解决这种问题,咱们能够将其定义在.m
文件中,而后在须要使用的地方使用extern
关键字获取。架构
static
static
关键字修饰局部变量时,只会初始化一次,在内存中只有一块地址。- (void)staticLocalTest {
for (int i = 0; i < 5; i++) {
static int num = 0;
num += 1;
NSLog(@"==static num==%d==", num);
}
}
复制代码
能够看出,被static
修饰的变量,延长了生命周期,本该在for
循环中每一次循环就销毁,实际上只会初始化一次,在for
循环结束以后才销毁。async
static
关键字修饰全局变量时,若是是在.m
文件中,做用域仅限于当前文件,外部类是不能够访问到该全局变量的;若是是在.h
文件中,能够被任意引入当前文件做为头文件的文件所使用。static 类型 变量名;
复制代码
静态全局变量在每一个文件中都会单独拷贝一份地址,互不影响。好比我在文件A
中定义了一份静态全局变量,分别在A
、B
、C
三个文件中使用,他们的赋值是互不影响的。而全局变量是只有一份地址,这是它们的不一样点。ide
iOS
系统根据使用场景的不一样,提供了3种方案:函数
TaggedPointer
:对于一些小对象,如NSNumber
、NSDate
等采用此种方案NONPOINTER_ISA
:64位架构下iOS
应用程序TaggedPointer
TaggedPointer
是苹果从64bit
开始使用的一项内存管理技术,用于优化NSNumber
、NSDate
、NSString
等小对象的存储。在没有使用TaggedPointer
以前,这些小对象须要动态分配内存、维护引用计数等。使用了Tagged Pointer
,指针的值再也不是地址了,而是真正的值。实际上它再也不是一个对象了,而是一个普通变量而已。因此,它的内存并不存储在堆中,不须要malloc
申请、或者是释放。当指针不够存储数据时,才会使用动态分配内存的方式来存储数据。布局
如:没有使用TaggedPointer
的时候,NSNumber
指针存储的是堆中NSNumber
对象的地址值。使用TaggedPointer
以后,NSNumber
指针里面存储的数据变成了:Tag + Data
,也就是将数据直接存储在了指针中。
经过下面例子咱们来看一下系统对TaggedPointer
的特殊处理:
- (void)taggedpointerTest {
self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"abc"];
NSLog(@"%@",self.nameStr);
});
}
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"carshcarshcarshcarshcarshcarshcarshcarshcarshcarsh"];
NSLog(@"%@",self.nameStr);
});
}
}
复制代码
运行程序,第二个for
循环会崩溃,提示objc_release
异常。崩溃的缘由是能够看到是由于多线程访问setter/getter
,那么为何第一个for
循环没有崩溃呢?从代码看两个for
循环的区别就是第一个nameStr
赋值短,第二个长一些。打断点,在控制台查看信息,能够看到第一个循环中的nameStr
是NSTaggedPointerString *
,而第二个是NSCFString *
。查看retain/release
的源码,能够看到:
id objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
id objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
复制代码
刚才咱们说了,TaggedPointer
的存储是类型+数据
,那么咱们来具体看一下,它的存储策略:
NSNumber
、NSDate
、NSString
。如,0xb
表明NSNumber
。ASCII
码编码值来存储字符NONPOINTER_ISA
NONPOINTER_ISA
是MACOSX_VERSION_10_11
以后出来的对isa
内存的优化。在isa
中添加了更多的信息。
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
复制代码
nonpointer
:表示是否对isa
指针开启指针优化。0表示纯isa
指针,11表示不止是类对象地址,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
。散列表是一张哈希结构的表,其包含了自旋锁、引用计数表、弱引用表。其中的引用计数表,就是来对内存管理作处理的。