iOS初学之OC程序员iOS 开发

阅读Effective Object-C 2.0笔记(二)

2016-04-12  本文已影响102人  iLeooooo

还是要好好学习英文啊,笔者只能看中文版的,下载地址如下:
http://download.csdn.net/detail/m6830098/7977521
看书的时候还是困的不行不行的-

昨天洗澡的时候突然想到一个问题,我写的代码是用文字的类型输入进去的,空格按的心累,好吧,还是怪我自己太low,百度了一下,把简书编辑模式设置成Markdown就能copy代码进来了。

回到正文,我们今天来看看Effective Object-C 2.0的第二章。

第一条:"属性"概率的理解。属性用于封装对象中的数据,OC对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过"存取方法(access method)"来访问,系统会自动为属性生成"获取方法(getter)","设置方法(setter)"。"点语法":等号左边调用对象点语法,相当于调用setter方法,等号右边相当于getter方法。一般系统默认自动生成存取方法,如果不想编译器自动合成存取方法,也可以自己实现,如果你只实现一个存取方法,另一个也会有编译器来合成。是用@dynamic关键字,它会告诉编译器,不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。

属性特质:原子性、读/写权限、内存管理语义、方法名。
1.具备readwrite(读写)特质的属性拥有getter和setter,若该属性由@synthesize实现,则编译器会自动生成这两个方法。
2.具备readonly(只读)的属性仅拥有获取方法。
3.assign: "设置方法"只会执行针对"纯量类型(scalar type,CGFloat或NSIneger)"的简单赋值操作。
4.strong:表明属性定义了一种"拥有关系(owning relationship)",为这种属性设置新值时,设置方法会先保留新值,释放旧值,然后再将新值设置上去。
5.weak:表明属性定义了一种"非拥有关系(nonowning relationship)"为这种属性设置新值时,设置方法既不保留新值,也不释放旧值,属性遭到摧毁时,属性值会清空(nil out)。
6.unsafe_unretained:该特征的语义和assign相同,但它适用于"对象类型",表达"非拥有关系(nonowning relationship)",当目标对象被摧毁时,属性值不会自动清空。
7.copy:与strong类似,然而设置方法不保留新值,而是将其"拷贝(copy)"。当属性值为NSString *,经常用copy来保护其封装性。

第二条:在对象内部尽量直接访问实例变量。用"点语法"通过存取方法来访问相关的实例变量和不经过存取方法直接访问实例变量的区别:
1.由于不经过OC的(方法派发)步骤,故直接访问实例变量比较快,编译器所生成的代码会直接访问对象实例变量的那块内存。
2.直接访问实例变量,不会调用其"设置方法",绕过了相关属性的"内存管理语义"。
3.如果直接访问实例变量,则不会触发"键值观测(Key-Value Observing,KVO)"通知,这样做会不会产生问题,取决于具体的对象行为。
4.通过属性访问有助于排查与之相关的错误。
有一种合理的折中方案,既:写入实例变量时,通过其setter来实现。而读取实例变量时,则用直接访问实例变量。
注意:如果使用"惰性初始化(lazy initialization)",这种情况,必须通过"获取方法"来访问属性。比如:一个属性指代的对象很复杂且不常用,创建成本较高。
- (WWBrain *)brain {
if (!_brain) {
_barin = [Brain new];
}
return _brain;
}

第三条:理解"对象等同性"这一概念。根据"等同性"来比较对象是一个非常有用的功能。不过,安照 == 操作符比较的结果未必是我们想要的,该操作比较的是两个指针本身,而不是其所指的对象。应该使用"isEqual"方法来判断两个对象的等同性。

NSString *foo = @"Badge 123"
NSString *bar = [NSString stringWithFormat:@"Badge %i", 123];
BOOL equalA = (foo == bar);     //equalA = NO
BOOL equalB = [foo isEqual:bar];   //equalB = YES
BOOL equalC = [foo isEqualToString:bar];    //equalC = YES

NSObject协议中有两个用于判断等同性的关键方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
NSObject类对这两个方法的默认实现是:当且仅当其"指针值"完全相等时,这两个对象才相等。如果想覆写这些方法就必须先理解其约定(contract)。如果"isEqual:"方法判定两个对象相等,那么其hash方法也必须返回同一个值。但是,如果两个对象的hash返回同一个值,那"isEqual:"方法未必会认为两者相等。
特定类所具有的等同性判定方法:"isEqualToArray:","isEqualToDictionary:"
注意:若想检测对象的等同性,请提供"isRqual:"和hash方法、相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同。

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


对象关联类型.png

在设置关联对象值时,若想令两个键匹配到同一个值。则二者必须是相同的指针才行,故在设置关联对象值时,通常使用静态全局变量做键。
#import <objc/runtime.h>
static void *WWMyAlertViewKey = "WWMyAlertViewKey";
- (void)askUserAQuestion {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"What do you want to do?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex){
if (buttonIndex == 0) {
//doCancel
} else {
//doContinue
}
};

    objc_setAssociatedObject(alert, WWMyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
    [alert show];
  }    

#pragma mark - UIAlertView Delegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
   void (^block)(NSInteger) = objc_getAssociatedObject(alertView, WWMyAlertViewKey);
   block(buttonIndex);
}  

用这种方法改写以后,创建视图与处理操作的代码放一起了,更加容易读懂。(注意block的循环引用)

第五条:理解objc_msgSend的作用。objc_msgSend叫"消息传递",消息有"名称(name)"或"选择子(selector)",可以接受参数,而且可能还有返回值。
objc_msgSend_stret:如果待发送的消息要返回结构体,可交给此函数处理。
objc_msgSend_fpret:如果消息要返回的是浮点数,可交给此函数处理。
objc_msgSendSuper:如果要给超类发消息,可交给此函数处理。

id returnValue = [someObject messageName:parameter];
id returnValue = objc_msgSend(someObject, @selector(messageName), parameter);
<return_type> Class_selector (id self, SEL _cmd,...)(objc_msgSend函数原型)

第六条:消息转发机制。


消息机制无法找到对应的SEL.png

消息转发分两大阶段:1.动态方法解析。2.完整的消息转发机制。

+ (BOOL)resolveInstanceMethod:(SEL)selector;
+ (BOOL)resolveClassMethod:(SEL)selector;

用代码实现用"resolveInstanceMethod:"@dynamic属性:

id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionaryGetter(id self, SEL _cmd, id value);

+ (BOOL)resolveInstanceMethod:(SEL)selector
{
   NSString *selectorString = NSStringFromSelector(selector);
   if (/* selector is from a @dynamic property */) {
       if ([selectorString hasPrefix:@"set"]) {
           class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
       } else {
           class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
       }
       return YES;
   }
   return [super resolveInstanceMethod:selector];
}

备援接收者:当前接收者还有第二次机会能处理未知的SEL,在这一步中,运行期系统会问它:能不能把这条消息转给其他的接收者来处理。处理方法:

- (id)forwardingTargetForSelector:(SEL)selector;

注意:我们无法操作由这一步锁转发的消息,若是想在发送给备援接收者之前先修改消息内容,就要用完整的消息转发机制完成。

完整的消息转发:如果转发算法到了这一步,唯一能做的就是启用完整的消息转发机制。首先穿件NSInvocation对象,把尚未处理的那条消息有关的全部细节都封于其中;有SEL,目标(target)以及参数。在触发NSInvocation对象时,"消息派发系统(message-dispath system)"亲自出马,把消息指派给目标对象。该步骤调用方法:

- (void)forwardInvocation:(NSInvocation *)invocation;

只需要改变调用目标,使消息在新的目标上得以调用既可。


消息转发全流程图.png

类的方法列表会把SEL的名称映射到相关的方法实现上,使得"动态消息派发系统"能够根据此找到应该调用的方法。这些方法均以函数指针的形式来表示,这种指针叫IMP;其原型如下

id (* IMP) (id, SEL, ...)

交换两个已经写好的方法实现,可用下列函数:

void method_exchangeImplementations(Method m1, Method m2)

此方法的参数表示待交换的两个方法实现,可用下列函数:

Method class_getInstanceMethod(Class aclass, SEL aSelector)

此函数根据给定的选择从类中取出相关的方法。交换lowercaseString与uppercaseString方法实现:

Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);

代码测试:

NSString *string = @"ThIs iS tHe StRiNg";
NSString *lowercaseString = [string lowercaseString];
//NSLog: THIS IS THE STRING

NSString *uppercaseString = [string uppercaseString];
//NSLOG: this is the string

NSLog(@"%@-----%@", lowercaseString, uppercaseString);

第七条:理解"类对象"的用意
每个Object-C对象实例都是指向某块内存数据的指针。所以在声明变量时,类型后面要跟一个 "*"。

NSString *pointer = @"Some string";  (分配在堆里面)
NSString point = @"Some string"; (分配在栈上,报错)

由于通用对象类型id本身已经是指针了,故能够写:

id genericTypeString = @"Some string";

描述Object-C对象所用的数据结构定义在运行期库的头文件中,id类型本身也定义在里面;

typedef  struct objc_object {
    Class isa;
} *id ;

由此可见,每个对象结构体的首个成员就是Class类的变量。该变量定义了对象所属的类,通常称为"is a"指针。如:在刚才的所用的对象,"is a"NSString,所以,"is a"指针就指向NSString。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 **methodLists;
    struct objc_cache *cachae;
    struct objc_protocol_list *protocols;
}
类之间的继承关系.png

在类继承体系中查询类型信息
"isMemberOfClass:":能够判断出对象是否为某个特定类的实例。
"isKindOfClass:":能够判断出对象是否为某个类或其派生类的实例。
要点:1.每个实例都有一个指向Class对象的指针,用以表明类型,而这些Class对象则构成了类的继承体系。
2.尽量使用类型信息查询方法来确定对象类型,不要直接比较类对象,因为某些对象可以实现了消息转发机制。

最后,本书一共7个章节,此为第二章节:对象、消息、运行时。

共勉!一步一个巴掌印。。。。。

上一篇下一篇

猜你喜欢

热点阅读