属性关键字
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、减少常驻内存对象,如单例