数据iOS Developer首页投稿(暂停使用,暂停投稿)

《Effective Objective-C 2.0》第二份读书

2017-04-13  本文已影响103人  iSuAbner

声明:这个笔记的系列是我每天早上打开电脑第一件做的事情,当然使用的时间也不是很多因为还有其他的事情去做,虽然吧自己买了纸质的书但是做笔记和看的时候基本都是看的电子版本,一共52个Tip每一个Tip的要点我是完全誊写下来的,害怕自己说的不明白所以就誊写也算是加强记忆,我会持续修改把自己未来遇到的所有相关的点都加进去,最后希望读者尊重原著,购买正版书籍。PS:不要打赏要喜欢~

GitHub代码网址,大大们给个鼓励Star啊。

整个系列笔记目录

《Effective Objective-C 2.0》第一份读书笔记
《Effective Objective-C 2.0》第二份读书笔记
《Effective Objective-C 2.0》第三份读书笔记

第四章 协议与分类

23.通过委托与数据源协议进行对象间通信

我举一个~ 获取网络数据的类含有一个“委托对象”,在获取完数据之后,它会回调这个委托对象。

数据结束委托通知示意图.png

EOCDataModel对象就是EOCNetworkFetcher的委托对象。EOCDataModel请求EOCNetworkFetcher“以异步方式执行一项任务”,而EOCNetworkFetcher在执行完这项任务之后,就会通知其委托对象,也就是EOCDataModel。

这里面代理要用weak修饰。通常情况下,因为代理delegate要对比TableView做相应的操作,所以代理delegate要持有TableView这个对象,而如果我们用Strong修饰TableView的delegate属性,就会引入保留环(retain cycle)。

如果要在委托对象上调用可选方法,那么必须提前使用类型信息查询方法判断这个委托对象是否响应相关选择子。

NSData * data ;
if([_delegate  respondsToSelector:@selector(networkFetcher: didReceiveData:)]){
    [_delegate  networkFetcher: didReceiveData:];
}

委托模式: 对象把应该对某个行为的责任委托给另一个类。
以TableView为例子
委托模式是信息从类流向受委托者也就是让这个责任流向了受委托者。
数据源模式(Data Source Pattern)是数据流向TableView,决定TableView的布局。

写一个现实中的例子吧。(通过协议代理哈,我知道这个方案更好的方案,只不过想写出来下协议代理的步骤)。(详细见第二十三条Demo)。
加入我想通过一个类开启定时器,然后另一个类来监控这个类的定时器,当这个定时器开启5S之后,相应的做一些事情,首先是被监视定时器的那个类:

//.h
#import <Foundation/Foundation.h>
@class Thirtyeight;
//首先是协议代理方式
typedef void(^iSuCompletionHandle)(int five);
@protocol iSuNetworkFetcherDelegate <NSObject>

- (void)netwrokFecher:(Thirtyeight *)networkFetcher didFinishWithData:(int)five;

@end
@interface Thirtyeight : NSObject

@property (nonatomic,weak) id<iSuNetworkFetcherDelegate> delegate;
@property (nonatomic,strong) NSTimer * iSuTimer;
@property (nonatomic,assign)  int iSuNumber;
@property (nonatomic,copy) iSuCompletionHandle iSuCompletion;
- (void)TimerTest;
@end
//.m
@implementation Thirtyeight
- (void)TimerTest{
    self.iSuNumber = 0;
    
  //这个Block我写着玩的,实际作用不大,只不过下面有一个关于block的问题。
    __weak typeof(self) weakSelf = self;
    self.iSuCompletion = ^(int five) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        strongSelf.iSuNumber = five;
        NSLog(@"赋值给self.iSuNumber为%i",strongSelf.iSuNumber);
    };
    
    
    __block int num = 0;
    _iSuTimer =[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        num++;
        NSLog(@"定时器里面的变化%d",num);

/*
这边就是最重要的代码了,也就是传递给监听对象数据。
*/
        [strongSelf.delegate netwrokFecher:strongSelf didFinishWithData:num];
        if (num == 5) {
            
            strongSelf.iSuNumber = num;
            NSLog(@"现在的数值为:%d",strongSelf.iSuNumber);
//这边调用block的时候,是不允许我们使用strong.iSuCompletion这样的操作。
            _iSuCompletion(num);
            [_iSuTimer invalidate];
        };
    }];
}

主动监听类:

//.m
@implementation ThirtyeightViewController
interface ThirtyeightViewController ()<iSuNetworkFetcherDelegate>

- (void)netwrokFecher:(Thirtyeight *)networkFetcher didFinishWithData:(int)five{
//这样就可以监听了。
    NSLog(@"现在行走的时间是多少:%d",five);
}
@end
要点:

24.将类的实现代码分散到便于管理的数个分类之中

分类功能是对类对相应功能的整理,使得整个类的条理更加清晰,防止更多不需要的方法在头文件中导入,影响系统性能。
例如:

//.h
#import <Foundation/Foundation.h>

@interface EOCBengi : NSObject
@property (nonatomic, copy, readonly) NSString * firstName;
@property (nonatomic, copy, readonly) NSString * lastName;
@property (nonatomic, strong, readonly) NSArray * friends;

- (id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
@end

@interface EOCBengi(Friendship)

- (void)addFriend:(EOCBengi *)person;
- (void)removeFirend:(EOCBengi *)person;
- (BOOL)isFriendWith:(EOCBengi *)person;
@end

@interface EOCBengi(Work)

- (void)performDaysWork;
- (void)takeVacationFromWork;
@end

@interface EOCBengi(Play)

- (void)goToTheCinema;
- (void)goToSportsGame;
 
@end
//.m
#import "EOCBengi.h"

@implementation EOCBengi

@end

@implementation EOCBengi(Friendship)

@end

@implementation EOCBengi(Work)

@end

@implementation EOCBengi(Play)

@end

要点:

25.总是为第三方类的分类名称加前缀

分类机制通常用于向无源码的既有类中新增功能。

要点:

26.勿在分类中声明属性

尽管在技术上讲,分类中也是可以声明属性的,但这种做法还是要尽量避免的。原因在于,除了"class-continuation分类"之外,其他分类都无法向类中新增实例变量,因此,它们无法把实现属性所需的实例变量合成出来。

如果在分类中声明了属性,我们可以通过运行期关联对象的方式get set数值,但是这样容易引起内存问题。因为我们在为属性实现存取方法时,经常会忘记遵守从内存管理语义,那么有可能在不经意之间就造成了内存出现错误。(当然这个错误是自己可以解决的)。

#import <objc/runtime.h>
static const char * kFriendsPropertyKey = “kFriendPropertyKey”
@implemetation EOCPerson(Friendship)

(NSArray*)friends(
     return objc_getAssociatedObject(self, kFriendsPropertyKey);
)

(void)setFriends:(NSArray*)friends{
      objc_setAssociatedObject(self , kFriendsPropertyKey , friends,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
要点

27.使用“class- continuation分类“ 隐藏实现细节

编写的准则是

  @interface EOCPerson ()
  //Methods here
   @end 

这个就是正常的我们创建的.m上面的东西,就是class- continuation类的延续。

要点:

28.通过协议提供匿名对象

匿名对象:以内联形式所创建出来的无名类。
这个匿名对象具体表象就是:

@property (nonatomic ,weak) id <EOCDelegate> delegate;

由于该属性类型是id<EOCDelegate>,所以实际上任何类的对象都能充当这一属性,即使这个类不集成与NSObject也是可以的,只要遵守协议<EOCDelegate>就好了。相同例子还有字典的存储,我们知道字典对于key是copy而对于值为保留的:

- (void)setObject:(id)object forKey:(id<NSCopying>)key;
要点:

第五章 内存管理

29.理解引用计数

在引用计数的架构下,每个对象都一个计数器,NSObject协议声明了下面三个方法用于操作计数器,以递增或递减其值。

Retain 递增保留计数
release 递减保留计数
autorelease 待稍后清理“自动释放池(autorelease pool)”时,再递减保留计数。

NSMutableArray  * array = [NSMutableArray alloc] init];
NSNumber * number =[NSNumber alloc] initWithInt:1223];
[array  addObject:number];
[number release];
[array release];

如果我们不走[array release]方法,那么我们知道number对象还是会存在的,因为数组还在持有他,但是绝对不应该假设对象一定存在,也就是说,不要这样写代码:

NSNumber * number = [NSNumber alloc] initWithInt:1337];
[array addObject:number];
[number release];
NSLog(@“ number = %@”,number);

如果我们调用了release之后,基于某些原因,其保留计数可能降至0.
为了避免在不经意间使用了无效对象,一般调用玩realse之后都会清空指针。这就保证了不会出现可能指向无效对象的指针。这种指针通常称为悬挂指针。比如:

NSNumber * number = [NSNumber  alloc] initWithInt:1337];
[array  addObject:number];
[number release];
number = nil;

不管是数组,其他的对象也可以保留别的对象,这一般都是用过“属性”来实现。会用到相关实例变量的获取方法及设置方法。若属性为“strong关系(string relationship)”,那么设置的属性就会保留,比方说,有一个属性为foo

 - (void)setFoo:(id ) foo {
     [foo retain];
     [_foo release];
     _foo = foo;
}

此方法将保留新值并释放旧值,然后更新实例变量,令其指向新值。顺序很重要。加入还未保留新值就先将旧值释放了,而且两个值又指向同一个对象,那么,先执行的release操作就可能导致喜用将对象永久回收。而后续的retain操作则无法令这个已经彻底回收的对象复生,于是实例变量就变成了悬挂指针。

自动释放池
调用release会立刻递减对象的保留计数,然而有些时候不能可以不调用release,改调用autorelease,此方法会在稍后递减计数,通常是在下一次“事件循环(event loop)时递减,不过也可能执行的更早些”。
此特性很有用,尤其在方法中返回对象时更应该用它,在这种情况下:

(NSString*)stringValue{
    NSString * str =[NSString alloc] initWithFormat:@“I am this:%@,self”];
     return str;
}

这个时候返回的str对象比期望的要多1 因为调用 alloc 会加1,但是不会有对应的释放操作,但是不能在方法内部释放 str,否则没等方法返回系统就把该方法回收了。这里应该用autorelease,它会稍后释放对象,从而给调用者留下足够长的时间,使其可以在需要时先保留返回数值,换句话就是保证对象在跨越“方法调用边界(method call boundary)”后一定存活。

(NSString *)stringValue{
    NSString * str =[NSString  alloc] initWithFormat:@“I am this %@”,self];
    return [str autorelease];
}

保留环(retain cycle)
就是相互持有

要点:

30. 以ARC简化引用计数

Clang编译器自带一个“静态分析器(static analyzer)” 。用于指明程序里引用计数出问题的地方。

由于ARC会自动执行retain,release,autorelease等操作,所以不能直接调用retain,release,autorelease,dealloc 。
将内存管理语义在方法命中表示出来早就成了OC的惯例,而ARC则将之确立为硬性规定。这些规则简单的体现在方法名上。若方法名以下列语句开头,则其返回的对象鬼调用者所有
alloc ,new ,copy ,mutableCopy
对于ARC和MRC的转换:
ARC:

_myPerson = [EOCPerson  personWithName:@“Bob Smith”];

MRC:

EOCPerson * tmp = [EOCPerson  personWithName:@“Bob Smith”];
_myPerson = [tmp retain];

ARC可以在运行期检测到这一对多余的操作,也就是autorelease及紧跟其后的retain。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。此时不直接调用对象的autorelease方法,而是改用调用objc_autoreleaseReturnValue。此函数会检视当前方法之后即将要执行的那段代码。若发现那段代码要在返回的对象上执行retain操作,则设置全局数据结构中的一个标志位,而不执行autorelease操作。

如果返回一个自动释放的对象,而调用方法的代码中保留此对象,那么此时不执行retain,而改成objc_retainAutoreleasedReturnValue函数。此函数要检测刚才提到的那个标志位,若已经执行retain操作。设置并检测标志位。要比调用autorelease和retain更快。

要点:

31.在dealloc方法中释放引用并解除监听

如果手动管理引用计数的话 dealloc中需要调用 [super dealloc] 而ARC中则不需要调用[super dealloc]。

要点:

32.编写“异常安全代码”时留意内存管理问题

OC的错误模型表示,异常只有在发生了严重错误(21条详解)的时候才会被抛出。
这个Tip里面我们研究MRC情况下怎么实现异常处理内存,就像C++那样的。
我们使用try块实现这个功能:
@finally的作用在于无论是否抛出异常都会走这一步,因为我们不清楚程序会不会在dosomeThing中抛出异常,如果抛出异常可能会影响下面的动作。

    @try {
        ThirtyTwo * object =[[ThirtyTwo alloc]init];
        [object dosomeThing];
        // [object release];
    } @catch (NSException *exception) {
        NSLog(@"抛出异常");
    } @finally {
        // [object release];
    }
要点:

33.以弱引用避免保留环

强引用:

#import <Foundation/Foundation.h>

@class EOCClassA;
@class EOCClassB;

@interface EOCClassA :NSObject
@property (nonatomic, strong) EOCClassB * other;
@end 

@interface EOCClassB :NSObject
@porperty (nonatomic, strong) EOCClassA * other;
@end

避免保留环的最佳方式就是弱引用,用weak或者是unsafe_unretained即可。

要点:

34.以“自动释放池块”降低内存峰值

创建自动释放池的方法如下:

@autoreleasepool {

}

当数据量过大的时候我们可以通过嵌套自动释放池的方法来降低内存峰值。

NSArray * databaseRecorde = /******/;
NSMutableArray * people = [NSMutableArray  new];
for (NSDictionary * record  in  databaseRecords){
  
     @autoreleasepool {
              EOCPerson  * person = [[EOCPerson alloc] initWithRecord:record]; 
               [person  addObject: person];
         }
}

自动释放池机制就像“栈(stack)”一样。系统创建好自动释放池之后,就将其推入栈中,而清空自动释放池,则相当于将其从栈中弹出。在对象上执行自动释放操作,就等于将其放入栈顶的那个池里。

之前MRC的时候有一个创建释放池的类NSAutoreleasePool 此对象更加重量级(heavyweight) 通常用来创建偶尔需要清空的池,比方说:

NSArray * databasRecode = /****/
NSMutableArray  * people = [NSMutableArray new];
int i = 0 ;

NSAutoreleasePool * pool = [NSAutoreleasePool  alloc] init ];
for (NSDictionary  * record  in  databaseRecode ){
        EOCPerson  * person = [EOCPerson  alloc] initWithRecord:record];
        [people  addobject:person];
       
       if (++i  == 10){
       [pool  drain];
          i = 0;
         }
}
[pool  drain];

是否应该用池来优化效率,完全取决于具体的应用程序。首先得监控内存用量,判断其中有没有需要解决的问题,如果没完成这一步,那就别急着优化。尽管自动释放池块的开销不太大,但毕竟还是有的,所以尽量不要建立额外的自动释放池。

要点:

35.用“僵尸对象”调用内存管理问题

僵尸对象:启动这项调试成功之后,运行期系统会把所有已经回收的实例转化为特殊的“僵尸对象(Zombie Object)”,而不会真正回收他们。这样就使得我们能够及时定位到错误位置。
打开全局断点和僵尸对象调试功能有两种方法:
第一种方法:

僵尸对象第一种生成方法.png

点击"+"选择Exception Breakpoint。

第二种方法:
点击上方调试开关右边的程序管理台,选择Edit Scheme。

Edit Scheme位置.png

然后选择Run - > Diagnostics - > Zombie Objects。

僵尸对象第二种生成方法.png
要点:

36.不要使用retainCount

要点:

结尾

自己写的笔记首先是用Pages写的,写完之后放到简书里面以为也就剩下个排版了,结果发现基本上每一个点的总结都不让自己满意,但是又想早点放上去,总感觉自己被什么追赶着,哈哈,本来写完笔记的时候是2W字的,结果到第二次发表的时候发现就成了2.5W了,需要改进的东西还是太多,希望朋友们有什么改进的提议都可以告诉我,我会一直补充这个笔记,然后抓紧改GitHub上的代码~

上一篇 下一篇

猜你喜欢

热点阅读