iOS开发iOS学习iOS开发资料收集区

Effective Objective-C 2.0 无废话精简篇

2017-08-03  本文已影响679人  鸟人扎墨客
1. 了解 Objective-C 语言的起源

Objective-C 语言使用”消息结构”而非”函数调用”.Objective-C 语言由 Smalltalk演化而来,后者是消息类型语言的鼻祖.编译器甚至不关心接收消息对象的何种类型.接收消息的对象问题也要在运行时处理,其过程叫做”动态绑定”.
Objective-C为 C 语言添加了面向对象特性,是其超类. Objective-C 使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型.接收一条消息后,究竟应执行何种代码,有运行期环境而非编译器决定.理解 C 语言的核心有助于写好 Objective-C 程序.尤其是掌握内存模型与指针.

2. 在类的头文件中尽量少引用其他头文件

Objective-C 语言编写类的标准行为:以类名做文件名,分别闯将两个文件,有文件后缀用. h,实现文件后缀用. m.
在开发中有时候我们会在. h 文件中引入很多用不到的内容,这当然会增加编译时间.除非有必要,否则不要引入头文件,一般来说,某个类的头文件中使用向前声明来体积别的类,并在实现文件中引入哪些类的头文件,这样做可以尽量降低类之间的耦合.有时无法使用前声明,比如要声明某个类遵循意向协议,这种情况下,尽量把 “该类遵循某协议”的这条声明移至 class=continuation 分类中,如果不行的话,就把协议单独存放在一个头文件中,然后将其引入.

3. 多用字面量语法,少用与之等价的方法
NSArray *arr = [NSArray arrayWithObjects:@"num1",@"num2",@"num3", nil];
NSArray *arr = @[@"num1",@"num2",@"num3”];

字面量语法创建字符串,数组,数值,字典.与穿件此类对象的常规方法相比,这么做更加简明扼要.

注意事项:

4. 多用类型常量,少用# deine 预处理指令

不要用预处理指令定义常量,这样定义出来的常量不含类型信息,编译器只会在编译前根据执行查找与替换操作,即使有人重新定义了常量值,编译器也不会产生井道信息,这将导致应用程序常量值不一致.

static NSString *const PersonConstant = @"PersonConstantStr” ;

但是我个人认为其实,还是#define用的多, 开发避免不了使用 pch文件. 如果有强迫症的同学,定义常量就想使用 staitc,extren,const这些关键字.那我建议新建一个专门存放这些常量的类,然后在 pch 中导入这个类.

Person.h
#import <Foundation/Foundation.h>
static NSString *const PersonConstant;
@interface Person : NSObject
@end

Person.m
#import "Person.h"
static NSString *const PersonConstant = @"PersonConstantStr";
@implementation Person
@end

.pch
#define DefineStr @"DefineStr"
#import "Person.h"
#endif
5. 用枚举表示状态,选项,状态码

应该用枚举来表示状态机的状态,传递给方法的选项以及状态码等值,给这些值起个易懂的名字

enum PersonEnum{
    PersonEnumNum1,
    PersonEnumNum2,
    PersonEnumNum3,
};
typedef enum PersonEnum PersonState;
6. 理解属性这一概念

属性是 Objective-C 的一项特性,用于封存对象中的数据.
属性特质:原子性 读写权限
内存管理语义

7. 在对象内部尽量直接访问实例变量

直接访问实例变量的速度比较快,因为不经过 Objective-C 方法派发,编译器所生成的代码会直接访问保存催下实例量的那块内存.
直接访问实例变量时,不会调用设置方法,这就绕过了相关属性所定义的内存管理语义.

8. 理解"对象等同性”这一概念

根据等同性来比较对象是一个非常有用的功能,不过,按照==操作符比较出来的结果未必是我们想要的,因为该操作比较的事两个指针本身,而不是其所指的对象.应该使用 NSObject 协议中的声明的”isEqual”方法来判断两个对象的等同性,一般来说两个类型不同的对象总是不相等的.直接比较字符串的时候 isEqual 比 isEqualToString慢,因为前者还要执行额外步骤.

9. 以"类族模式"隐藏实现细节

“类族”是一种很用用的模式,可以隐藏抽象基类背后实现的细节. 这是一种”工厂模式”.比如iOS 用户界面框架 UIKit 中就有一个名为 UIButton 的类.想创建按钮,需要调用下面这个类方法.
+(UIButton*)buttonWithType:(UIButtonType)type;
我到是认为,作者想告诉我我们要好好封装代码,这个地方没啥好说的.

10. 在既有类中使用关联对象存放自定义数据

有时需要在对象中存放相关信息,这是我们通常会从对象所属的类中继承一个子类,然后改用这个子类对象.然而并非所有情况下都这么做,有时候类的实例可能是由某种机制所创建的,而开发者无法令这种机制创建出自己所写的实例. Objective-C 中有意向强大的特性可以解决问题,这就是关联对象.

11. 理解objc_msgSend的作用

用Objetive-C的术语来说,这叫做“消息传递”。这里说的是运行时。

12. 理解消息转发机制

当对象接收到无法解读的消息后,就会启动消息转发机制,程序员可经此过程告诉对象应该图和处理未知消息。这里说的是运行时。

13. 用方法调配技术调试黑盒方法

运行期间,可以向类中新增或替换选择子所对应的方法实现。
使用另一份实现来替换原有的方法实现,这道工序叫做方法调配,开发者常用此技术想原有实现中添加新功能。
一般来说,只有调试程序的时候才需要运行期修改方法实现,这种做法不易滥用。这里说的是运行时。

14. 理解类对象的用意

每个Objective-C对象实例都是指向某块内存数据的指针,如果把对象所需的内存分配到栈上编译器就会报错
每个对象结构体的首个成员是Class类的变量,该变量定义了对象所属的类,通常称为isa指针。

typedef struct objc_class *class
struct objc_class{
    Class isa;
    Class super_class;
    const char* name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list *ivars;
    struct objc_cache *cache;
    struct objc_protocol_list protocols;
}

此结构体存放类的元数据,例如类的实例实现了几个方法,具备多少个实例变量等信息。次结构体的首个变量也是isa指针,这说明Class本身亦为Objctive-C对象。结构体里还有个变量叫做super_class,它定义本类的超类,类对象所属的类型(isa指针所指向的类型)是另外一个类,叫做元类,用来标书类本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法,每个类仅有一个类对象,每个类对象仅有一个与之相关的元类。 (元数据,就是这个类的数据。)
isKindOfClass:能够判断对象是否为某类或其派生类的实例
isMemberOfClass: 能够判断出对象是否为某个特定类的实例

15. 用前缀避免命名空间冲突

这个没啥可说的

16. 提供全能初始化方法

这个没啥可说的

17. 实现description方法

调试程序时经常需要打印并查看对象信息。description 很实用。

Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,assign)int age;
@property(nonatomic ,copy)NSString* name;
@end

Person.m
#import "Person.h"
@implementation Person
- (NSString *)description
{
    return [NSString stringWithFormat:@"name %@ , age %d", self.name, self.age];
}
@end

description写出想要的属性, 在ViewController打印 Person 对象 Person的相关属性就会出现在控制台。

18. 尽量使用不可变对象

这个没啥可说的

19. 使用清晰而协调的全名方式

这个没啥可说的

20. 为私有方法名加前缀

这个没啥可说的

21. 理解Objective-C错误模型

NSError的用法更加灵活,因此经由此对象,我们可以把导致错误的原因汇报给调用者。

@try {
        NSString *str = @"wotaxiwa";
        NSString *strErr = [str substringFromIndex:100];
        NSLog(@"%@",str);
    } @catch (NSException *exception) {
       
        NSLog(@"ERROR:     %@",exception);
    } @finally {
        NSLog(@"%s",__func__);
    }

如果出现exception,异常后面的代码将不会继续执行

22. 理解NSCopying协议

copy mutableCopy 我认为就是原型设计模式不明白的同学请看这个网页

涉及到深拷贝和浅拷贝的知识不明白的同学请看这篇博客

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

这一条说的就是delegate(代理设计模式)。但是并没有说delegate的循环引用的问题,在使用代理声明一个 @property的时候,记得用weak。

@protocol PersonDelegate <NSObject>
-(void)areYouPerson;
@end

@property (nonatomic,weak)id<PersonDelegate> pd;
24. 将类的实现代码分散到便于管理的数个分类之中

这个没啥可说的

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

这个没啥可说的

26. 勿在分类中声明属性

正常的分类是不可以声明属性的,但是从技术上说,分类里可以用runtime声明属性。

#import <objc.runtime.h>
static const char *kFriendsPropertyKey = “kFriendsPropertyKey”;
@implementation EOCPerson(Friendship)
-(NSArray*)friends{
    return objc_getAssociatedObject(self,kFriendsPropertyKey);
}

-(void)setFriends:(NSArray*)friends{
    objc_setAssociateObject(self.kFriendsPropertyKey,friends,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

这样做可行,但是不太理想,要吧相似的代码写很多遍。而且容易出现Bug,可以使用class-continuation实现分类添加属性。

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

class-continuation分类和普通的分类不同,它必须在其所接续的那个类的实现文件里。其重要之处在于,这是唯一能生命实例变量的分类,而且此分类没有特定的实现文件,其中的方法应该定义在类的主实现文件里。与其他分类不用,“class-continuation分类”没有名字,比如,有个类叫做EOCPerson,其“class-continuation分类”写法如下:

@interface EOCPerson()
@end

我们在创建一个类的时候系统已经自动帮我们在,m中实现了,以下代码都应该很熟悉吧。

#import "ViewController.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
  
}
@end

没错它就是 class-continuation分类,在此代码之间可以添加属性,修改属性。

@interface ViewController ()
@end

使用class-continuation分类的好处

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

这个没啥可说的
说的就是这句话

@property (nonatomic,weak)id<PersonDelegate> pd;
29. 理解引用计数

理解引用计数,方便于了解iOS的内存管理。不过现在都是ARC的时代了。
引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后,其保留计数至少为1。若保留计数为正,则对象继续存活。当保存计数降为0,对象就被销毁了。
在对象生命期中,其余对象通过引用来保留或释放此对象。保留与释放操作分别会递增及递减保留计数。

30. 以ARC简化引用计数

使用ARC要计数,引用计数实际上还是要执行的,只不过保留与释放操作现在由ARC自动为你添加。由于ARC会自动执行retain、release、autorelease等操作,所以直接在ARC下调用这些内存管理方法是非法的。
ARC在调用这些方法时,并不用过普通的Objective-C消息派发机制,二十直接调用其底层C语言版本,这样做性能更好,直接调用底层函数节省很多CPU周期。
虽然有了ARC之后无需担心内存管理问题,但是CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFRelease.

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

当一个对象销毁的时候会调用dealloc方法,但是当开销较大或系统内稀缺资源则不再此列,像是文件描述、套接字、大块内存等都属于这种资源,通常对于开销较大的资源实现一个方法,当程序用完资源对象后,就调用此方法。这样一来,资源对象的生命期就变得明确了。

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

图不重要看字。
图片中的情景是MRC。现在已经是ARC时代了。但是在使用@try 的时候也要注意,在捕获到异常的时候@try{}中的语句执行到异常代码的那一行后不在执行,然后把异常抛给@catch。当然@finally是一定要执行的。
如下 并没有执行代码中的NSLog(@"%@",array);

    @try {
        NSArray *array = @[@"a",@"b",@"c"];
        NSString *str_arr = array[4];
        NSLog(@"%@",array);

    } @catch (NSException *exception) {
        NSLog(@"%@",exception);
    } @finally {
        NSLog(@"%s",__func__);
    }
33. 以弱引用避免保留环
34. 以“自动释放池块”降低内存峰值
    @autoreleasepool {
        <#statements#>
    }

    for (int i = 0; i < 1000000; i++) {
        @autoreleasepool {
            NSNumber *num = [NSNumber numberWithInt:i];
            NSString *str = [NSString stringWithFormat:@"%d ", i];
            [NSString stringWithFormat:@"%@%@", num, str];
           
            if(lagerNum-1 == i)
            {
                NSLog(@"end");
            }
        }
    }


由此可见我们合理运用自动释放池,可降低应用程序的内存峰值。

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

向已回收的对象发送消息是不安全的。
在左上角标题栏找到项目单击后选择 Edit scheme 勾选图中检测僵尸对象


36. 不要使用retainCount

这个没啥可说的
MRC时代的产物,忽略

37. 理解”块“这一概念

这里其实就是在说block,复习一下block的语法吧
返回值类型(block名称)(参数)
需要注意的是 定义block时候,其所占内存区域是分配在栈中的,快只在定义它的那个范围内有效。block所使用的整个内存区域,在编译期已经完全确定,因此,全局block可以生命在全局内存里,而不需要在每次用到的时候于栈中创建,另外,全局block的拷贝是个空操作,因为全局block绝不可能为系统所回收,这种block实际上相当于单例。

38. 为常用的块类型创建typedef

这个没啥可说的

typedef <#returnType#>(^<#name#>)(<#arguments#>);
@property (nonatomic,copy)name nm_blk;
39. 用handler块降低代码分散程度

这条书上说的就是block的回调。只不过是把block放在方法中去使用。

//  Person.h
#import <Foundation/Foundation.h>
typedef void(^Blk)(NSString *name,int age);
@interface Person : NSObject
-(void)handler:(Blk)blk;
@end

//  Person.m
#import "Person.h
@implementation Person
-(void)handler:(Blk)blk{
    if(blk){
        blk(@"zhangsan" ,28);
    }
}
@end

//  使用
Person *per =[Person new];
[per handler:^(NSString *name, int age) {
     NSLog(@"%@ %d",name, age);
}];

这样使用的好处是 在两个对象通信的时候可以不使用delegate,方便了代码的管理。其实这样的用法很常见,多用于封装网络请求的基类。

40. 用块引用其所属对象时不要出现保留环

这个没啥可说的

41. 多用派发队列,少用同步锁

派发队列可用来表述同步语义,这种做法比使用@synchronize块或NSLock对象更简单
将同步与异步派发结合起来,可以实现与普通枷锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程
使用同步队列及栅栏块,可以令同步行为更加高效(不常用)

42. 多用GCD,少用performSelector系列方法

这个没啥可说的

43. 掌握GCD及操作队列的适用时机

这个没啥可说的
解决多线程与任务管理问题时,派发队列并非唯一方案
操作队列提供了一套高层的Objective-C API 能实现纯GCD所具备的绝大部分功能,而且还完成一些更为复杂的操作,那些操作弱改用GCD来实现,则需另外编写代码。
使用NSOperation对线程管理

44. 通过Dispatch Group机制,根据系统资源状况来执行任务

这个没啥可说的
一系列任务可归入一个dispatch group之中。开发者可以在这组任务执行完毕时获得通知
通过dispatch group,可以在并发式派发队列里同时执行多项任务。此时GCD会根据系统西苑状况来调度这些并发执行的任务。开发者若自己来实现此功能。则需要便携大量代码。

45. 使用dispatch_once来执行秩序运行一次的线程安全代码
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
        <#code to be executed once#>
});
46.不用使用dispatch_get_current_queue

这个没啥可说的
iOS系统6.0版本起,已经正式启用此函数了

__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_6,__MAC_10_9,__IPHONE_4_0,__IPHONE_6_0)
DISPATCH_EXPORT DISPATCH_PURE DISPATCH_WARN_RESULT DISPATCH_NOTHROW
dispatch_queue_t
dispatch_get_current_queue(void);


47.熟悉系统框架

打开Xcode command + shift + 0 选择性的了解一些 Foundation、UIKit
也可以看看这篇博客 http://www.jianshu.com/p/58bc11c800e4

48. 多用枚举,少用for循环

因为枚举遍历的时候用的多线程(GCD并发执行),所以效率更快些。我觉得其实用什么都行。

NSArray *arr = @[@"b",@"c",@"s"];
[arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
 }];
49. 对自定义其内存管理语义的collection使用无缝桥接
NSArray *anNSArray = @[@1,@3,@5,@8];
CFArrayRef acFArray =  (__bridge CFArrayRef)anNSArray;
NSLog(@"%@",acFArray);

通过无缝桥接技术,可以在Foundation框架中Objective-C对象与CoreFoundation框架中的C语言数据结构之间来回转换。
在CoreFoundation层面穿件collection时,可以指定许多回调函数,这些函数表示此collection应如何处理其元素,然后可运用无缝桥接技术,将其转换成具备特殊内存管理语义的Objective-C collection。

50. 构建缓存时选用NSCache而非NSDictionary

NSCache胜过NSDictionary之处在于,当系统资源耗尽时,它能自动删减缓存。

51. 精简Initialize与load的实现代码

类初始化的时候一定会调用两个方法

+(void)load{}

+ (void)initialize
{
    if (self == [<#ClassName#> class]) {
        <#statements#>
    }
}
52. 别忘了NSTimer会保留其目标对象
//  Person.h
#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic,strong)NSTimer *timer;
-(void)start;
-(void)stop;
@end

//  Person.m
#import "Person.h"
@implementation Person

-(void)start{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(run) userInfo:nil repeats:YES];
}

-(void)run{
    NSLog(@"%s",__func__);
}

-(void)dealloc{
    NSLog(@"%s",__func__);
}

-(void)stop{
    [self.timer invalidate];
}
@end


调用
@property (nonatomic,strong)Person *person;

self.person = [Person new];
[self.person start];


-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self.person stop];
}

好,那么问题来了,是不是没有调用dealloc方法,没有调用dealloc方法就说明Person对象并没有被销毁,为什么没有被销毁

因为在控制器强引用了self.person,[self.person start]强引用了 self.timer; self.timer 的target指向了self(self.person)所以循环引用了。


怎么解决。 NSTimer销毁的时候,把Person对象为nil即可

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self.person stop];
    self.person = nil;
}
上一篇 下一篇

猜你喜欢

热点阅读