【Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法】总结(一)

2017.03.15

有时候感觉写代码时很迷茫,不知道用的方法好不好(设计模式),所以就找一些关于objective-c 的语法书来看看,把基础打得更加扎实点,无意中发现了这本书,于是,开始研究起来,需要对我以后的代码以及代码风格有帮助。

第一章 :熟悉objective-c

1. objective-c 使用的是“消息结构” 而非“函数调用”。

区别:使用消息结构的语言,其运行时所执行的代码是由运行环境来决定(所以objective-c的运行期很重要,是核心),在运行时动态的动态载入要执行的代码(动态绑定);而函数调用的语言,是由编译器决定,所执行的代码在编译期时就已经决定了,如果时多态,就按“虚方法表”来决定。

2.objective-c的对象类型是保存在上,由一个指针指向这个内存块,指针(32位系统是4字节,64位系统是8字节)保存在上。



3.头文件(header file) 和 实现文件(implementation file) 分离

少引用头文件,能使用@class就使用(“向前声明forward declaring”该类 ),尽量在implementation file 中引用头文件。

a.引用越多,会增加编译的时间。

b.向前声明也解决了两个类相互引用的问题。(解决“循环引用chicken-and-egg situation”)

c.协议最好单独放一个头文件中,如果放在某个大的头文件里,那么只要引入此协议,就必定会引入那个头文件中的全部内容,这样就有可能产生相互依赖问题,还会增加编译时间。

d."委托协议"(delegate protocol)(23条)不用单独放一个头文件,因为此时协议只有和接受协议委托的类放一起才有意义。此时最好能在实现文件中声明此类实现了该委托协议,并把这段实现代码放在分类中实现(class-continuation)(27条)。

4.多用字面量语法,少用与之等价的方法 : 使用字面量语法(literal syntax)可以缩短源代码长度,使其更为易读

NSString* someString = @"effective objective-c 2.0";
NSNumber* intNumber = @1;
NSNumber* floatNumber = @2.5f;
NSNumber* doubleNumber = @3.1415926;
NSNumber* boolNumber = @YES;
NSNumber* charNumber = @'a';

适用于表达式:

int x = 5;
float y = 6.32f;
NSNumber* expressionNumber = @(x * y);

NSArray *animals = @[@"cat",@"dog",@"mouse",@"badger"];
NSString* dog = animals[1];
NSString *personDate = @"firstName" : @"Matt",
@"lastName" : @"Galloway",
@"age" : @28};
NSString *lastName = personDate[@"lastName"];
mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";
缺点:使用字面量语法创建出来的字符串,数组,字典对象都是不可变的。若想要可变版本的对象,则需要复制一份:

NSMutableArray* mutable = [@[@1, @2, @3, @4, @5] mubaleCopy];

5.多用类型常量,少用#define预处理指令:定义类型常量可以另阅读更清晰的知道所对应的类型,方便写开发文档。

static const NSTimeInterval kAnimationDuration = 0.3;
const 可以防止变量被改变。

//in the header file
extern NSString* const EOCStringConstant;
//in the implementation file
NSString* const EOCStringConstant = @"VALUE";
为了避免名称冲突,最好是用与之相关的类型最为前缀。

extern 来声明全局常量,这种常量会出现全局符号表中,提高检索速度。

6.用枚举表示状态,选项,状态码:每种状态都用一个便于理解的值来表示,代码易懂。

enum EOCConnectionState {
EOCConnectionStateDisconnected,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
typedef enum EOCConnectionState EOCConnectionState;

指定底层数据类型所用的语法:(C++11的特性)

enum EOCConnectionState : NSInteger {
/* ··· */
}
或者向前声明时指定底层数据类型:

enum EOCConnectionState : NSInteger;
enum EOCConnectionState {
EOCConnectionStateDisconnected,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};

NS_ENUM的用法,经常用于switch语句里

typedef NS_ENUM(NSUInteger, EOCConnectionState) {
EOCConnectionStateDisconnected,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};

switch (_currentState) {
EOCConnectionStateDisconnected:
break;
EOCConnectionStateConnecting:
break;
EOCConnectionStateConnected:
break;
};

注意,在这switc中,我们不加default分支,因为如果稍后又加了一种状态,那么编译器就会发出警告信息,提示我们处理新加的状态码。
注意,NS_ENUM定义通用枚举,NS_OPTIONS定义位移枚举


第二章:对象,消息,运行期

7.属性的特质:readonly(只读),可以用此特质把某个属性对外公开为只读属性,然后在"class-continuation分类"中将其重新定义为读写属性。
weak:非拥有关系,不保留新值,不释放旧值,特质同assign类似,然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。(ARC可用,MRC不可用,有点可惜了)。
可以通过@property 语法来定义对象中所封装的数据。
8.在对象内部尽量直接访问实例变量:读取实例变量的时候采用直接访问的形式,而在设置实例变量的时候通过属性来做。
直接访问实例变量,不会触发“键值观测Key-Value Observing,KVO”通知,不经过objective-c的“方法派发method dispatch”步骤,提高访问速度。
在初始化方法及dealloc方法中,总是应该直接通过实例变量来读写数据。
在初始化中,若是通过“设置方法”来做,那么将会是子类的设置方法,从而有可能不是期望的值。

9.等同性检测:若想检测对象的等同性,请提供“isEqual:”与hash方法,不要盲目地逐个检测每条属性。

10.以“类族模式 class cluster pattern” 隐藏实现细节:将实现细节隐藏在抽象基类后面,以保持接口简洁

类族模式 可以通过”工厂模式“来实现。

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

12.理解objc_magSend的作用

13.理解消息转发机制

14.用“方法调配技术” 调试“黑盒方法”:在运行期中新增或替换选择子所对应的方法实现。

使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”。

SEL originalSelector = @selector(nextKeyView);
    SEL swizzledSelector = @selector(hook_nextKeyView);
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    if (!originalMethod || !swizzledMethod) {
        return;
    }
    
    IMP originalIMP = method_getImplementation(originalMethod);
    IMP swizzledIMP = method_getImplementation(swizzledMethod);
    const char *originalType = method_getTypeEncoding(originalMethod);
    const char *swizzledType = method_getTypeEncoding(swizzledMethod);
    
    class_replaceMethod(class,swizzledSelector,originalIMP,originalType);
    class_replaceMethod(class,originalSelector,swizzledIMP,swizzledType);
或者:

    SEL originalSelector = @selector(nextKeyView);
    SEL swizzledSelector = @selector(hook_nextKeyView);
    method_exchangeImplementations(originalSelector, swizzledSelector);

一般选择第一种方法,第二种我们需要保证选择子是存在的。

一般来说,只有调试程序的时候才需要在运行期修改方法的实现,这种方法不宜滥用,不能因为oc语言里有这个特性就一定要用他。

15.理解“类对象”的用意:类对象就是某个[类名 class] 返回的对象,用来构成类的继承体系。
每个对象实例都是指向某块内存数据的指针,这些数据不能保存在栈上。
类对象是“单例singleton”的,在应用程序范围内,每个类的class仅有一个实例。

每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。

探知类型信息的方法:

isMemberOfClass:

isKindOfClass:

直接比较两个实例变量的类对象,用“==”比较。


第三章:接口与API设计

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

选择与你的公司,应用程序或者二者皆有关联之名称作为类名的前缀,并在所有代码中使用这一前缀。

若自己所开发的程序库中用到了第三方库,则应为其中的名称加上前缀。

17.提供“全能初始化方法”:可为对象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”。

接口:

//ZMButton
- (void)showBadgeWithAttributeString:(NSAttributedString*)attributeString bgColor:(NSColor*)bgColor borderColor:(NSColor*)borderColor borderWidth:(float)borderWidth corner:(ZMBadgeCorner)corner offsetX:(float)offsetX offsetY:(float) offsetY;
- (void)showBadgeWithString:(NSString*)string bgColor:(NSColor*)bgColor borderColor:(NSColor*)borderColor borderWidth:(float)borderWidth corner :(ZMBadgeCorner)corner offsetX:(float)offsetX offsetY:(float) offsetY;
实现:

- (void)showBadgeWithAttributeString:(NSAttributedString*)attributeString bgColor:(NSColor*)bgColor borderColor:(NSColor*)borderColor borderWidth:(float)borderWidth corner:(ZMBadgeCorner)corner offsetX:(float)offsetX offsetY:(float)offsetY{
    //具体的实现
}
- (void)showBadgeWithString:(NSString*)string bgColor:(NSColor*)bgColor borderColor:(NSColor*)borderColor borderWidth:(float)borderWidth corner:(ZMBadgeCorner)corner offsetX:(float)offsetX offsetY:(float) offsetY{
    if(!string || [string isEqualToString:@""]){
        [self hideBadge];
        return;
    }
    NSAttributedString* attributeString = [[[NSAttributedString alloc] initWithString:string] autorelease];
    [self showBadgeWithAttributeString:attributeString bgColor:bgColor borderColor:borderColor borderWidth:borderWidth corner:corner offsetX:offsetX offsetY:offsetY];
}
我们发现,最终的实现还是通过第一个方法来实现的,所以我们称第一个方法为“全能初始化方法”。

注意:如果子类的全能初始化方法与超类方法的名称不同,那么总应覆写超类的全能初始化方法。

注意:如果子类重写超类的全能初始化方法是没有道理的,那么常用的办法是覆写超类的全能初始化方法并于其中抛出异常:

- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height{
    @throw  [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must be use initWithDimension :instead" userInfo:nil];
}
每个字类的全能初始化方法都应该调用其超类的对应方法,并逐层向上。

在类中提供一个全能初始化方法,并于文档里指明,其他初始化方法均应调用此方法。

18.实现description方法

- (NSString*)description{
return [NSString stringFormat:@"<%@: %p, \"%@ %@\">",[self class], self, _firstName, _lastName];
}
//Output eg:
//<EOCPerson: 0x7fb249c030f0, "Bob Smith">
我们可以借助NSDictionary 类的description方法:在自定义的description方法中,把待打印的信息放到字典里面,然后将字典对象所输出的内容包含在字符串里并返回,这样就可以实现精简的信息输出方式了。
- (NSString*)description{
return [NSString stringWithFormat:@"<%@: %p, %@>",[self class], self, @{@"title":_title,@"latitude":@(_latitude),@"longitude":@(_longitude)}];
}
实现description方法返回一个有意义的字符串,用以描述该实例。

若想在调试时打印出更详尽的对象描述信息,则应该实现debugDescription方法。

19.尽量使用不可变对象

设计类的时候,应充分运用属性来封装数据。

把不让外人所改动的变量设置为readonly。如果自己想修改封装在对象内部的数据,这时候可以在分类中将对象的readonly属性重新声明为readwrite。

注意:在公共接口中声明的属性可于分类中重新声明,属性的其他特质必须保持不变,而readonly可扩展为readwrite。
尽量使用不可变对象,如果使用了可变的对象,那么有可能有多个变量拥有这个对象,当某一个变量对其进行修改时,那么这个拥有这个对象的变量都会受到影响。当然有些情况下我们就是想要这样的效果,我们可以合理的使用可变的对象。

20.使用清晰而协调的命名方式

起名时应遵从标准的Objective-C 命名规范,这样创建出来的接口更容易为开发者所理解。

方法名要言简意赅,从左至右读起来要像个日常用语中的句子才好。

方法名里不要使用缩略后的类型名称。

给方法起名时的第一要务就是确保其风格与你自己的代码或所要继承的框架相符。

21.为私有方法名加前缀:容易把公共方法和私有方法区别开

具体使用何种前缀可根据个人喜好来定,其中最好包含下划线与字母p,可以考虑用”p_“。

eg:- (void)p_privateMethod;

注意:不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的。

22.理解Objective-C 的错误模型

@throw [NSException exceptionWithName:@"ExceptionName"
reason:@"there was an error"
userInfo:nil]



23 

28.使用“class-continuation分类” 隐藏实现细节:类中经常包含一些无须对外公布的方法及实例变量。

为什么需要这种分类呢?因为其中可以定义方法和实例变量。



第四章:协议与分类
第五章:内存管理
第六章:块与大中枢派发(Grand Central Dispatch, GCD)
第七章:系统框架

53.NSTimer 会保留其目标对象




                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值