XMPPFramework开发(管理三):好友列表

到头来驾驭那一个年知其可是不知其所以然的iOS内存管理办法

</br>

前言

从自我起来读书iOS的时候,身边的仇人、网上的博客都告诉自己iOS的内存管理是凭借引用计数的,然后说引用计数超过1则对象保存在内存的堆中而引用计数等于0则对象销毁。然后又说在所谓的ARC时代,强指针指向一个目的,则对象不销毁;一个对象没有其余一个强指针指向则销毁….,最终,我想说这几个都很有道理的榜样,然则,我或者不了解怎么引用计数器为0为何会被销毁,为何一个对象没有强指针指向就会销毁,为何在@property中一个OC对象要运用strong举办修饰
…. 。所以,在攻读 Objective-C高级编程:iOS与OS
X多线程和内存管理
后,让自身清楚了成百上千工作。以下是对此那本书里面知识的总计性内容,要是要详细精晓,请阅读该书籍。

小心:上面的内容是顺应于已经对此iOS内存管理有早晚理解的程序员

搞事前言


前一篇博客,大家对XMPPFramework的记名注册功效以及逻辑做了详尽的求证,用户登录成功之后,大家需求做的就是取获得近来账号的好友列表和个人音信,明天这一篇博客就是对好友列表的有关逻辑以及代理方法来做一下授课表明.我们先看看SDChat中的好友列表示意图.

</br>

内存管理的想想情势

  • 自己生成的对象,自己所有
  • 非自己生成的对象,自己也能拥有
  • 不再需求自己装有对象时释放
  • 非友好所有的靶子无法自由
  1. 温馨生成的目的,自己独具

在iOS内存管理中有七个第一字,alloc、new、copy、mutableCopy,自身行使那些关键字发生对象,那么自己就拥有了对象

    // 使用了alloc分配了内存,obj指向了对象,该对象本身引用计数为1,不需要retain 
    id obj = [[NSObject alloc] init]; 

    // 使用了new分配了内存,objc指向了对象,该对象本身引用计数为1,不需要retain 
    id obj = [NSObject new]; 
  1. 非自己生成的对象,自己也能有所

    // NSMutableArray通过类方法array产生了对象(并没有使用alloc、new、copy、mutableCopt来产生对象),因此该对象不属于obj自身产生的
    // 因此,需要使用retain方法让对象计数器+1,从而obj可以持有该对象(尽管该对象不是他产生的)
    id obj = [NSMutableArray array];
    [obj retain];
  1. 不再必要协调装有对象时释放

    id obj = [NSMutableArray array];  
    [obj retain];

    // 当obj不在需要持有的对象,那么,obj应该发送release消息
    [obj release];
  1. 不知道该怎么做自由非友好拥有的对象

    // 1. 释放一个已经释放的对象
    id obj = [[NSObject alloc] init];

    // 已经释放对象
    [obj release];

    // 释放了对象还进行释放
    [obj release];


    // 2. 释放一个不属于自己的对象
    id obj1 = [obj object]; 

    // obj1没有进行retain操作而进行release操作,使得obj持有对象释放,造成了野指针错误
    [obj1 release];

如上为iOS举办内存管理的四种考虑方式(记住不论是ARC依旧MRC都依照该寻思方式,只是ARC时代这一个干活儿让编译器做了)

XMPPFramework中好友关系表达解释


在XMPPFramework中吗,好友关系是可以由此订阅来兑现的,也就是说A与B互相订阅,那么A与B就是好友了,若是A只是订阅了B,B没有订阅A,那么大家就说A与B两者不是忘年交,当然了,我在实际上进度中搞好友添加的逻辑照旧比较多的,那里须要明白A与B互相订阅(openfire服务器中订阅状态为both,当然了,订阅状态也有from和to,那样的也终于好友.具体景况前面会详细表达),那么A与B就是好友那几个逻辑即可.

</br>

引用计数器研商

苹果对于引用计数的管制是通过一张引用计数表进行管理的

引用计数表.png

咱俩日常在操作对象的引用计数器时,其实就是对这些引用计数表举办操作,在收获到该表的地方以及相应对象的内存地址,就可以透过对象的内存从该表中开展索引获取到对应的引用计数值,然后根据用户的操作来回到计时器、计时器加1、计时器减1,下边就深远商讨retain、release、alloc、dealloc具体怎么操作该引用计数表

知音列表获取流程.


当用户登录成功之后,大家做的最重视的一个模块就是加载好友列表模块.那么好友加载模块的全体流程是何许的呢?我们先看一个SDChat好友列表的流程图,辅助大家耳熟能详好友列表在实质上进度中怎么着表现的.(图片可能看不清楚,请自行下载查看,谢谢.)

</br>

知音服务器数据得到代码部分

XMPPFramework中好友列表的保管主旨类是XMPPRoster,这一个类可以用来对忘年交的音讯获得,添加,删除等操作.在SDChat中,大家把XMPPRoster声明为SDXmppManager的一个性能对象,并且在初叶化进程中激活好友模块.代码如下所示.(表达:XMPPRosterCoreDataStorage对象使用存储好友数据的.)

self.rosterCoreDataStorage= [XMPPRosterCoreDataStorage sharedInstance];

self.roster = [[XMPPRoster alloc]initWithRosterStorage:self.rosterCoreDataStorage dispatchQueue:dispatch_get_global_queue(0, 0)];

//激活roster
[self.roster activate:self.stream];

实在好友获取是有三种情势的,骚栋使用的是代理方法赢得好友节点的.由于代理方法默许的是在报到成功之后默许就会调用获取好友节点,不过自己做页面的时候须要调剂一下密友节点获取的机遇,所以,我就把XMPPRoster的机关获取好友节点效能关闭了.当然了,你能够行使机动获取.这几个需要按照实际情形而定,完毕代码如下所示.

self.roster.autoFetchRoster = NO;

那般在大家登录成功将来,大家需求在- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender那么些措施中手动调起获取好友的方法.调起方法也很简短,只必要一行代码就可以.

[[SDXmppManager defaulManager].roster fetchRoster];

当大家调起了得到好友的法子之后,大家需求在在联系人列表(SDContactsVC)那么些控制器中先安装XMPPRoster对象的代理.我是在初阶化就设置了代理对象.

[[SDXmppManager defaulManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];

安装落成以后代理方法其实总共是有多少个的,五个代理调取的实际分别是具备好友节点获取伊始,每一个密友节点获取到的时候,所有好友节点获取成功未来,大家得以根据实际境况来进展分化的操作.比如我们在取得初阶以前开端化好友节点数组,获取截止刷新页面等等,具体的两个代理方法如下所示.

//开始获取好友节点列表的时候
-(void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender;

//获取每一个好友节点的时候
-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item;

//结束获取好友节点列表的时候
-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender;

那是自己只利用了后面的七个代理方法,那是有来头的,因为我的密友节点数组([SDUser defaulUser].contactsArray)不需求每一回拿走都开展创新,而且那七个代理方法在实质上采取进程中,自动调取的次数过多,比如添加完好友或者去除完好友都能自行调取那一个三个代理方法,为了不要求的劳动,所以我只是用了前边的七个代理方法.仍旧那句话,大家可以依照自己的实际上景况自行调用不一致的代办方法.

咱俩先看一下SDChat中在-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item;骚栋都做了怎么着的操作.首先,大家获取到每一个好友节点item,然后我们先判断item节点的订阅音讯subscription的值,大家须要的莫逆之交是双方互为订阅,也就是”subscription”属性为”both”、”from”、”to”才是大家需求的好友节点.所以符合那二种景况的都是大家须求的相知节点,所以if的筛选标准就出去了,如下代码所示.其余订阅类型分歧的节点我们前边会说到现实的情状.

if ([[[item attributeForName:@"subscription"] stringValue] isEqualToString:@"both"]||[[[item attributeForName:@"subscription"] stringValue] isEqualToString:@"from"]||[[[item attributeForName:@"subscription"] stringValue] isEqualToString:@"to"]) {

}

在筛选完毕将来,大家要求做的事务就是收获到item管理,节点的JID音信了,那里大家只需求两行代码就足以成功了.

NSString *SJid = [[item attributeForName:@"jid"] stringValue];

XMPPJID *jid = [XMPPJID jidWithString:SJid];

任由是初期界面上展现好友的JID新闻,如故中期突显电子名片音信,大家都亟待先遍历好友节点数组([SDUser defaulUser].contactsArray)判断数组中是或不是早已存在该好友新闻了.那样做的原故是因为-(void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item;其一代理方法可能对一个好友节点获取很多次,假若大家不开展拔取性的增进的话,数组可能会见世好友重复的光景的,所以大家须要先判断是或不是存在该好友新闻,具体代码如下所示.(关于isDeleteFriend布尔值的留存的意思,当大家删除好友的时候,订阅音信subscription的值可能并不是”remove”,有可能是”both”,所有大家那里必要加一个布尔值,后边的删除好友,我们会详细表明的.)

BOOL isExist = NO;

for (SDContactModel *contact in self.user.contactsArray) {

       if ([contact.jid.user isEqualToString:jid.user]) {

            isExist = YES;

          }
}         

看清完是不是存在好友音信之后,大家就可以按照isExist那几个布尔值来判定了是或不是要添加多少了,添加多少经过如下代码所示,这里我是收获了忘年交的片子音讯进行的增加,先前时期的话可以直接添加JID.

if (!isExist) {
    //添加数据
    XMPPvCardTemp *vCard =  [[SDXmppManager defaulManager].vCardTempModule vCardTempForJID:jid shouldFetch:YES];

    SDContactModel *contact =[[SDContactModel alloc]init];
    contact.jid = jid;
    contact.vCard =vCard;
    contact.isAvailable = NO;

    [self.user.contactsArray addObject:contact];

}

下边就是从服务器获取到相知数据的主干流程了.

</br>

知音数据本地整理代码部分

当大家获取好友数据形成之后,我们并不是平素展现到页面上,大家要求对忘年交数据开展整治然后再突显到界面之上.大家透过-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender;其一代理方法来调取我们的知心人数据整理方法-(void)networkingWithContactsArray;.

-(void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{

    [self networkingWithContactsArray];

}

SDChat的至交界面是近乎于微信的至交界面的,是分组体现的.所以,数据存储的总体思路是,列表的数据源是储存于一个字典当中,大家把首字母相同的JID或者是用户名存储于一个数组当中.每个key是每一个JID的首字母大写(或者是用户名称的首字母大写).因为字典是无序的,那么怎样成功有序的排列呢?我们要求建立其它一个数组作为排序数组,同时也起着索引数组的效率.大家把字典中存有的Key放入数组中,然后排序,数据提取进度中大家只需求基于数组的排列顺序拿取即可.示意图如下所示.

这就是说大家看一下实在代码进度中,对于字典的数量增长整理部分,首先大家要开端化字典对象,然后大家遍历好友节点数组([SDUser defaulUser].contactsArray),取出大家须求排序的每一个重中之重字符串(不管是JID仍然用户名).大家调用-(NSString *)transform:(NSString *)chinese本条点子回去首字母并且大写.在这么些主意中大家有二种情景须要处理,一种是赢得字符串战败,也就是说传入的是一个nil值,大家一向回到
“#”
,别的一种是只要首字母是数字,那么大家也是索要再次回到”#”的.所以那样回去首字母所使用到的形式总共就有了四个,五个用来判定是还是不是是数组,一个则是截取并且开展字母大写的操作.多少个章程如下所示.

//截取首字母并且大写
-(NSString *)transform:(NSString *)chinese{

    if (chinese == nil ||[chinese isEqualToString:@""]) {

        return @"#";

    }

    NSMutableString *pinyin = [chinese mutableCopy];
    CFStringTransform((__bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformMandarinLatin, NO);
    CFStringTransform((__bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformStripCombiningMarks, NO);

    NSString *subString = [[pinyin uppercaseString] substringWithRange:NSMakeRange(0, 1)];

    if ([self isPureInt:subString] || [self isPureFloat:subString]) {

        return  @"#";
    }

    return subString;
}

//判断是否为整型:
- (BOOL)isPureInt:(NSString*)string{
    NSScanner* scan = [NSScanner scannerWithString:string];
    int val;
    return [scan scanInt:&val] && [scan isAtEnd];
}
//判断是否为浮点型:
- (BOOL)isPureFloat:(NSString*)string{
    NSScanner* scan = [NSScanner scannerWithString:string];
    float val;
    return[scan scanFloat:&val] && [scan isAtEnd];
}

那么通过重临首字母,大家需求看清一下脚下的字典对象中是否业已存在了改分组的数组,如若存在,那么直接存储,即使不设有,那么初步化一个数组之后,以首字母为Key,空数组为Value保存到字典中,然后再把多少存储到数组中去.具体代码如下所示.

if (self.contactsPinyinDic[firstWord] ==nil) {

    //如果联系人字典数组中没有该分组,那么就初始化一个分组数组,然后存储.
    NSMutableArray *sectionArray =[NSMutableArray  arrayWithCapacity:16];

    [sectionArray addObject:contact];

    [self.contactsPinyinDic setValue:sectionArray forKey:firstWord];

}else{

    NSMutableArray *sectionArray =self.contactsPinyinDic[firstWord];

    [sectionArray addObject:contact];

}

拉长完结将来,咱们就需求对索引数组进行操作了,首先我们依然先初始化大家的索引数组,开首化的历程中,大家就把具备的key值添加到大家的数组当中去.然后大家必要丰盛一个空字符串@"",那是为着给”新的爱侣”这个分组做准备的.代码如下所示.

self.indexArray = [NSMutableArray arrayWithArray:self.contactsPinyinDic.allKeys];
[self.indexArray addObject:@""];//添加一个空的字符串,用于菜单分组

下一场,咱们对索引数组进行遍历排序操作.

    for (int i = 0; i<self.indexArray.count; i++) {

        for (int j = 0; j<i; j++) {

            if (self.indexArray[j]>self.indexArray[i]) {

                NSString *objString = self.indexArray[j];
                self.indexArray[j] =  self.indexArray[i];
                self.indexArray[i] = objString;

            }
        }
    }

一旦存在”#”,为了界面的精彩,大家把”#”放在索引数组的结尾一位,然后,大家就刷新大家的页面即可.

//索引数组移动#号到最后.
if ([self isIncludeWithJing]) {

    [self.indexArray removeObject:@"#"];

    [self.indexArray addObject:@"#"];
}

[self.contactsList reloadData];

那样在tableView的数据源方法中,分组个数为索引数组的要素个数self.indexArray.count;每一个section中元素的个数(除了一个分组)都是为字典中对应的每一个value数组的个数.

接下来,大家就可以做出初阶的界面的规范来了.当然了,那样的画面要求大家做过多办事的,也是我们下一篇博客所要说到的,电子名片的达成.

</br>

alloc

当大家调用alloc函数时大家更为会调用allocWithZone方法

    id obj = [[NSObject alloc] init];


    + (id)alloc {
        return [self allocWithZone:NSDefaultMallocZone()];
    }

    + (id)allocWithZone:(NSZone*)z {
        return NSAllocateObject(self,0,z);
    }

调用NSAllocateObject函数对内存举办分配

结束


SDChat中的好友获取的逻辑和代办方法就说到此处了,那里自己要先声美赞臣(Meadjohnson)下,SDChat中或许还存在着Bug,假如有别的问题,欢迎联系骚栋,谢谢.接下来的一篇我觉着应该先把XMPPFramework电子名片的落成说一下,XMPPFramework我认为最坑的就是丰盛好友这一块了,逻辑相比多,准备在第五篇中展开讲解表明.希望我们持续关心~最终把SDChat的传送门送给大家.我们可以相比较着Demo来看本篇博客.

retain、release、retainCount

该书籍对于那八个函数调用先是使用GNUstep(一个Cocoa框架的互换框架,作用类似)进行教学,后来又讲解了苹果对于引用计数的贯彻。在此间大家就谈谈苹果的落到实处了。

调用retain、release、retainCount时函数调用顺序:

retain、retainCount、release函数调用顺序.png

正如所示,调用种种函数时会调用__CFDoExternRefOperation函数,该函数蕴含于CFRuntime.c中,该函数简化代码如下:

- (NSUInteger)retainCount 
{
    return (NSUInteger)__CFDoExternRefOperation(OPERATION_retainCount,self);
}

- (id)retain 
{
    return (id)__CFDoExternRefOperation(OPERATION_retain,self);
}

- (void)release 
{
    return __CFDoExternRefOperation(OPERATION_release,self);
}

    int __CFDoExternRefOperation(uintptr_r op,id obj) {
        CFBasicHashRef table = 取得对象对应的散列表(obj);
        int count;

        switch(op) {
            case OPERATION_retainCount: 
                count = CFBasicHashGetCountOfKey(table,obj);
                return count; 
            case OPERATION_retain: 
                CFBasicHashAddValue(table,obj);
                return obj; 
            case OPERATION_release: 
                count = CFBasicHashRemoveValue(table,obj):
                return 0 == count;
        }
    }

代码如上所示,可以想象苹果就是应用类似于上述的引用计数表来管理内存,也就是说我们在调用retain、retainCount、release时首先调用__CFDoExternRefOperation进而赢获得引用技术表的内存地址以及本对象的内存地址,然后依据目标的内存地址在表中询问得到到引用计数值。

若是retain就加1
要是retainCount就径直重临值,
假定release则减1而且在CFBasicHashRemoveValue将官引用计数缩短到0时会调用dealloc,从而调用NDDeallocateObject函数、free函数将目标所在内存释放

以上就是在谈论苹果对此引用计数的管制艺术,对于GNUStep办法请自行查阅书籍

–>SDChat传送门🚪

</br>

autorelease

职能:将对象放入自动释放池中,当自从释放池销毁时对自动释放池中的对象都进展一次release操作
挥洒方式:

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    id obj = [[NSObject alloc] init];

    [obj autorelease];

    [pool drain];   

对于autorelease的落到实处格局,书籍也相比较了GNUSetp与苹果落成的艺术,现在经过GNUStep源代码来了然苹果的兑现

  1. GNUStep实现

    id obj = [[NSObject alloc] init];
    [obj autorelease];

    - (id)autorelease {
        [NSAutoreleasePool addObject:self];
    }

    + (void)addObject:(id)anObject {
        NSAutoreleasePool *pool = 取得正在使用的Pool对象;  
        if (pool != nil) {
            [pool addObject:anObject];
        }else {
            NSLog(@"NSAutoreleasePool非存在状态下使用Pool对象");
        }
    }

    - (void)addObject:(id)anObject {
        [array addObject:anObject];
    }

从上边可以看看,自动释放池就是经过数组已毕的,大家在调用autorelease时最终就是将本对象添加到当前自行释放池的数组
而针对性于活动释放池销毁时对数组中的进行三回release操作,见下边

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    ... 
    // 当自动释放池销毁时
    [pool drain];

    - (void)drain {
        [self dealloc];
    }

    - (void)dealloc {
        [self emptyPool];
        [array release];
    }

    - (void)emptyPool {
        for (id obj in array) {
            [obj release];
        }
    }
  1. 苹果的落成

    class AutoreleasePoolPage 
    {
        static inline void *push() 
        {
            相当于生成或持有NSAutoreleasePool类对象
        }

        static inline void *pop(void *token)
        {
            相当于废弃NSAutoreleasePool类对象
            releaseAll();
        }

        static inline id autorelease(id obj)
        {
            相当于NSAutoreleasePool类的addObject类方法   
            AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的AutoreleasePoolPage实例; 
            autoreleasePoolPage->add(obj);
        }

        id *add(id obj) 
        {
            将对象追加到内部数组中
        }

        void releaseAll() 
        {
            调用内部数组中对象的release实例方法 
        }
    };

    void *objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }

    void objc_autoreleasePoolPage(void *ctxt)
    {
        AutoreleasePoolPage::pop(ctxt);
    }

    id *objc_autorelease(id obj) 
    {
        return AutoreleasePoolPage::autorelease(obj);
    }

如上所示,苹果内部选取了近乎于GNUStep中的思想,将对象添加进数组进行保管

ARC中内存管理艺术

介绍
关于那有些的内存,小编是分了两局地进行商量,第一片段介绍ARC管理所必要的重中之重字__strong
、__weak、__unsafe_unretained、__autoreleasing的效果;第二有的介绍了ARC针对于那些关
键字的切实内管管理落到实处格局。上面大家就综合两有的的情节开展一遍研究

苹果官方文档说ARC是有”编译器自行举行管制”,但实在只是是编译器是不够,须求满意上面啷个规范

  • clang(LLVM编译器)3.0以上
  • objc4 Objective-C运行时库493.9以上

__strong

作用
    id __strong obj = [[NSObject alloc]init];

如上代码,表示obj这么些强指针指向NSObject对象,且NSObject对象的引用计数为1

    id __strong obj1 = obj; 

如上代码,表示obj1以此强指针与obj指针指向同一个NSObject对象,且NSObject对象的引用计数为2

    id __strong obj = [NSMutableArray array];

如上代码,表示obj这些强指针指向的NSMutableArray对象的引用计数为1

综上所示,当一个对象被强指针指向则援引计数就加1,否则,该目的没有一个强指针指向则自动释放内存

那就是说问题来了,为何一个对象被强指针指向引用计数就加1呢?
为何分配在堆里面的靶子内存能够自行释放内存?

原理

首先种情况: 对象是经过alloc、new、copy、multyCopy来分配内存的

    id __strong obj = [[NSObject alloc] init];

当使用alloc、new、copy、multyCopt进行对象内存分配时,强指针直接针对一个引用计数为1的靶子,在编译器作用下,上述代码会转换成以下代码

    id obj = objc_msgSend(NSObject,@selector(alloc));
    objc_msgSend(obj,@selector(init));

    // 当让这个代码会在合适的时候被调用,不是马上调用
    objc_release(obj);

其次种情况:
对象不是本身变化,可是我拥有(一般那样的对象是经过除alloc、new、copy、multyCopy外方法暴发的)

    id __strong obj = [NSMutableArray array];

在那种意况下,obj也针对一个引用计数为1的对象内存,其在编译器下转移的代码如下:

    id obj = objc_msgSend(NSMutableArray,@selector(array));

    // 代替我们调用retain方法,使得obj可以持有该对象
    objc_retainAutoreleasedReturnValue(obj);
    objc_release(obj);

据此使得obj指向了一个引用计数为1的靶子,
可是,objc_retainAutoreleaseReturnValue有一个成对的函数objc_autoreleaseReturnValue,那七个函数可以用来最优化程序的运作
如下代码:

    + (id)array 
    {
        return [[NSMutableArray alloc] init];
    }

代码转换如下:

    + (id)array 
    {
        id obj = objc_msgSend(NSMutableArray,@selector(alloc));
        objc_msgSend(obj,@selector(init));

        // 代替我们调用了autorelease方法
        return objc_autoreleaseReturnValue(obj);
    }

在更换后的代码,大家得以望见调用了objc_autoreleaseReturnValue函数且这几个函数会回去注册到活动释放池的靶子,不过,这几个函数有个特点,它会翻动调用方的一声令下执行列表,如果发现接
下来会调用objc_retainAutoreleasedReturnValue则不会回来注册到活动释放池的靶子而只是再次回到一个对象而已。

双方的涉及图如下:

关系图.png

透过那些,我们就足以通报为何强指针指向一个目的,这么些目的的引用计数就加1

__weak

作用
    id __weak obj = [[NSObject alloc] init];

据悉大家的文化,可以驾驭NSObject对象在转移之后马上就会被放飞,其根本缘由是__weak修饰的指针没有引起对象内部的引用计数器的更动
因此,__weak修饰的指针常用来打破循环引用或者修饰UI控件,关于__weak修饰的指针引用场景那里不叙述,下边首要介绍其原理

原理

咱俩了解弱指针有多个效益:一.
修饰的指针不会挑起指向的靶子的引用计数器变化 二.
当指向的对象被销毁时,弱指针全体置为nil,
那么除了这么些之外,我们还有一个要说的就是,为啥大家
在程序中无法反复的选拔weak呢?

  1. 缘何弱指针不会挑起指向的靶子的引用计数器暴发变化

    id __weak obj = [[NSObject alloc] init];

编译器转换后的代码如下:

    id obj;
    id tmp = objc_msgSend(NSObject,@selector(alloc));
    objc_msgSend(tmp,@selector(init));
    objc_initweak(&obj,tmp);
    objc_release(tmp);
    objc_destroyWeak(&object);

对于__weak内存管理也凭借了看似于引用计数表的表,它通过对象的内存地址做为key,而相应的指针作为value举行管理,在上述代码中objc_initweak就是瓜熟蒂落这部分操作,而objc_destroyWeak
则是绝迹该目的对应的value。所以,weak在修饰只是让weak表扩展了记录没有引起引用计数表的变化

  1. 当弱指针指向的对象呗销毁时,弱指针怎么才能自动置为nil?
    为何大家在先后中不可能屡屡使用weak呢

对象通过objc_release释放对象内存的动作如下:

  • objc_release
  • 因为引用计数为0所以执行dealloc
  • _objc_rootDealloc
  • objc_dispose
  • objc_destructInstance
  • objc_clear_deallocating

而在对象被丢掉时最终调用了objc_clear_deallocating,该函数的动作如下:

  1. 从weak表中赢得已舍弃对象内存地址对应的具有记录
    2)将已废弃对象内存地址对应的记录中拥有以weak修饰的变量都置为nil
    3)从weak表删除已舍弃对象内存地址对应的笔录
    4)依据已放任对象内存地址从引用计数表中找到呼应记录删除

据此可以解释为啥对象被灭绝时对应的weak指针变量全体都置为nil,同时,也看出来销毁weak步骤较多,假设大度行使weak的话会大增CPU的载重
而不提议多量应用weak,还有一个缘故看下边的代码:

    id __weak obj1 = obj; 
    NSLog(@"obj2-%@",obj1);

编译器转换上述代码如下:

    id obj1; 
    objc_initweak(&obj1,obj);

    // 从weak表中获取附有__weak修饰符变量所引用的对象并retain 
    id tmp = objc_loadWeakRetained(&obj1);

    // 将对象放入自动释放池
    objc_autorelease(tmp);
    NSLog(@"%@",tmp);
    objc_destroyWeak(&obj1);

据此当大家走访weak修饰指针指向的对象时,实际上是访问注册到机关释放池的目标。因而,借使大气利用weak的话,在我们去访问weak修饰的对象时,会有大气目的注册到机关释放池,那会影响程
序的性能。推介方案 :
要访问weak修饰的变量时,先将其赋给一个strong变量,然后举办访问

最后一个题材: 为啥访问weak修饰的目的就会造访注册到活动释放池的靶子呢?

  • 因为weak不会唤起对象的引用计数器变化,由此,该目的在运行进度中很有可能会被保释。所以,须要将目的注册到自动释放池中并在机关释放池销毁时释放对象占用的内存。

__unsafe_unretained

作用

__unsafe_unretained功用须求和weak举行自查自纠,它也不会引起对象的中间引用计数器的扭转,然而,当其针对性的目标被灭绝时__unsafr_unretained修饰的指针不会置为nil。而且貌似__unsafe_unretained就和它的名字一样是不安全,它不纳入ARC的内存管理

__autoreleasing

作用

ARC无效

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain];

ARC有效*

    id __autoreleasing obj1 = obj;

如上所示,通过__autoreleasing修饰符就到位了ARC无效时同样的功用

理所当然,在某有些场地下大家不通过显式指定__autoreleasing关键字就可以形成机关切册到自动释放池的效益,例如以下处境

第一种:

    @autoeleasepool {
        // 如果看了上面__strong的原理,就知道实际上对象已经注册到自动释放池里面了 
        id __strong obj = [NSMutableArray array];
    }

第二种:

访问__weak修饰的靶鸡时,对象就被登记到了活动释放池

第三种:

以下方式的默许修饰符是__autorelease

  • id *obj;
  • NSObject **obj;

与此同时,也引出一个题目:
为何在@property中OC对象使用strong而基本数据类型使用assign?

属性默认修饰符.png

从表中可以推论出,在ARC在OC对象的默许修饰符是__strong,因此,在@property中使用strong
而基本数据类型是不纳入到ARC内存管理中的,__unsafe_unretained也不归ARC管,由此,使用assign对中央数据类型举办修饰

原理 “`objc @autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];
}

代码转换如下:  

```objc
    id pool = objc_autoreleasePoolPush(); 
    id obj = objc_msgSend(NSObject,@selector(alloc));
    objc_msgSend(obj,@selector(init));
    objc_autorelease(obj);
    objc_autoreleasePoolPop(pool);

    @autoreleasepool {
        id __autoreleasing obj = [NSMutableArray array];
    }

代码转换如下:

    id pool = objc_autoreleasePoolPush();
    id obj = objc_msgSend(NSMutableArray,@selector(array));
    objc_retainAutoreleasedReturnValue(obj);
    objc_autorelease(obk);
    objc_autoreleasePoolPop(pool);

上述代码,代表的就是自我变化并兼有对象、自身不生成但也拥有对象的两种__autorelease内存管理状态

ARC规则

  • 无法利用retain、release、retainCount、autorelease方法(如果ARC下拔取会并发编译错误)

  • 不能动用NSAllocateObject、NSDeallocateObject函数(即使ARC下行使会油然则生编译错误)

  • 无须显式调用dealloc(ARC下,显式调用dealloc并在代码中书写[super
    dealloc]也会产出编译错误)

  • 使用@autoreleasepool块代替NSAutoreleasePool

    @autoreleasepool{}块相比较NSAutoreleasePool而言显得代码更加整洁、层次性强,而且@autoreleasepool代码快哉ARC或者非ARC下都是可以使用的
  • 需坚守内存管理命名规则

    1) alloc、new、copy、mutableCopy等以这些名字开头的方法都应当返回调用方能够持有的对象  
    2)init开头的方法必须是实例方法并且要返回对象,返回值要是id或者该方法对应类的对象类似或者其超类或者其子类。另外,init开头的方法也仅仅用作对对象进行初始化操作
  • 无法应用区域(NSZone)

    区域是以前为了高效利用内存的使用率而设计的,但是,目前来说ARC下的模式已经能够有效利用内存,区域在ARC下还是非ARC下都已经被单纯的忽略 
  • 对象型变量不可能作为C语言结构体的分子

    OC对象型变量如果成为了C语言结构体的成员,那么,ARC不能掌握该对象的生命周期从而有效管理内存,因此,不能这样使用。 
  • 显式转换”id” 和 “void*”

    非ARC下:  
    id obj = [[NSObject alloc] init];
    void *p = obj; 
    这样的代码是可行的,id和void*可以方便得自由转化 ,但是,在ARC下是不一样的 

    ARC下id和void*有三个转换的关键字 __bridge、__bridge_retained、__bridge_transfer: 
    id obj = [[NSObject alloc] init]; 
    void *p = (__bridge void*)obj;

    注意: __bridge不会引起对象的引用计数变化,因此,安全性不太好。相比较,__bridge_retained不仅仅实现了__bridge的功能而且能让p调用retain方法使p持有对象。另外,
    __bridge_transfer也是和release方法类似,使用__bridge_transfer进行转化,既让对象p调用一次retain方法,而且原来指针obj会调用一次release方法也非常安全 

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注