基本概念
一、OC的三大特性为:封装、继承、多态
1、封装:
- 成员变量的封装:setter、getter 方法,self调用方便高效
- 普通方法的封装:简洁、高效,不用关心内部过程
- SDK的封装:使用方便、私有隐藏,安全性高(不会泄露数据和代码)
2、继承
- 抽取了重复代码:子类可以拥有父类中的所有成员变量和方法
- 建立了类与类之间的联系
- 每个类中都有一个superclass指针指向父类
- 所有类的根类都是NSObject,便于isa指针 查询
- 调用某个对象的方法时,优先去当前类中找,如果找不到,再去父类中找
- 子类重新实现父类的某个方法,会覆盖父类以前的方法。
继承的缺点:耦合性太高(类与类之间的关系过于紧密),没有多继承,OC里面都是单继承,多继承可以用protocol委托代理来模拟实现;可以通过实现多个接口完成OC的多重继承。
3、多态
例如: -(void) animal:(id); id 就是多态,传入时,才识别 具体类的真实形象。runtime 也是运行时,才识别具体类。
- 要想使用多态必须使用继承(继承是多态的前提)
- 多态:父类指针指向子类对象 Animal *aa = [Dog new]; 调用方法时会检测对象的真实形象
- 好处:如果函数或方法参数中使用的是父类类型,可以传入父类,子类对象。
- 局限性:父类类型的变量不能直接调用子类特有的方法,必须强制转换为子类类型变量后,才能使用。
4.property的关键字分三类
1.原子性(也就线程安全)
有atomic和nonatomic, acomic就是线程安全,但是一般使用nonacomic,因为acomic的线程安全开销太大,影响性能,即使需要保证线程安全,我们也可以通过自已的代码控制,而不用acomic。
2.引用计数
- assign:assign用于非指针变量,基本修饰数据类型,统一由系统的栈区进行内存管理。
- weak:对对象的弱引用,不增加引用计数,也不持有对象,当对象消失后指针自动变为nil。
- copy:分为深拷贝和浅拷贝
- 浅拷贝:对内存地址的复制,让目标对象指针和原对象指向同一片内存空间会增加引用计数
- 深拷贝:对对象内容的复制,开辟新的内存空间
- strong:是每对这个属性引用一次,retainCount 就会+1,只能修饰 NSObject 对象,不能修饰基本数据类型。是 id 和 对象 的默认修饰符。
- unsafe_unretained:和week 非常相似, 也是可以同时修饰基本数据类型和 NSObject 对象 ,其实它本身是 week 的前身 , 在 iOS5 之后,基本都用 week 代替了 unsafe_unretained 。 但它们之间还是稍微有点区别的,并不是完全一样,对上层代码来说,能用 unsafe_unretained 的地方,都可以用 week 代替。同时要注意一点,这个修饰符修饰的变量不属于编译器的内存管理对象
5.类别Category
重写一个类的方式用继承还是分类,取决于具体情况:
- 假如目标类有许多的子类,我们需要拓展这个类又不希望影响到原有的代码,继承后比较好。
- 如果仅仅是拓展方法,分类更好.(不需要涉及到原先的代码)
优点:
- 分类中方法的优先级比原来类中的方法高,也就是说,在分类中重写了原来类中的方法,那么分类中的方法会覆盖原来类中的方法。+(void)load方法是一个特例,它会在当前类执行完之后,category中的再执行。)
- 可以用runtime进行method swizzling(方法的偷梁换柱)来处理异常调用的方法。
缺点:
通过观察头文件我们可以发现,Cocoa框架中的许多类都是通过category来实现功能的,可能不经意间你就覆盖了这些方法中的其一,有时候就会产生一些无法排查的异常原因。
其他问题
浅拷贝和深拷贝的区别?
- 浅拷贝:只复制指向对象的指针,指针指向同一个地址,而不复制引用对象本身。
- 深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A 时,B 不变。(即:B = [A copy]; )
内存管理
案例:办公室开关灯,有人开灯,无人关灯;进人加一,出人减一;第一个开灯的人持有此灯,第一个人能废弃此灯,其他人只是弱引用。
内存管理原则
- 1.自己生成的对象,自己持有
- 2.非自己生成的对象,自己也能持有
- 3.不再需要自己持有的对象时,释放掉
- 4.非自己持有的对象,无法释放(释放会崩溃)
对象操作与Objective-C方法的对应
| 对象操作 | Objective-C方法 |
|---|---|
| 生成并持有对象 | alloc、new、copy、mutableCopy |
| 持有对象 | retain (引用计数加1) |
| 释放对象 | release(引用计数减1) |
| 废弃对象 | dealloc(引用计数为0) |
相关释放函数:autorelease、NSAutoreleasePool、@autoreleasepool
使用autorelease方法,可以取得对象的存在,但是自己不持有对象(obj不立刻释放掉,注册到autoreleasePool中)
ARC 环境下的使用功能规则
- 不能使用retain、release、retainCount、autorelease
- 不能使用NSAllocateObject、NSDeallocateObject
- 必须遵循内存管理命名规则(驼峰命名法)
- 不要显式调用dealloc
- 使用 @autorelease 代替 NSAutoreleasePool
- 不能使用区域 (NSZone)
- 对象型变量不能作为C语言结构体(struct/union)的成员
- 显式转换 “id” 和 “void *”
会让对象引用计数增加的操作
-
- new、alloc、retain、copy、mutableCopy
-
- 用 addview 把一个控件添加到视图上,这个控件的引用计数+1;
-
- 把一个对象添加到数组中,数组内部会把这个对象的引用计数+1;
-
- 属性赋值,会让对象的引用计数+1;
-
- xib中得控件,跟代码关联后,会让对象的引用计数+1;
-
-
push这个操作会让对象的引用计数增加。
-
ARC、MRC混编
- ARC环境下 使用MRC代码,使用 -fno-objc-arc转换
- MRC环境下 使用ARC代码,使用 -fobjc-arc转换
所有权修饰符
- __strong 修饰符
- __weak 修饰符
- __unsafe_unretained 修饰符(MRC环境下使用)
- __autoreleasing 修饰符 (ARC环境下使用)
-
__strong修饰符是id类型和对象类型的默认修饰符,一般是隐式的; id obj = [[NSObject alloc] init]; //同上 id __strong obj = [[NSObject alloc] init];
-
__weak 解决 循环引用的 修饰符,防止内存泄漏。在持有某对象的弱引用时,若该对象被废弃,则弱引用自动失效,且被赋值nil,不在持有该对象。
-
__unsafe_unretained 和 _ _weak修饰的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会被立即释放。
-
__autoreleasing 隐式修饰符:非自己生成的对象,自己也能持有,会注册到自动释放池中。
释放对象时,废弃对象不被持有的流程
- 1、objc_release (释放)
- 2、因为引用计数为0,所以执行dealloc(释放内存)
- 3、_objc_rootDealloc(根解除)
- 4、object_dispose(废弃处理)
- 5、objc_destructInstance(销毁)
- 6、objc_clear_deallocating(清除)
对象被废弃时,最后调用objc_clear_deallocating的动作(相关表 SideTables)
- 1、从weak表中获取废弃对象的记录(地址为键值)。address= h(key)
- 2、将包含在记录中的所有附有_ _weak修饰符变量的地址,赋值nil。
- 3、从weak表中删除该记录。
- 4、从引用计数表中删除废弃对象的记录(地址为键值)。
其他
- 内存泄漏:应当废弃的对象在超出其生存周期后继续存在。
- 野指针:指针变量未初始化,其值是随机的;指针释放后未置空,指向不存在的内存。
- 悬垂指针: 指向曾经存在的对象,但是该对象已经不存在了。
- 标记指针: Tagged Pointer 是一种特殊的“指针”,其特殊在于,其实它存储的并不是地址,而是真实的数据和一些附加的信息。(后面做专门讲解)
- isa 指针: NONPOINTER_ISA 对象的isa指针,用来表明对象所属的类类型和一些附加信息。
标记指针: Tagged Pointer
看到苹果对于Tagged Pointer特点的介绍:
- Tagged Pointer专门用来存储小的对象,例如NSNumber, NSDate, NSString。
- Tagged Pointer指针的值不再是地址了,而是真正的值。实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
- 在内存读取上有着3倍的效率,创建时比以前快10倍
例如:NSString 其输出的class类型为 NSTaggedPointerString。在字符串长度在9个以内时,iOS其实使用了tagged pointer做了优化的。 直到字符串长度大于9,字符串才真正成为了__NSCFString类型。 注意:对于以前的@""符号创建的字符串还是常量字符串,这个没有改变,但是采用stringWithFormat 等NSString方法的创建字符串对象则有了区别。
NSString *strS = @"123456789";
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithFormat:@"1"];
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithUTF8String:"1234567"];
strS = [NSString stringWithUTF8String:"abcdabc"];//7
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithUTF8String:"abcdabcd"];//8
strS = [NSString stringWithUTF8String:"teeeeeeet"];//9
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithUTF8String:"eeeeeeeeee"];//10
strS = [NSString stringWithUTF8String:"eeeeeeeeeee"];//11
NSLog(@"%@ : %p",strS.class,strS);
strS = [NSString stringWithUTF8String:"eeeeeeeeeeee"];//12
NSLog(@"%@ : %p",strS.class,strS);
运行结果:
__NSCFConstantString : 0x10c843820
NSTaggedPointerString : 0xa000000000000311
NSTaggedPointerString : 0xa636261646362617
NSTaggedPointerString : 0xa040000000000049
NSTaggedPointerString : 0xa00000000000000b
__NSCFString : 0x6000000315c0
isa 指针(NONPOINTER_ISA)
- isa:是一个指向对象所属Class类型的指针。
- nonPointer_isa:对象的isa指针,用来表明对象所属的类类型和一些附加信息。
如果isa指针仅表示类型的话,对内存显然也是一个极大的浪费。于是,就像tagged pointer一样,对于isa指针,苹果同样进行了优化。isa指针表示的内容变得更为丰富,除了表明对象属于哪个类之外,还附加了 「引用计数」extra_rc,是否有被weak引用标志位weakly_referenced,是否有附加对象标志位has_assoc等信息。(rc:reference counter 引用计数)
//------- 联合体(定义)-------
union isa_t
{
isa_t() { } //构造函数1
isa_t(uintptr_t value) : bits(value) { } //构造函数2
Class cls; //成员1(占据64位内存空间)
uintptr_t bits; //成员2(占据64位内存空间)
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct { //成员3(占据64位内存空间:从低位到高位依次是nonpointer到extra_rc。成员后面的:表明了该成员占用几个bit。)
uintptr_t nonpointer : 1; //(低位)注意:标志位,表明isa_t *是否是一个真正的指针!!!
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; //引用计数 相关成员1
uintptr_t extra_rc : 19; //引用计数 相关成员2 (用19位来 记录对象的引用次数)
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
}
引用计数表的关系
其实在绝大多数情况下,仅用优化的isa_t 来记录对象的引用计数就足够了。只有在19位的extra_rc盛放不了那么大的引用计数时,才会借助SideTable出马。
SideTable:是一个 全局的引用计数表,它记录了所有对象的引用计数。
先说一下这四个数据结构的关系。 在runtime内存空间中,SideTables 是一个长度为8个元素 的hash数组,里面存储了SideTable。SideTables的hash键值就是一个对象obj的address。即:table1 = h(address1) 因此可以说,一个obj,对应了一个SideTable。但是一个SideTable,会对应多个obj。因为SideTable的数量只有64个,所以会有很多obj共用同一个SideTable。
- spinlock_t slock: 自旋锁,用于上锁/解锁 SideTable。
- RefcountMap refcnts :以DisguisedPtr<objc_object>为key的hash表,用来存储OC对象的引用计数(仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到)。
- weak_table_t weak_table : 存储对象弱引用指针的hash表。是OC weak功能实现的核心数据结构。
struct weak_table_t {
weak_entry_t *weak_entries; // 保存了所有指向指定对象的 weak 指针
size_t num_entries; // 存储空间
uintptr_t mask; // 参与判断引用计数辅助量
uintptr_t max_hash_displacement; // hash key 最大偏移值
};
其中,refcents是一个hash map,其key是obj的地址,而value,则是obj对象的引用计数。 即:referenceCount = h ( address )
而weak_table则存储了弱引用obj的指针的地址,其本质是一个以obj地址为key,弱引用obj的指针的地址作为value的hash表。hash表的节点类型是weak_entry_t。 即:objPointer = h ( address )