[摘要]Effective Objective-C 2.0(一)

在类的头文件尽量少引入其他头文件

原因:

假如你在头文件A中引入了B、C…等其他头文件,那么当你引入头文件A时,就需要引入B、C…等头文件,提高了类之间的耦合度,增加了编译时间

处理:
  • 尽量不要引入头文件。一般来说,应该在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件

    向前声明:提前告诉编译器后面有这个类的声明,在C++中避免相互引用,格式如:@class MyClass;

  • 有时无法使用向前声明,比如要声明某个类遵循一项协议:1、尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中;2、把该协议单独放在一个头文件中,然后将其引入

多用字面量语法,少用与之等价的方法

原因
  • 可以缩减源代码的长度,使其更为易读
  • 字面量会比方法更加安全,如下:

    id obj1 = @"dog";
    id obj2 = @"bird";
    id obj3 = @"cat";
    
    NSArray *arrA = [NSArray arrayWithObjects:obj1, obj2, obj3, nil];
    NSArray *arrB = @[obj1, obj2, obj3];
    
    //假如obj2为nil,字面量遇到值为nil的对象会抛出异常,而方法会直接生成arrA,但是[arrA count]为1, [arrA objectAtIndex:2]会抛出异常。因此前者更安全。
处理
```
//字面量的声明方式
NSString *someString = @"hello world";

NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.1415926;
NSNumber *charNumber = @'a';
NSNumber *boolNumber = @YES;

int a = 3, b = 4;
NSNumber *expressionNumber = @(a * b);

NSArray *animals = @[@"tiger", @"dog", @"cat"];
NSString *str = animals[2];

NSMutableArray *animals = [@[@"tiger", @"dog", @"cat"] mutableCopy];

NSDictionary *personData = @{@"firstName":@"Matt",
                             @"lastName":@"Holly",
                             @"age":@32}
```

多用类型敞亮,少用#define预处理命令

处理
  • 不要用预处理指令定义常量。因为这样定义出来的常量不含类型信息,编译器只会在编译前执行查找与替换的操作。即使有人重定义了常量值,编译器也不会发出警告,这回导致应用程序的常量值不一致。
  • 在实现文件中使用static const来定义“只在编译单元可见的常量”,无需加前缀。

    
    #define ANIMATION_DURATION 0.3;
    
    static const NSTimeInterval kAnimationDuration = 0.3;
    //显然后者描述更加清晰
  • 在头文件中使用extern来声明全局变量,并在实现文件定义其值,这种常量会出现在全局符号表中,所以应该添加前缀。

    //in head file
    extern const NSTimeInterval SJAnimationDuration;
    
    //in implementation file
    const NSTimeInterval SJAnimationDuration = 0.3;

用枚举表示状态、选项、状态码

处理
  • 如果把传递给某个方法的选项表示为枚举类型,二多个选项又可同时使用,那就把哥哥选项值定义为2的幂,以便通过按位或操作将其组合起来。

    typedef NS_OPTIONS(NSInteger, SJDirection){
        SJDirectionUp = 1 << 0;
        SJDirectionDown = 1 << 1;
        SJDirectionLeft = 1 << 2;
        SJDirectionRight = 1 << 3;
    }
  • 用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层的数据类型。这样可以确保枚举采用的数据类型是开发者指定的而非编译器选择的。

  • 在处理枚举类型的switch语句中不要事先default分支。这样子的话,假如新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。

属性、特质

属性
  1. @property oc能根据变量名称自动创建存取方法(对外公开存取方法),以及以下划线开头的变量
  2. @sythesize 在类的实现代码里面通过@sythesize语法来制定变量的名字,也能生成存取方法(对内存取方法)

    @implementation SJPerson
    @sythesize firstName = _firstName;
    @end
  3. @dynamic 告诉编译器不用自动创建实现属性所用的实例变量,也不要为其创建存取方法

特质
  1. 原子性
    默认情况下是atomic,会使用同步锁,一般情况下变量声明为nonatomic,因为atomic也不能够确保变量线程安全,需要更深层的锁
//atomic
-(void)setPropertyName:(id)object{
    if(_object != object){
        @synchronized(self){//nonatomic不加这层锁
            //默认retain
            [object retain];
            [_object release];
            _object = object;
        }
    }   
}
  1. 读/写权限
    • readwrite(读写) 属性拥有getter与setter方法,若该属性由@sythesize实现,则编译器会自动生成这两个方法
    • readonly(只读) 属性仅拥有getter方法,只有当该属性由@sythesize实现时,编译器才会为其合成获取方法。(一般的做法是在对外公开的头文件中声明为只读,在类或类别中重新定义为读写特质)
  2. 内存管理语义

    • assign:“设置方法”只针对“纯量类型”(CGFloat or NSInteger等)的简单赋值操作,相当于ARC中的weak,引用计数不加1,且释放后不会将值自动置为nil

    • strong :“拥有关系”先保留新值,再释放旧值,最后把新值赋给属性

    -(void)setSJPersonValue:(SJPerson)*SJPersonValue
     {
         if(_SJPersonValue != SJPersonValue){
             [SJPersonValue retain];
             [_SJPersonValue release];
             _SJPersonValue = SJPersonValue;
         }
     }
    • weak:“非拥有关系”

    • unsafe_unretained:语义与assign相同,但是适用于“对象类型”。非拥有(unretained)非安全(属性值不会自动清空)

    • copy “拥有关系” 与strong类似,不会保留新值,而是copy一份赋给属性,NSString属性经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个NSMutableString类的实例。总结来说就是“不可变”属性应该用copy特质修饰。
    -(void)setSJPersonValue:(SJPerson)*SJPersonValue
     {
         if(_SJPersonValue != SJPersonValue){
             SJPerson *tmp = [SJPersonValue copy];
             [_SJPersonValue release];
             _SJPersonValue = tmp;
         }
     }
  3. 方法名

    • getter = < name > 指定“获取方法”的方法名。相当于重新定义getter
    @property (nonatomic, getter = isOn) BOOL on;
    • setter = < name > 指定“设置方法”的方法名。不常见。

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

处理
  • 在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应该通过属性来写。
//前者用“点语法”设置属性值
//后者直接访问获取变量值
self.name = _name;
  • 在初始化方法及dealloc方法中,总是应该直接通过实例变量来读写数据。
-(id)initWithName:(NSString*)name
{
    if(self = [super init])
    {
        _name = [name copy];
    }
    return self;
}
  • 使用惰性初始化技术配置数据,应该通过属性来读取数据。
-(MyBrain)brain{
    if(!brain)
    {
        _brain = [brain new];
    }
    return _brain;
}

对象等同性

处理
  • 检测对象的等同性,需要提供isEqual与hash方法
-(BOOL)isEqual:(id)object
{
    if([self class] == [object class])
    {
        return [self isEqualToString:(NSString*)object];
    }else{
        return [super isEqual:object];
    }
}

-(BOOL)isEqualToString:(NSString*)string
{
    if(![string isKindOfClass:[NSString class]])
    {
        [NSException raise:NSInvalidArgumentException
                    format:@"Object is not instance of NSString"];
    }
    ...
}
  • 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象未必相同

类簇的概念

NSString与NSMutableString相当于类簇

关联对象

见书P39

方法调配

  • 类的方法列表会把选择子的名称映射到相关的方法实现之上,这些方法均以指针IMP方式存在
#import <obj/runtime.h>
//操作选择子与IMP映射表的方法
//交换IMP实现
void method exchangeImplementations(Method m1, Method m2)
//获得方法实例
Method class_getInstanceMethod(Class aClass, SEL aSelector)

类对象

  • 类的对象数据结构定义,每个实例都有isa指针指向他的类,以表明其类型
typedef struct objc_object {
    Class isa;//指向类
} *id;
  • Class类的定义
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 **methodList;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
}

类继承图
- 类型信息查询

NSMutableDictionary *dict = [NSMutableDictionary new];
[dict isMemberOfClass:[NSDictionary Class]];// NO
[dict isMemberOfClass:[NSMutableDictionary Class]];//YES
[dict isKindOfClass:[NSDictionary Class]];// YES
[dict isKindOfClass:[NSArray Class]];//NO
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值