Effective Objective-C 2.0 总结与笔记(

2018-11-17  本文已影响0人  JellyP_gdgd

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

​ “对象”就是“基本构造单元”,开发者可以通过对象来存储并传递数据。对象之间传递数据并执行任务的过程就是“消息传递”。程序运行起来后,为其提供相关支持的代码就是“Objective-C运行期环境”,它提供了一些使得对象之间能够传递消息的重要函数,并且包括创建类实例所用的全部逻辑。

第6条:理解“属性”这一概念

//public区段内声明实例变量
@interface EOCPerson : NSObject {
@public
    NSData *_dataOfBirth;
    NSString *_firstName;
    NSString *_lastName;
}

//使用@property方式声明
@interface EOCPerson : NSObject

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

@end
    
//@property方式等效于
@interface EOCPerson : NSObject

- (NSString *)firstName;
- (void)setFirstName;
- (NSString *)lastName;
- (void)setLastName;

@end

如果要访问属性,可以使用“点语法”,编译器会把“点语法”转换成对存取方法的调用。

EOCPerson *aPerson = [EOCPerson new];
aPerson.firstName = @"GDGD";//same as
[aPerson setFirstName:@"GDGD"];

NSString *lastNameOfGD = aPerson.lastName;//same as
NSString *lastNameOfGD = [aPerson lastName];

使用属性的方式进行声明实例变量,编译器会自动生成存取方法,虽然这个时候在编译器上看不到生成的方法,这个方法默认的名字是:getter—就是属性的名字,setter—属性名字前面加set。

如果想要修改“合成方法”的名字,可以使用@synthesize语法来指定关键字的名字。

//在实现文件中
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end

如果不想要编译器自动生成的方法,可以使用@dynamic语法来阻止编译器的生成。

@implementation EOCPerson
@dynamic firstName, lastName;
@end

(1)、原子性:默认情况下,编译器所合成的方法会通过锁定机制确保其原子性 (atomicity) 。如果属性具备nonatomic特质,则不使用同步锁。在iOS开发的程序里,由于同步锁开销较大,如果使用原子性的属性会导致性能问题,所以属性都是nonatomic的特质。

(2)、读写权限:

​ readwrite (读写) :拥有前面说的获取方法 (getter) 和设置方法 (setter),默认情况。

​ readonly (只读) :仅拥有获取方法。可以对外暴露为只读属性,然后在“class-continuation分类”中重新定义为读写属性。(27条有说)

(3)、内存管理语义:这是最难理解的部分了,虽然这个特质仅会影响“设置方法”,但是其中涉及到内存管理部分,所以还是非常重要。

​ assign:“设置方法”只会针对“纯量类型”(scalar type,例如CGFloat,NSInteger等)的简单赋值。一般在之前分配在栈里的数据类型就是使用assign修饰。

​ strong:此特质表示一种拥有关系,当给这种属性设置新值的时候,会先保留新值,再释放旧值,然后设置新值上去,一般Objective-C对象使用这个修饰词。

​ weak:表示非拥有关系,为这种属性设置新值的时候,不保留新值,也不释放旧值,当属性所指的对象被销毁的时候,属性值也会被清空。这就是一种弱持有,一般可以用来打破循环引用的情况(后面会介绍)。

​ unsafe_unretained :语义和assign相同,但是适用于对象类型(object type),表示一种非拥有关系,但是当目标对象被销毁的时候,属性值不会被清空。

​ copy:和strong类似,但是设置方法并不保留新值,而是将其拷贝。一般NSString *就使用这个特质来修饰。这是因为NSString *有可能指向一个可变的NSMutableString 实例,如果使用的不是copy的话,那么当可变字符串被篡改后,会影响到你不可变的字符串,所以要拷贝一个不可变的字符串。

(4)、方法名:可以用来指定存取方法的方法名。

​ getter=<name>:指定获取方法的方法名,如果某属性是Boolean,一般会用这种方式来在获取方法名上加上is前缀。

​ setter=<name>:指定设置方法的方法名,较少使用。

第7条:在对象内部尽量直接访问实例变量

//使用属性访问
- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}

- (void)setFullName {
    NSArray *components = [[self fullName] componentsSeparatedByString:@" "];
    self.firstName = components[0];
    self.lastName = components[1];
}

//使用实例变量访问
- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@", _firstName, _lastName];
}

- (void)setFullName {
    NSArray *components = [[self fullName] componentsSeparatedByString:@" "];
    _firstName = components[0];
    _lastName = components[1];
}
//lazy initialization
- (NSString *)firstName {
    if (!_firstName) {
        _firstName = @"GDGD";
    }
    return _firstName;
}

第8条:理解“对象等同性”这一概念

    NSString *foo = @"hhh123";
    NSString *bar = [NSString stringWithFormat:@"hhh%i",123];
    Boolean equalA = (foo == bar); // NO
    Boolean equalB = [foo isEqual:bar];//YES
    Boolean equalC = [foo isEqualToString:bar];//YES
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

NSObject对这两个方法的默认实现是:指针值完全相等的时候,两个对象才相等。如果isEqual方法为Yes,那么两个对象的hash值相等,反之不成立。理解这个意义是自定义实现isEqual:的关键。

设计hash方法的时候,需要尽量减少对象的碰撞,防止出现运算复杂度过大的情况。

    NSMutableSet *set = [NSMutableSet new];
    
    NSMutableArray *arrayA = [@[@1, @2] mutableCopy];
    [set addObject:arrayA];
    //set = {((1,2))}
    
    NSMutableArray *arrayB = [@[@1] mutableCopy];
    [set addObject:arrayB];
    //set = {((1),(1,2))}
    
    [arrayB addObject:@2];
    //set = {((1,2),(1,2))} 打破了set的语义
    
    NSSet *copySet = [set copy];
    //copySet = {((1,2))}

第9条:以”类族模式“隐藏实现细节

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

//此方法以给定的键和策略为某对象设置关联对象
- 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);

可以把对象想象成NSDictionary,把关联到该对象的值理解为字典的条目,本质的区别在于设置关联对象的key是个不透明的指针,在NSDictionary里如果两个键值相等那么isEqual:方法的返回值就是YES,但是关联对象必须是两个指针相同才行,在设置关联对象的时候通常使用静态全局变量做键。

第11条:理解objc_msgSend的作用

/**
someObject —— 接受者
messageName —— 选择子
parameter —— 参数
**/
id returnValue = [someObject messageName:parameter];

//编译器看到这个消息之后会转换成一条标准的C语言函数调用,调用的是消息中心的核心函数,objc_msgSend
void objc_msgSend(id self, SEL cmd, ...);

//所以上面那个函数调用经过编译器会转换成如下函数-->
id returnValue = objc_msgSend(someObject,
                              @selector(messageName:),
                              parameter);

//obj_msgSend函数会依据接受者和选择子的类型来调用适当的方法,为了完成此操作的方法需要在接受者所属类中搜寻方法列表,如果有就跳到其执行的代码,如果没找到就沿着体系继续往上查找,找到合适的方法再跳转。如果还没有,后面就会涉及到消息转发机制。
//obj_msgSend会将匹配结果换存在”快速映射表“里,这样每个类其实都有这样一块缓存,所以执行起来很快,但是还是不如”静态绑定的函数调用操作“那样迅速。

第12条:理解消息转发机制

第13条:用“方法调配技术”调试“黑盒方法”

//此函数的两个参数表示待交换的两个方法实现
void method_exchangeImplementations(Method m1, Method m2);

//方法实现可以通过下列函数获得。
Method class_getInstanceMethod(Class cls, SEL name);

//example
//将uppercaseString和lowercaseString两个方法利用方法调配进行调换
Method originalMethod = class_getClassMethod([NSString class], @selector(lowercaseString));
    Method swappedMethod = class_getClassMethod([NSString class], @selector(uppercaseString));
    method_exchangeImplementations(originalMethod, swappedMethod);

除了上面说的两个系统方法的替换,还可以使用自定义的方法和系统方法进行替换,这样的话就能够为那些系统的黑盒方法增加日志记录功能,这个非常有助于程序调试。很少人会在调试程序之外的场合使用上述方法来永久改变某个类的功能

第14条:理解“类对象”的用意

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

每个对象结构体的首个成员是Class类的变量,该变量定义了对象所属的类,称为"is a"指针。

Class的定义如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

此类结构体存放类的元数据,例如类的实例实现了几个方法,具备多少个实例变量等信息,首个变量也是isa指针,说明Class本身也是Objective-C对象。结构体有个叫做super_class的变量,是本类的超类。类对象所属的类型是另一个类,叫做“元类”,用来表示类对象本身所具备的元数据。每个类仅有一个类对象,每个类对象仅有一个与之相关的元类。

example:

假设有个名为someClass的子类从NSObject继承而来,则既成体系如下所示:


super_class指针确立了继承关系,而isa指针描述了实例所属的类。

上一篇 下一篇

猜你喜欢

热点阅读