iOS内存管理

属性关键字

assign 修饰基本类型,修饰对象会造成野指针

strong ARC下持有引用

retain MRC下的持有引用

保留新值,释放旧值,赋值新值,指向同一块内存区域

copy

复制新值,释放旧值,赋值新值
修饰对象时,复制的新对象是不可变对象,故修饰不可变对象
赋值对象不可变,则浅拷贝,两者指向同一块内存区域
赋值对象可变,则是深拷贝,两者指向不同内存,互不影响

weak 非持有引用,指向对象释放,自动置空

readonly 只读 只有getter方法,但可直接访问成员变量

readwrite 可读可写

atomic 原子操作 setter/getter方法加锁,效率低###nonatomic 非原子操作,无锁,效率高

内存问题

内存局部暴涨

短时间内申请很多局部变量,造成内存急剧增长
使用自动释放池包裹代码,及时释放,解除内存占用

内存泄漏

实际是持有对象不释放

实际场景

1、Block
2、NStimer
3、Delegate
4、通知
5、单例持有

解决方式

1、使用weak弱引用打破循环
2、合适时机手动置空,打破循环

过度释放

释放的变量再次释放,会造成崩溃

weak实现原理

内存布局

struct SideTable {
	spinlock_t slock; //锁 自旋锁(优先级反转问题)->互斥锁
	RefcountMap refcnts; //引用计数表(无isa优化或者引用计数益处)
	weak_table_t weak_table; //弱引用表(weak的实现核心)
}

struct weak_table_t {
	weak_entry_t *weak_entries; //hash数组,存在弱引用的对象的信息
	size_t    num_entries; //hash数组元素个数
	uintptr_t mask; //hash数组长度 - 1,参与hash运算
	uintptr_t max_hash_displacement; //hash最大冲突次数
}

#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
	DisguisedPtr<objc_object> referent; // 被弱引用的对象
	// 引用该对象的对象列表,联合。 引用个数小于4,用inline_referrers数组。 用个数大于4,用动态数组weak_referrer_t *referrers
	union {
		struct {
			weak_referrer_t *referrers;  // 超过4个的弱引用该对象的对象指针地址的动态hash数组
			uintptr_t out_of_line_ness : 2;   // 是否使用动态hash数组标记位
			uintptr_t num_refs : PTR_MINUS_2;  // hash数组中的元素个数
			uintptr_t mask; // hash数组长度-1,会参与hash计算				uintptr_t max_hash_displacement;  // hash冲突最大次数
		 };
		struct {
			weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; // 4个一下弱引用的固定存储表
    		};
	};

		bool out_of_line() {
    		return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
		}

		weak_entry_t& operator=(const weak_entry_t& other) {
   	 	memcpy(this, &other, sizeof(other));
    		return *this;
		}

		weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
	 : referent(newReferent) // 构造方法,里面初始化了静态数组
		{
    		inline_referrers[0] = newReferrer;
   	 	for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
        			inline_referrers[i] = nil;
    		}
    	}
    }

weak实现逻辑&方法

weak_register_no_lock方法添加弱引用
id  weak_register_no_lock(weak_table_t *weak_table, id referent_id,   id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id; //当前对象地址
    objc_object **referrer = (objc_object **)referrer_id; //指向当前对象的弱引用地址的地址
    // 如果当前对象为空或者当前对象采用了taggedPointer优化,直接返回对象地址,不需要弱引用
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    // 确保被引用的对象可用(没有在析构,同时应该支持弱引用)
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }
    // 正在析构的对象,不能够被弱引用,是否抛出异常取决于crashIfDeallocating
    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }
    // 在 weak_table中找到当前对象对应的weak_entry,并将referrer加入到weak_entry中
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) { 
        append_referrer(entry, referrer); 	// 将referrer插入到weak_entry_t的引用数组中
    } 
    else { // 如果找不到,就新建一个
        weak_entry_t new_entry(referent, referrer);  
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }
    return referent_id;
}
weak_unregister_no_lock方法删除弱引用
	void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,  id *referrer_id)
	{
    		objc_object *referent = (objc_object *)referent_id;
    		objc_object **referrer = (objc_object **)referrer_id;
    		weak_entry_t *entry;
		if (!referent) return;
		if ((entry = weak_entry_for_referent(weak_table, referent))) { // 查找对应的weak_entry_t
       		 remove_referrer(entry, referrer);  // 在对应的weak_entry_t的hash数组中,移除referrer
	       	 // 移除元素之后, 要检查一下weak_entry_t的hash数组是否已经空了
       		 bool empty = true;
       		 if (entry->out_of_line()  &&  entry->num_refs != 0) {
           		 	empty = false;
        		}
       		else {
            			for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
               			if (entry->inline_referrers[i]) {
                    			empty = false; 
                   			break;
               			}
           		 	}
       		 }
        		if (empty) { // 如果weak_entry_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除
           		 	weak_entry_remove(weak_table, entry);
        		}
    		}
    	}
weak_entry_for_referent取weak_entry_t元素
static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent); // 指向的对象要存在
    weak_entry_t *weak_entries = weak_table->weak_entries;
    if (!weak_entries) return nil; //弱表要存在
    size_t begin = hash_pointer(referent) & weak_table->mask;  // 这里通过 & weak_table->mask的位操作,来确保index不会越界
    size_t index = begin;
    size_t hash_displacement = 0; //记录哈希冲突次数
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries); // 触发bad weak table crash
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) { 
        	// 当hash冲突次数超过上限,说明元素没有在hash表中,返回nil 
            return nil;
        }
    }
    return &weak_table->weak_entries[index];
}
append_referrer添加weak指针元素
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) {
    if (! entry->out_of_line()) { // 如果weak_entry 尚未使用动态数组,走这里
        //尝试插入到固定的inline_referrers数组
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }
        // 如果inline_referrers已满,则转为referrers*动态数组,创建动态数组
        weak_referrer_t *new_referrers = (weak_referrer_t *)calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        //存储inline_referrers元素到动态数组
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[I];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }
    // 对于动态数组的插入
    assert(entry->out_of_line()); // 断言: 此时一定使用的动态数组
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { 
    	// 如果动态数组中元素个数大于或等于数组位置总空间的3/4,则扩展数组空间为当前长度的一倍
    	return grow_refs_and_insert(entry, new_referrer); // 扩容,并插入
    }
    
    // 如果不需要扩容,直接插入到weak_entry中
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // 确保了 begin的位置只能大于或等于数组的长度
    size_t index = begin;  // 初始的hash index
    size_t hash_displacement = 0;  // 用于记录hash冲突的次数,也就是hash再位移的次数
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;  // index + 1, 移到下一个位置,再试一次能否插入
        if (index == begin) bad_weak_table(entry); // index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
    }
    if (hash_displacement > entry->max_hash_displacement) { //重新设置hash冲突极限上限
        entry->max_hash_displacement = hash_displacement;
    }
    // 将ref存入hash数组,同时,更新元素个数num_refs
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

对象内存释放

当对象的引用计数为0时,系统会开始调用dealloc方法释放当前对象的内存
dealloc会调用_objc_rootDealloc方法对对象进行释放,_objc_rootDealloc方法里面会调用rootDealloc方法

rootDealloc

inline void objc_object::rootDealloc() {
    if (isTaggedPointer()) return; //过滤taggedPointer形式
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {//isa优化、不存在弱表、关联对象、边表计数、C++析构函数释放会快
        assert(!sidetable_present());
        free(this);
    } else {
        object_dispose((id)this);
    }
}

object_dispose&&objc_destructInstance

void *objc_destructInstance(id obj) 
{
    if (obj) {
        bool cxx = obj->hasCxxDtor(); //C++ 析构方法
        bool assoc = obj->hasAssociatedObjects(); //关联对象.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }
    return obj;
}

clearDeallocating

inline void objc_object::clearDeallocating() {
    if (slowpath(!isa.nonpointer)) {
        //采用isa优化
        sidetable_clearDeallocating();
    } else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // 存在weak表或者使用sideTable引用计数
        clearDeallocating_slow();
    }
    assert(!sidetable_present());
}

clearDeallocating_slow

NEVER_INLINE void objc_object::clearDeallocating_slow()
{// 未采用isa优化,且存在弱表或着SideTable引用计数
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
    SideTable& table = SideTables()[this]; // 在全局的SideTables中,以this指针为key,找到对应的SideTable
    table.lock();
    if (isa.weakly_referenced) { // 如果obj被弱引用
        weak_clear_no_lock(&table.weak_table, (id)this); // 在SideTable的weak_table中对this进行清理工作
    }
    if (isa.has_sidetable_rc) { // 如果采用了SideTable做引用计数
        table.refcnts.erase(this); // 在SideTable的引用计数中移除this
    }
    table.unlock();
}

weak_clear_no_lock

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{//入参是全局弱表&&当前对象地址
    objc_object *referent = (objc_object *)referent_id;
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); // 找到referent在weak_table中对应的weak_entry_t
    if (entry == nil) { return; }
    weak_referrer_t *referrers;
    size_t count;
    // 找出weak引用referent的weak 指针地址数组以及数组长度
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    //遍历,对所有的weak指针置空
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i]; // 取出每个weak ptr的地址
        if (referrer) {
            if (*referrer == referent) { // 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
                *referrer = nil;
            } else if (*referrer) { // 如果所存储的weak ptr没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    weak_entry_remove(weak_table, entry); // 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
}

内存优化

小对象优化

Taged Pointer:64位架构上对于NSString、NSNumber、NSDate小对象的存储优化
 iOS平台判断对象指针最高有效位为1 ,就是Taged Pointer ,取最低有效位 (p&(1<63))==1,当指针不够存储时会变回动态分配内存方式存储数据

isa的优化

在ARM64架构之前,objc的isa指针就是一个指向类或者元类的指针,而ARM64系统之后,对isa进行了优化,变成了一个共用体(union),用位域来存储更多信息
![isa结构](https://img-blog.csdnimg.cn/82e71aed625d4aeb8399540ddd0b01e6.png)
![isa结构体注释](https://img-blog.csdnimg.cn/e010ea77e39e4157aeb2a7a0dca8dc09.png)

局部优化记录

图片
1、常用的图片,通过UIImage(named: "")方法加载,存入内存缓存,伴随APP退出释放;不常用图片,通过UIImage(contentsOfFile: "")方式加载,用完即释放
2、纯色图片创建 1px的就行;水平渐变图片根据方向设置生成宽高,节约内存
3、图片展示大小设置,设置与展示视图大小相同即可
4、改变图片颜色通过tintColor&&renderMode方式
视图
1、自定义UIVIew使用CAShapeLayer绘制代替drawRect方法
2、UILabel大小设置接近ContentSize大小
3、大视图分块复用展示&&延迟加载,类似tableview
4、Masory布局内存消耗高于frame
缓存
1、设置上限,适时缓存,及时清理
2、YYModel、SDWebImage、Lottie动画
模型
1、减少非需要字段
2、减少常驻内存对象,如单例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值