17.Effective Objective-C2.0(一)

1.了解OC起源

  • OC使用消息结构的语言,不是函数调用,其运行时所执行的代码由运行环境来决定而不是编译器;
  • OC对象所占内存分配堆空间中,而绝不会分配在栈上;
  • 需要保存 int, float,double, char等非对象类型的,通常使用CGRect这种结构体就可以了(可能会用到栈空间)。
struct CGRect {
    CGPoint origin;
    CGSize size;
};
typedef struct CGRect CGRect;

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

  • 除非必须不要引入头文件,可以使用向前声明@class XXX;
必须在类的头文件中引入其他头文件
  • 你写的类继承自某个超类,必须引入超类的头文件;
  • 如果因为实现属性,实例变量,或者要遵从某个协议需要引入头文件,这时尽量将这条声明放在 “class- continuation” 中,如果不行就放在一个单独的文件中。
  • 委托协议不用写在一个单独的头文件中,在这种情况下,协议只有和接受协议的类放在一起才有意义。

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

   //字面数值
   NSNumber *someNumber1 = [NSNumber numberWithInt:1]; 
   NSNumber *someNumber1 = @1;
    
    
    //数组
    NSArray *animal = [NSArray arrayWithObjects:@"cat",@"dog",@"mouse", nil];
    NSArray *animal1 = @[@"cat",@"dog",@"mouse"];
    
    NSString *dog = [animal objectAtIndex:1];
    NSString *dog1 = animal1[1];
    
    
    //字典中的对象必须是OC对象
    NSDictionary *personData = [NSDictionary dictionaryWithObjectsAndKeys:@"Matt",@"firstName",
                                @"Galloway",@"lastName",
                                [NSNumber numberWithInt:28],@"age", nil];
    NSDictionary *personData1 = @{
                                  @"firstName":@"Matt",
                                  @"lastName":@"Galloway",
                                  @"age":@28
                                  };
    NSString *lastName = [personData objectForKey:@"lastName"];
    NSString *lastName1 = personData[@"lastName"];

    //可变数组和字典
    [mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
    mutableArray[1] = @"dog";
    
    [mutableDictionary setObject:@"Galloway" forKey:@"lastName"];
    mutableDictionary[@"lastName"] = @"Galloway";
  • 使用字面量语法创建数组或者字典的时候。值中如果有nil,会抛出异常,所以务必确保值中不包含nil。
  • 使用字面量语法创建数组或者字典都是不可变的,想要可变版本就复制一份。这样会多调用一个方法,在创建一个对象。
    NSMutableArray *mutableArray = [@[@1,@2,@3,@4,@5]mutableCopy];

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

1.局限于某编译单元的常量(实现文件之内使用,不公开)
#import "Dog.h"

static const NSTimeInterval  kAnimationDuration = 0.3;

@implementation Dog
@end
  • 如果试图修改const修饰符声明的变量,编译器会报错;
  • static修饰符意味着该变量仅在定义这个变量的编译单元中可见;
  • 这种常量前通常加k。
2.外界可见的常量
//.h
extern NSString *const EOCStringConstant;
//.m
NSString *const EOCStringConstant = @"VALUE";
  • 常量定义从右向左解读:一个常量,这个常量是指针,指向NSString对象。
  • extern修饰符声明全局变量,常量放到全局表中;
  • 这种常量前通常加类名。

5.用枚举表示状态,选项,状态码

    //枚举定义
    enum EOCConnectionState {
        EOCConnectionStateDisconnected,
        EOCConnectionStateConnecting,
        EOCConnectionStateConnected,
    };
    enum EOCConnectionState state = EOCConnectionStateDisconnected;
    
    //如果每次不想写enum,使用typedef关键字重新定义枚举类型
    enum EOCConnectionState {
        EOCConnectionStateDisconnected,
        EOCConnectionStateConnecting,
        EOCConnectionStateConnected,
    };
    typedef enum EOCConnectionState EOCConnectionState;
    EOCConnectionState state = EOCConnectionStateDisconnected;

常用的2种枚举宏定义

//简单的枚举
    typedef NS_ENUM(NSInteger,EOCConnectionState){
        EOCConnectionStateDisconnected,
        EOCConnectionStateConnecting,
        EOCConnectionStateConnected,
    };
//包含一系列选项的枚举,多个选项可以同时组合使用
    typedef NS_OPTIONS(NSInteger,EOCPermittedDirection){
        EOCPermittedDirectionUp     = 1 << 0,
        EOCPermittedDirectionDown   = 1 << 1,
        EOCPermittedDirectionLeft   = 1 << 2,
        EOCPermittedDirectionRight  = 1 << 3,
    };
  • 这2种宏定义的枚举可以向后兼容,同时指定了底层的数据类型。
  • 在枚举类型的switch语句中不要实现default分支。

6.理解属性这一概念

<一>.基本定义

1.1 实例变量:OC对象通常会把其所需要的数据保存为实例变量;

#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject {
    NSData *_dateOfBirth; //实例变量
    NSString *_firstName;  //实例变量
}
@end

1.2 属性:实例变量 + 存取方法 + 内存管理语义(在我理解)

属性会

1.自动生成存取方法
2.自动生成带下划线的实例变量
3.在生成的存取方法中会按照属性特质和相关的内存管理语义生成和处理实例变量。(比如变量的拷贝,读写权限等。。。)

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property NSString *firstName; //属性
@property NSString *lastName; //属性

@end

等价于下面

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
  • 1.3 如果想自己指定实例变量的名字
#import "EOCPerson.h"
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@end
  • 1.4 如果不想让编译器自动实现存取方法
@interface EOCPerson : NSObject
@property NSString *firstName; //属性
@property NSString *lastName; //属性
@end

@implementation EOCPerson
@dynamic firstNamela,lastName;
@end
  • 在OC中,对象是基本构造单元。开发者通过对象来存储和传递数据,在对象之间传递数据并执行任务的过程叫做消息传递。
  • [EOCPerson alloc] 生成对象,分配内存空间,_firstName,_lastName是实例变量。[[EOCPerson alloc] init]一般在init方法里面进行实例对象的初始化。
  • OC的做法 将实例变量作为一种存储偏移量所用的“特殊变量”,交由类对象进行保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也变了。这样在还认识访问实例变量,总能使用正常的偏移量。甚至可以在运行期向类中新增实例变量,这就是稳固的“应用程序二进制接口”(ABI)。


    屏幕快照 2017-09-13 16.50.17.png
1.5 属性的特质

注:属性的特质会影响编译器所生成的存取方法,在设置属性所对应的实例变量的时候,一定要遵从该属性声明的语义。

  • 原子性:atomic,nonatomic
  • 读写权限:readwrite(读写),readonly(只读)
1.6 内存管理语义

属性用于封装数据,但是数据要有具体的所有权语义

  • assign:只针对纯量类型的简单赋值操作。一般用于 ‘基本数据类型’、‘枚举’、‘结构体’ 等非OC对象类型
  • strong:表明一种拥有关系,为这种属性赋新值的时候,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。一般用于OC对象类型(NSArray、NSDate、NSNumber、模型类)
  • weak:表明一种非拥有关系,为这种属性赋新值的时候,设置方法既不保留新值,也不释放旧值。此特质同assign相似,但是当属性所指的对象遭到销毁的时候,属性值会清空(nil out)。一般应用: 代理对象
  • unsafe_unretained:此特质同assign相似,但是适用于对象类型,表明一种非拥有关系,但是当属性所指的对象遭到销毁的时候,属性值不会清空。
  • copy:此特质同strong类似。但是设置方法并不会保留新值,而是将其拷贝。(一般用于NSString&Block)
@property (nonatomic,关键字) NSObject *a;
当使用了不同的关键字后自动实现的set方法:

//assign
-(void)setA:(int)a{
        _a=a;
}

//retain
-(void)setA:(Car *)a{
       if(_a!=a){
           [_a release];
           _a=[a retain];
       }
}

//copy
-(void)setA:(NSString *)a{
       if(_a!=a){
          [_a release];
          _a=[a copy];
       }
}
举个例子:
  NSMutableString*str = [NSMutableString stringWithFormat:@"aaa"];
  self.name = str;

//再说一下 copy 类中的 set 方法,如果属性是 copy 的,那么系统默认只会在 set 方法中调用 copy 的方法:
-(void)setName:(NSString *) name{
  _name = [name copy];
}
  • 属性使用strong的含义就是:将NSString指向了NSMutableString所指向的位置,并对NSMUtbaleString计数器加一,此时,如果对NSMutableString进行修改,也会导致NSString的值修改,原则上这是不允许的.

  • 属性使用copy的含义就是:在给name属性赋值的时候,系统默认先将str执行一次copy方法,然后再将结果赋给我们的属性,只有这样你再之后对str修改之后,name的值还是不变的,说明两个指针其实指向的是不同的内容.

注:并不是所有情况下我们的string都必须使用copy,因为如果我们的需求是希望string是随着我的改变而改变的,那么这个时候应该使用strong

<三>类的copy:

1.什么时候实现类的拷贝呢?

我们对NSString对象可以执行copy,是因为在NSString实现了类的copy,但是在一些自定义的类中就需要我们自己实现类的拷贝了。

2.类的拷贝怎样实现?

如果想实现自定义类的 copy 方法,我们是需要先遵守 NSCopying 协议,然后实现-(id)copyWithZome:(NSZone*)zone的方法:

-(id)copyWithZone:(NSZone *)zone{
Mitchell *copyMit = [[Mitchell allocWithZone: zone]  init];
copyMit.name = self.name;
return copyMit;
}

zone:系统返回给我们 copy 对象的内存空间
注意:必须在初始化方法中给属性赋值,才能让 copy 出的对象和原来的对象有相同的属性。

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

原则:
  • 在类的 初始化方法 和 dealloc中不能通过属性的set方法赋值,总是应该通过实例变量来写入;
  • set 方法中不能再次调用 set 方法,get 方法中不能再次调用get方法;
  • 在懒加载的方法中必须通过 get 方法访问属性;
  • 在对象内部读取数据时,直接访问实例变量,写入数据时通过属性来写入;
原理:
  • 直接访问实例变量并不经过OC的“方法派发”(11),所以直接访问实例变量的速度比较快,在这种情况下,编译器所生成的代码会直接访问保存实例变量的那块内存。
  • 直接访问实例变量,不会调用其设置方法,这样就绕过了为相关属性所定义的“内存管理语义”。例如:在ARC下直接访问一个声明为copy的属性,那么不会拷贝该属性,只会保留新值并释放旧值。
  • 直接访问实例变量,不会触发“键值观察”通知。
  • 通过属性访问有助于排查错误。

补充:OC继承方法调用流程:首先到子类中去找,如果有该方法,就调用子类的方法,如果没有再到父类中去找,直至找不到或者程序奔溃。

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

  • ==操作符:比较的是两个指针本身;
    NSString *foo = @"Badger 123";
    NSString *bar = [NSString stringWithFormat:@"Badger %i",123];
    BOOL eaualA = (foo == bar);//NO
    BOOL eauaB = [foo isEqual:bar];//YES
    BOOL eaualC = [foo isEqualToString:bar];//YES
  • NSObject 协议中有两个用来判断等同性的关键方法:
    -(BOOL)isEqual:(id)object;
    -(NSUInteger)hash;
  • OC类对这两个方法的默认实现是:当且仅当其“指针值”完全相等时,这两个对象才相等。(但是需要注意的是这是针对自定义的对象来说的,对于系统中已经存在的对象,可能已经覆写了这个方法);
  • 如果 isEqual 方法判定两个对象相等,那么其 hash 方法也必须返回同一个值,反正,不成立。
特定类的等同性判定方法
  • isEqualToString
  • isEqualToArray
  • isEqualToDictionary

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

用处:
  • 隐藏“抽象基类”背后的实现细节,并将实现细节隐藏在一套简单的公共接口里面。如UIButton这种。。

实现:

.h

#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger,EOCEmployeeType) {
    EOCEmployeeTypeDeveloper,
    EOCEmployeeTypeDesigner,
    EOCEmployeeTypeFinance,
    
};

@interface EOCEmployee : NSObject

@property (copy) NSString *name;
@property NSUInteger salary;

+ (EOCEmployee *)employeeWithType:(EOCEmployeeType)type;

- (void)doADaysWork;

@end

.m

#import "EOCEmployee.h"

@implementation EOCEmployee
+ (EOCEmployee *)employeeWithType:(EOCEmployeeType)type{
    switch (type) {
        case EOCEmployeeTypeDeveloper:
            return [EOCEmployeeTypeDeveloper new]
            break;
        case EOCEmployeeTypeDesigner:
            return [EOCEmployeeTypeDesigner new]
            break;
        case EOCEmployeeTypeFinance:
            return [EOCEmployeeTypeFinance new]
            break;
    }
    
    
}

- (void)doADaysWork{
//Subclasser implement this 
}

@end

  • 每个实体子类 (EOCEmployeeTypeDeveloper,
    EOCEmployeeTypeDesigner,
    EOCEmployeeTypeFinance)继承自基类EOCEmployee;
  • 系统框架中许多类族,大部分的collection类都是类族;不可变的类定义了对所有的数组都通用的方法,可变的类定义了那些只适用于可变数组的方法;

注:因为类族的存在,所以有两个方法需要区分一下:

  • isKindOfClass:检查某个实例所属的类是否位于类族中
  • isMemberOfClass:检查某个实例对象是否是某个类的实例
向NSArray这样的类族中新增子类的原则:
  • 子类应该继承自族类的抽象基类;
  • 子类应该定义自己的数据存储方法;
  • 子类应该覆写超类文档中指明需要覆写的方法;

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

用处:
  • 有时需要在对象中存放相关的信息,一般是从对象所属的类中继承一个子类,然后改用这个子类的对象,但是并非所有的 都可以这样做,有时候类的实例可能是由某种机制创建的,而开发者无法令这种机制创建出自己所写的子类的对象,这个时候就可以通过“关联机制”把两个对象连起来。
  • 可以给某个对象关联很多个对象,这些对象通过 “键” 来区分。存储对象值得时候指明 “存储策略” ,用以维护相应的 “内存管理”语义。(模仿定义属性的时候的 “拥有关系” & “非拥有关系”)
    注:在其他做法性行不通的时候使用,因为可能会引入难以查找的bug。
//用给定的键和策略为某对象设置关联对象值
void objc_setAssociatedObject(id  object, const void * key, id  value, objc_AssociationPolicy policy)
//用给定的键从某对象中获取相应的关联对象值
id objc_getAssociatedObject(id  object, const void * key)
//移除指定对象的全部关联对象
void objc_removeAssociatedObjects(id  object)
    //objc_AssociationPolicy
    OBJC_ASSOCIATION_ASSIGN = 0,         
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,                            
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  
    OBJC_ASSOCIATION_RETAIN = 01401,    
    OBJC_ASSOCIATION_COPY = 01403      

11.理解 objc_msgSend 的作用

相关定义
消息:

“接收者” ,“选择子”(方法) ,和参数构成;

消息传递:

在对象上调用方法;

给某个对象 “发送消息”

就是在该对象上调用方法;(发送给某个对象的全部消息都由 “动态消息派发系统” 处理,该系统会查出对应的方法,并执行其代码)

静态绑定:

C语言的函数调用方式,在编译期就能决定运行时所应调用的方法;函数地址硬编码在指令中;

动态绑定:

OC语言的函数调用方式,在运行期才能决定运行时所应调用的方法;

底层:

在底层所有的方法都是C函数,然而对象在收到消息之后,究竟调用哪个方法则完全于运行期决定,甚至可以在运行期改变,这些特性使得OC成为一门真正的动态语言。

id returnType = [someObject messageName:parameter];

编译器处理之后

id returnType = objc_msgSend(someObject @selector(messageName:),parameter];
  • objc_msgSend函数会根据接受者与选择子的类型来调用适当的方法。为了完成此操作:
    1)方法需要在接受者所属的类中搜寻其 “方法列表” ,如果能找到,就跳至其代码,
    2) 如果没有就沿着继承体系向上找,
    3) 如果还是找不到,那么就执行“消息转发”。

注:objc_msgSend会将匹配的结果缓存在 “快速映射表” 中,每个类都有这样一块缓存。

12.理解消息转发机制

定义&过程:
    1. 当对象无法响应某个选择子,就会进入 “消息转发” 流程,程序员可以在此过程中告诉对象应该怎样处理未知的消息。
    1. 通过运行期的动态方法解析,我们可以在需要用到某个方法时在将其加入到类中;
    1. 对象可以将其无法解读的消息转发给其他对象处理;
    1. 经过上面2,3步还是无法处理的消息子,就启动完整的消息转发机制;
动态方法解析(2)
屏幕快照 2017-09-15 12.21.20.png

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

定义
方法调配:

不需要源代码也不需要通过继承子类覆写方法,在运行时就可以改变这个类本身的功能,这样,新功能将在本类的所有实例中生效。而不是仅限于覆写了相关方法的那些子类实例;

IMP:类的方法列表会把选择子的名称映射到相关的方法实现上,使得 “动态消息派发系统” 能够据此找到应该调用的方法,这些方法均以函数指针的形式存在,这种指针叫做IMP。

原型: id (*IMP) (id, SEL, ...)

函数指针 & SEL

应用:交换两个方法的实现
Method  originMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method  swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(originMethod,swappedMethod);

黑盒方法

开发者可以为那些 “完全不知道其具体实现的” 黑盒方法增加日志的功能。
实现:在已有类中增加分类,首先调用原先的功能,然后输出这个类的相关信息。

注意:一般情况下不会使用 “方法调配” 来永久的改变系统类的功能,这样是不理智的。。。

14. 理解类对象的定义

屏幕快照 2017-09-15 16.51.36.png
屏幕快照 2017-09-15 16.53.46.png
  • 1.每个实例对象的类都是类对象,每个类对象的类都是元类对象,每个元类对象的类都是根元类(root meta class的isa指向自身)
  • 2.类对象的父类最终继承自根类对象NSObject,NSObject的父类为nil
  • 3.元类对象(包括根元类)的父类最终继承自根类对象NSObject

类对象存的是关于实例对象的信息(变量,实例方法等),而元类对象(metaclass object)中存储的是关于类的信息(类的版本,名字,类方法等)。
每个实例对象都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系;
如果类对象无法再编译期确定,那么就应该使用类型信息查询的方法来探知;
尽量使用类型信息查询的方法来确定对象,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。

18. 尽量使用不可变的对象

原则
  • 尽量使用不可变的对象(尽量将对外公布的属性设置为只读,而且只有在其确实需要时才对外公布)
  • 如果某属性仅可用于对象内部修改,则在 “class-continuation” 分类中将其readonly属性扩展为readwrite属性
  • 不要把可变的 collection 作为属性公开,而应该提供相关的方法,以此修改对象可变的collection。
原理
  • 因为collection会把各个对象按照其hash码分装到不同的 “ 箱子数组 ” 中去。如果一个可变的对象在装到箱子数组中之后再改变,那么它的hash码可能又变了,那么其所在的箱子可能是错误的。
    正确的做法应该是:可变对象在放到数组之后不再改变
    .h
#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject

@property (nonatomic,copy,readonly) NSString *firstName;
@property (nonatomic,copy,readonly) NSString *lastName;
@property (nonatomic,strong,readonly) NSSet *friends;


- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
- (void)addFriends:(EOCPerson *)person;
- (void)removeFriends:(EOCPerson *)person;

@end

.m

#import "EOCPerson.h"

@interface EOCPerson()

@property (nonatomic,copy,readwrite) NSString *firstName;
@property (nonatomic,copy,readwrite) NSString *lastName;
@end

@implementation EOCPerson {
    NSMutableSet *_internalFreind;
    
}

- (NSSet *)friends {
    //外部生成一份不可变的set
    return [_internalFreind copy];
}

- (void)addFriends:(EOCPerson *)person {
    [_internalFreind addObject:person];  
}

- (void)removeFriends:(EOCPerson *)person {
    [_internalFreind removeObject:person];

}

- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
    if (self = [super init]) {
        _firstName = firstName;
        _lastName = lastName;
        _internalFreind = [NSMutableSet new];
        
    }
    return self;
}

@end

15.用前缀避免命名空间的冲突 & 19使用清晰协调命名方式 & 20为私有方法添加前缀 & 25总为第三方类的分类名称前添加前缀

15.用前缀避免命名空间的冲突的原则
  • Apple公司宣称其保留使用所有“两个字母”的权利,所以你选择的前缀应该是3个字母的。
  • 在类名前添加和公司或者程序相关的前缀充当命名空间的作用。
  • 在分类和分类中的方法添加前缀。
  • 类的实现文件中的全局变量和纯C函数,在编译好的目标文件中,这些文件是要算作顶级文件的。
  • 如果自己开发的程序库用到了第三方哭,应该为其中的名称加上前缀。
19.使用清晰协调的命名方式的原则
  • 方法和变量名使用驼峰式大小写命名法:以小写字母开头,其后每个单词的首字母大写。
  • 类名也用驼峰式,不过首字母大写。
  • 类和协议的名称前加上前缀。
20.为私有方法添加前缀的原则
@interface XXX:NSObject
-(void)publicMethod;
@end

@implementation XXX
-(void)publicMethod{}
-(void)p_privateMethod{}
@end
  • 因为OC语言没有办法将方法标为私有,每个对象都可以响应任何的消息(12条),而且可以在运行期检视某个对象所能直接响应的消息(14条),根据给定的消息检查出其对应的方法,这一工作要在运行期完成(11条),所以OC中没有那种约束方法调用的机制用以限定谁能调用此方法,能在哪个对象上调用此方法以及何时能调用此方法。
  • Apple保留了使用_作为私有方法的前缀。
25.总为第三方类的分类名称前添加前缀
@interface NSString (ABC_HTTP)
- (NSString *) abc_urlEncodedString;
- (NSString *) abc_urlDecodedString;
@end
  • 原理:分类机制通常用于向无源码的既有类中新增功能,但是问题是分类中的方法是直接添加在类里面的,相当于这个类的既有方法。将分类方法加入到类中这一操作在运行期加载分类时完成。运行期系统会把分类中所实现的每个方法都加入到类的方法列表中去。如果重名,会产生覆盖。可能会产生多次覆盖,以最后一个分类为准。

16.提供全能初始化方法

1.定义
  • 可以为对象提供必要信息以便其能完成工作的初始化话方法叫做全能初始化方法(designated initializer)。
2.使用原则
  • 2.1在类中提供一个全能初始化方法,并于文档里面指明,其余的初始化方法均应调用此方法。
  • 2.2每个子类的的全能初始化方法都应该调用其超类的对应方法并逐层向上。
  • 2.3若全能初始化方法与超类不同,应该覆写超类中的对应的方法。其中如果超类中的初始化方法不适用于子类,那么应该覆写超类方法,并在其中抛出异常。(这种情况一般是指实例真的没有办法初始化了)
3.解释一下
屏幕快照 2017-09-13 10.50.09.png
  • 只有在全能初始化方法初始化方法中,才会存储内部数据,这样的话当底层的数据存储机制改变的时候,只需要修改此代码即可,不需要修改其他的初始化方法。(因为其他的初始化都要调用这个方法)
  • 这样设计可以保证初始化的时候类的完整性,和修改的时候底层数据的统一性。
  • 全能初始化方法并不是只有一个。

17.实现 description 方法

1.为什么要实现description 方法?

  • 首先在构建需要打印的字符串的时,object对象会收到description的消息,该方法所返回的描述信息将取代“格式化字符串”里面的“%@”。
  • 像NSArray,NSDictionary是系统中自带的类,所以在打印(NSLog)的时候会有比较简洁明了的输出。(系统处理的)
  • 但是自定义的类在打印输出时,信息并没有进行筛选和处理,所以在这里我们可以覆写description方法,这样在NSLog时可以输出我们想要的信息并将信息排列整齐。
//普通的描述信息,在NSLog打印本类时,在输出窗口显示
- (NSString *)description {

    return [NSString stringWithFormat:@"%@ %@",_name,_classNumber]; 
}

//有时候开发者不想把类名和指针地址这种信息放在普通的描述信息里面,但是却希望在调试的时候(po self)能够方便的看到他们,在这种情况下将他们放在 debugDescription 中。
- (NSString *)debugDescription {
    
    // \:代表转义后面的双引号
    return  [NSString stringWithFormat:@"<%@: %p, \"%@ %@\" >",[self class],self, _name,_classNumber];
    
}

//将类中的信息封装为字典输出
.h


#import <Foundation/Foundation.h>

@interface EOCLocation : NSObject

@property (nonatomic,copy,readonly) NSString *title;
@property (nonatomic,assign,readonly) float latitude;
@property (nonatomic,assign,readonly) float longtitude;

- (id)initWithTitle:(NSString *)title
           latitude:(float)latitude
         longtitude:(float)longtitude;
@end

.m


#import "EOCLocation.h"

@implementation EOCLocation

- (id)initWithTitle:(NSString*)title latitude:(float)latitude longtitude:(float)longtitude {
    
    if (self = [super init]) {
        _title = [title copy];
        _latitude = latitude;
        _longtitude = longtitude;
        
    }
    return self;
}


//覆写description方法
-(NSString *)description {
    
    return [NSString stringWithFormat:@"<%@: %p, %@>",
            [self class],
            self,
            @{@"title":_title,
              @"latitude":@(_latitude),
              @"longtitude":@(_longtitude)}
            ];  
}

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