52个编写高质量iOS有效方法(31-40)

快到年底了 无心工作,哎

1.讲了dealloc方法的使用。平时的使用过程中要注意下面几点内容:

  • 在dealloc方法中,需要做的仅仅是释放指向其他对象的引用,并且取消原来的订阅(KVO)或者是NSNotificationCenter等通知,其他的事情不要做。
  • 不论是执行异步任务还是执行同步任务,都不要再dealloc方法中调用,因为此时对象已经处在即将被回收的状态了

2.许多时下流行的语言都提供了“异常”(exception)这一特性。纯C中没有异常,而C++与OC都支持异常。实际上,C++和OC的异常相互兼容,也就是说,从其中一门语言里抛出的异常能用另外一门语言来捕获。

发生异常时应该如何管理内存是个值得研究的问题。看下面的代码

@try{
EOCSomeClass *object = [[EOCSomeClass alloc]init];
[object doSomethingThatMayThrow];
}
@catch(...)
{
    NSLog(@"Whoops there was an error.oh well");
}

如果doSomethingThatMayThrow抛出异常,由于异常会跳至catch块,因为那行release代码不会运行。在这种情况下,如果代码抛出异常,那么对象就泄露了。解决办法是使用@finally块,无论是否抛出异常,其中的代码都保证会运行,且只运行一次,如下所示:

@try{
EOCSomeClass *object = [[EOCSomeClass alloc]init];
[object doSomethingThatMayThrow];
[object release];
}
@catch(...)
{
    NSLog(@"Whoops there was an error.oh well");
}
@finally{
    [object release];
}

很遗憾,在ARC下,由于不是手动release,所以上面的情况ARC不会自动处理。因为这样做要插入大量样板代码。

● 捕获异常时,一定要注意将try块内所创立的对象清理干净

●默认情况下ARC不会生成安全处理异常所需要的清理代码。开启编译选项后,可以插入代码,不过会导致应用程序过大,而且会降低效率。

3.最简单的循环引用由两个对象构成,他们相互引用对方。如图所示:

Paste_Image.png

这样的引用,导致谁也不会被释放掉。还有多个对象的循环应用,如图:

Paste_Image.png

避免循环引用的最佳方式就是弱引用。这种引用经常用来表示“非拥有关系”在MRC下使用unsafe_unratain和ARC下的weak。

Paste_Image.png

上图中的虚线就是weak,

● 将某些引用设为weak,可避免出现“循环引用”。

● weak引用可以自动清空,也可以不清空。自动情况(autonilling)是随着ARC而引入的新特性,由运行期系统来实现。在具备自动清空的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象

4.考虑下面代码:

for(int i=0;i<100000;i++){
    [self doSomethingWithInt:i];
}

如果“doSomethingWithInt”方法要创建临时对象,那么这些对象很可能会被放到自动释放池里。这样一来在执行for循环时,应用程序所占用内存就会持续上涨,而等到临界值对象都释放后,内存用量又会突然下降。

for(int i=0;i<100000;i++){
@autoreleasepool{
    [self doSomethingWithInt:i];
}
}

加上这个自动释放池之后,应用程序循环时的内存峰值就会降低。自动释放池就像“栈”(stack)系统创建好自动释放池后,将其推入栈中,而清空自动释放池,则相当于从栈中弹出。
“自动释放池”本身又有开销,所以是否使用“自动释放池”取决于应用程序。
● 自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池子里
● 合理运用自动释放池,可以降低应用程序的内存峰值
● @autoreleasepool这种新式写法能创建出更为轻便的自动释放池

5.Cocoa提供了“僵尸对象”(Zombie Object)这个非常方便的功能。它的实现代码深植于OC的运行期程序库、Foundation框架以及CoreFoundation框架中。他的原理是这样的:系统在即将回收对象时,如果发现xcode启用了对僵尸象功能,那么还将执行一个附加步骤:把对象转化为僵尸对象,而不彻底回收。

即便是使用了ARC,也依然会出现这种内存bug
● 系统在回收对象时,可以不将其真的回收,而是把它转化位僵尸对象。通过环境变量NSZombieEnable可以开启此功能。

● 系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使该对象变为僵尸对象。僵尸类能够响应所有的选择子,响应方式为:打印一条包含消息内容及其接受者的消息,然后终止应用程序。

6.这里我们禁止使用retainCount,无论是ARC环境还是MRC环境。
NSObject协议中定义了下列方法,用于查询对象的保留计数:

-(NSUInterger)retainCount; 

然而ARC已经将此方法废弃了。实际上,如果在ARC中调用,编译器就会报错,跟ARC中调用retain、release、autorelease的情况是一样的。但是在非ARC下还是可以调用retainCount接口。为啥不要使用retainCount呢?

此方法之所以无用,首要原因在于:它返回的保留计数只是某个给定时间点上的值。该方法并未考虑到系统会稍后把自动释放池清空,所以未必真实反应实际的保留计数了。

下面的写法是非常糟糕的:

while([object retainCount]){  
    [object release];  
}  

第二个错误在于:retainCount可能永远不返回0,因为有时候系统会优化对象的释放行为。

第三种情况:看下面的代码

NSString *string = @"Some string";  
NSLog(@"string retainCount=%lu",[string retainCount]);  
NSNumber *numberI = @1;  
NSLog(@"numberI retainCount = %lu",[numberI retainCount]);  
NSNumber *numberF = @3.141f;  
NSLog(@"numberF retainCount=%lu",[number retainCount]); 

在64位MAC OSX 10.8.2系统中,用Clang 4.1编译后,这段代码输出的消息如下:

string retainCount = 18446744073709551615 //2^64-1  
numberI retainCount = 923372036854775807 //2^63-1  
number retainCount = 1  

string是个常量,编译器把NSString对象所表示的数据放到应用程序的二进制文件里,这样运行程序时就可以直接用了,无须再创建NSString对象。NSNumber也类似,它使用了一种叫做“标签指针”(tagged pointer)的概念来标注特定类型的数值。这总做法不使用NSNumber对象,而是把数值有关的全部消息放到指针值里面。运行期系统会在消息派发期间检测到这种标签指针,并对它志向相应操作,使其行为看上去和真正的NSNumber一样。这种优化在某些场合使用,但是浮点数就没有这个优化,保留计数还是1

7.OC中多线程编程的核心就是block与gcd。这虽然是两种不同的技术,但他们是一并引入的。block是一种可在C、C++及OC代码中使用的“词法闭包”(lexical closure),它极为有用,借此机制,开发者可将代码像对象一样传递,令其在不同环境(context)下运行。在block的范围内,它可以访问到其中的全部变量。
gcd是一种与block有关的技术,它提供了对线程的抽象,而这种抽象基于“派发队列”(dispatch queue)。开发者可将block排入队列中,有gcd负责处理所有调度事宜。gcd会根据系统资源情况,适时得创建、复用、摧毁后台线程,以便处理每个队列。此外,使用GCD还可以方便的完成常见编程任务,比如编写“只执行一次的线程安全代码”(thread-safe single-code execution),或者根据可用的系统资源来并发执行多个操作。
block和gcd是当前OC的编程基石。因此必须理解其工作原理及功能
block可以实现闭包。这项语言特性是作为扩展而加入GCC编译器中的,在近期版本的Clang中都可以使用。从技术上讲,这是位于C语言层面的特性,因此只要有支持此特性的编译器以及能执行block的运行期组件,就可以在C、C++、OC,PC++代码中使用它。
下面是block的一个基本写法

void (^someBlock)() = ^{  
    //Block implementation here  
};  

block的强大之处是:在声明它的范围内,所有变量都可以为其所捕获。例如下面代码:

int additional = 5;  
int(^addBlock)(int a ,int b) = ^(int a,int b){  
    return a+b+additional;  
};  
int add = addBlock(2,5);//add = 12  

默认情况下,为block捕获的变量,是不可以修改的。如果在block中需要修改需要使用__block修饰符,修饰变量。

NSArray *array = @[@0,@1,@2,@3,@4,@5];  
__block NSInteger count =0;  
[array enumerateObjectUsingBlock:^(NSNumber* number,NSUInteger idx, BOOLBOOL *stop){  
    if([number compare:@2]==NSOrderAscending){  
        count++;  
    }  
}];  
//count =2  

block中直接使用count的值。如果block捕获的变量是对象类型,那么就会自动保留它。系统在释放这个block的时候也会将其一并释放,block本身也是变量,有引用计数。
block还可以使用self变量。block总能修改实例变量,所以在声明时无需加__block。但是self却被保留了。如果self所指代的哪个对象同时页保留了块,那么这种情况就会导致“循环引用”。
每个OC对象都占据者某个内存区域。block本身也是对象,在存放对象的内存区域中,首个变量是指向Class对象的指针,该指针也叫isa。其余内存里含有block对象正常运转所需的各种信息,block对象的内部实现参考:点击打开链接 如图:

Paste_Image.png

在内存布局中,最重要的是invoke变量,这是个函数指针,指向block的实现代码。descriptor变量是指向结构体的指针,每个block都包含了此结构体,其中声明了block对象的总体大小,还声明了copy与dispose两个辅助番薯所对应的函数指针。前者是保留捕获的对象,后者则将之释放。
block还会把它所捕获的所有变量都拷贝一份。这些拷贝放到descriptor变量后面,捕获了多少对象,就要占据多少内存。请注意,拷贝的并不是对象本身,而是指向这些变量的指针变量。invoke函数为何要把block对象作为参数传进来呢?原因就在于,执行block时,要从内存中把这些捕获的变量读出来。
全局block,栈block,堆block
定义block的时候,是分配在栈上的,block只在定义它的范围有效。下面的写法很危险:

void (^block)();  
if(/*some condition*/){  
    block = ^{NSLog(@"Block A");};  
}else {  
    block = ^{NSLog(@"Block B");};  
}  
block();  

定义在if-else中的两个block都是在栈上的,作用范围只限于两个大括号之内。所以上述可能运行正确,可能错误。解决这个问题的办法是给block对象发送copy消息。这样就可以把block复制到堆上了。修改后代码:

void (^block)();  
if(/*some condition*/){  
    block = [^{NSLog(@"Block A");} copy];  
}else {  
    block = [^{NSLog(@"Block B");}copy];  
}  
block(); 

采用手动计数的,需要将其release,采用ARC则不用。
除了“栈block”、“堆block”之外,还有一类block叫做“全局Block”。这种block不会捕捉任何状态(比如外围的变量等),运行时也无需有状态来参与。block所需要的整个内存区域,在编译期已经完全确定了,因此,全局block可以声明在全局内存里,而不需要在每次用到的时候于栈中创建。另外,全局block的拷贝操作是一个空操作,因为全局block绝不可能为系统回收。这种block相当于单例。
更多block存储区域参考点击打开链接
【本节要点】
● block是C、C++、OC中的词法闭包
● block可以接收参数、也可以返回值
● block可以在栈上、堆上、全局。分配在栈上的block可以拷贝到堆上。

8.类似于一种语法糖,看起来比较舒服

//before  
-(void)startWithCompletionHandler:(void (^) (NSData *data,NSError *error))completion;  
//after  
typedef void(^EOCCompletionHandler) (NSData *data,NSError *error);  
-(void)startWithCompletionHandler:(EOCCompletionHandler)completion;  

9.没有什么可说的,讲了一下block相对于delegate的优势,摘抄一下总结:
● 在创建对象时,可以使用内联的handler block将相关业务逻辑一并声明。
● 在有多个实例需要监视时,如果采用delegate模式,那么经常需要根据传入的对象来切换。而偌该用handler实现,则可直接将block与相关对象放在一起。
● 涉及API时如果用到了handler block,那么可以增加一个参数,使调用者可通过此参数来决定鹰把block安排在哪个队列上执行。

10.注意循环引用的问题,防止内存泄露
在没有出现block copy的情况下,是不会出现循环引用的。因为:栈上的block,虽然引用了self,构成环形引用,但是,最终栈上的block是需要释放的,这是一个出口。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容