IT评测·应用市场-qidao123.com技术社区

标题: 【iOS】小蓝书学习(二) [打印本页]

作者: tsx81428    时间: 2025-4-18 06:23
标题: 【iOS】小蓝书学习(二)
第6条:明白”属性“这一概念

”属性“(property)
这是OC中的一项特性,用于封装对象中的数据。OC对象通常会将其所必要的数据保存为各种实例变量。实例变量一样平常通过“存取方法”accessmethod来访问。其中,“获取方法”getter用于读取变量值,而“设置方法“setter用于写入变量值。
  1. @interface ECOPerson:NSObject {
  2. @public
  3.   NSString* _firstName;
  4.   NSString* _lastName;
  5. @private
  6.   NSString* _someInternalData;
  7. }
复制代码
我们在_fristName前再加一个实例变量,这个时间,偏移量硬编码就会读取到错误的值。
  1. @interface ECOPerson:NSObject {
  2. @public
  3.   NSString* _dadaOfBirth;
  4.   NSString* _firstName;
  5.   NSString* _lastName;
  6. @private
  7.   NSString* _someInternalData;
  8. }
复制代码
这时,就会出现下图所示的问题:

这里我们就必要留意到,如果代码利用了编译期盘算出来的偏移量,那么在修改类定义之后必须重新编译,否则就会堕落
所以在我们利用的时间,只管不要直接访问实例变量,而是通过存取方法来做,虽然属性最终还是得通过实例变量来实现,但是它却提供了一种简洁的抽象机制。
  1. @interface ECOPerson:NSObject
  2. @property NSString* firstName;
  3. @property NSString* lastName;
  4. @end
复制代码
对于该类的利用者来说,上述代码写出来的类和下面这种写法是等效的:
  1. @interface ECOPerson:NSObject
  2. - (NSString*) firstName;
  3. - (void)setFirstName:(NSString*)firstName;
  4. - (NSString*) lastName;
  5. - (void)setLastname:(NSString*)lastName;
  6. @end
复制代码
@dynamic关键词:
在上述语法中,编译器会主动合成存取方法,如果我们想制止编译器主动合成存取方法,就必要利用@dynamic关键词。这个关键词会告诉编译器不要主动创建实现属性所用的实例变量,也不要为其创建存取方法。
  1. @interface RCOPerson:NSManagerObejct
  2. @property NSString* firstName;
  3. @property NSString* lastName;
  4. @end
  5. @implementation ECOperson
  6. @dynamic firstName,lastName;
  7. @end
复制代码
要点:

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

在对象外部访问实例变量时,我们总是应该通过属性来做,而在对象内部访问实例变量时,有人认为应该“通过属性访问”,有人又说应该“直接访问”,也有人认为两种方法应该搭配利用。但是本书发起各人在读取实例变量时采用直接访问的情势,而在设置实例变量的时间通过属性来做。
我们以下面这个类为例,举例阐明:
  1. @interface ECOPerson : NSObject
  2. @property (nonatomic, copy)NSString* firstName;
  3. @property (nonatomic, copy)NSString* lastName;
  4. -(NSString*) fullName;
  5. -(void) setFullName:(NSString*) fullName;
  6. @end
复制代码
fullName和setFullName这两个“便捷方法”可以这样来实现:
  1. - (NSString *)fullName {
  2.     return [NSString stringWithFormat:@"%@ %@",self.firstName, self.lastName];
  3. }
  4. - (void)setFullName:(NSString *)fullName {
  5.     NSArray* components = [fullName componentsSeparatedByString:@" "];
  6.     self.firstName = [components objectAtIndex:0];
  7.     self.lastName = [components objectAtIndex:1];
  8. }
复制代码
下面我们重写这两个方法,不经过存取方法,而是直接访问实例变量:
  1. - (NSString *)fullName {
  2.     return [NSString stringWithFormat:@"%@ %@",_firstName, _lastName];
  3. }
  4. - (void)setFullName:(NSString *)fullName {
  5.     NSArray* components = [fullName componentsSeparatedByString:@" "];
  6.     _firstName = [components objectAtIndex:0];
  7.     _lastName = [components objectAtIndex:1];
  8. }
复制代码
直接访问和属性访问的区别:

惰性初始化
在这种情况下,我们必须通过“获取方法”来访问属性,否则实例变量永久也不会初始化。
举例阐明,ECOPerson类也许会用一个属性来表示人脑中的信息,这个属性所指代的对象相称复杂。由于该属性不常利用,并且创建的成本比较高,我们就可能会在“获取方法”中实行惰性初始化:
  1. -(EOCBrain*)brain {
  2.    if(!_brain) {
  3.       _brain = [Brain new];
  4.    }
  5.    return _brain;
  6. }
复制代码
  若没有调用“获取方法”就直接访问实例变量,则会看到尚未设置好的brain,所以说,如果利用了“惰性初始化”技术,那么必须通过存取方法来访问brain属性。
  要点:

第8条:明白“对象等同性”这一概念

根据“等同性”(equality)来比较对象是一个非常有用的功能。但是按照==利用符比较出来的结果未必是我们想要的结果,这是由于该利用比较的是两个指针自己,而不是其所指的对象。我们应该利用NSObject协议中声明的isEqual来判定两个对象的等同性。
  1.     NSString* foo = @"Badger 123";
  2.     NSString* bar = [NSString stringWithFormat:@"Badger %i", 123];
  3.     BOOL equalA = (foo == bar);//<equalA = NO
  4.     BOOL equalB = [foo isEqual:bar];//<equalB = YES
  5.     BOOL equalC = [foo isEqualToString:bar];//<equalC = YES
复制代码
在NSObject协议中有两个用于判定等同性的关键方法:
  1. - (BOOL)isEqual:(id)object;
  2. - (NSUInteger)hash;
复制代码
NSObject类对这两个方法默认实现是:当且仅当其“指针值”完全相同时,这两个对象才相等。如果利用isEqual方法判定两个对象相等,那么其hash方法也必须返回同一个值,但是如果两个对象的hash方法返回同一个值,那么isEqual方法未必会认为两者相等。
容器中可变类的等同性
在容器中放入可变类对象时,把某个对象放入collection后,就不应再改变其哈希码了。collection会把各个对象按照其哈希码装到差别的箱子中去,如果在装入箱子后改变其哈希码,那么对于所处的这个箱子来说就是一个错误。
要点:

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

“类族”是一种很有用的模式,可以隐藏“抽象基类”背后的实现细节。OC中的系统框架广泛利用这种模式,这里我们以UIButton为例阐明。
当我们想要创建一个按钮的时间,我们必要利用下面这个类方法:
  1. + (UIButton*)buttonWithType:(UIButtonType)type;
复制代码
该方法返回的对象,其范例取决于传入的按钮范例。然而,不管返回什么范例的对象,他们都继承自同一个基类:UIbutton,这么做的意义是让利用者不用关心创建的按钮继承于UIButton的那一个子类,只需明确如果创建按钮等问题即可。
创建类族

下面我将举例展示如何创建类族:
  1. //定义员工类型
  2. typedef NS_ENUM(NSUInteger, EOCEmployeeType) {
  3.     EOCEmployeeTypeDeveloper,
  4.     EOCEmployeeTypeDesiner,
  5.     EOCEmployeeTypeFinance
  6. };
  7. @interface EOCEmployee : NSObject
  8. //定义属性
  9. @property (nonatomic, copy) NSString *name;
  10. @property (nonatomic, assign) NSUInteger salary;
  11. //定义方法
  12. + (EOCEmployee *)employeeWithType:(EOCEmployeeType)type;
  13. - (void)doADaysWork;
  14. @end
复制代码
  1. @implementation EOCEmployee
  2. + (EOCEmployee *)employeeWithType:(EOCEmployeeType)type {
  3.     switch (type) {
  4.         case EOCEmployeeTypeDeveloper:
  5.             return [EOCEmployeeTypeDeveloper new];
  6.         case EOCEmployeeTypeDesiner:
  7.             return [EOCEmployeeTypeDesiner new];
  8.         case EOCEmployeeTypeFinance:
  9.             return [EOCEmployeeTypeFinance new];
  10.     }
  11. }
  12. - (void)doADaysWork {
  13.     // Subclasses implement this.
  14. }
  15. @end
复制代码
  1. @interface EOCEmployeeTypeDeveloper : EOCEmployee
  2. @end
  3. @implementation  EOCEmployeeTypeDeveloper
  4. - (void)doADaysWork {
  5.         [self writeCode];
  6. }
  7. @end
复制代码
这个例子中,基类实现了一个类方法,该方法根据创建的雇员类别分配好对应的雇员类实例,这种“工厂模式”是创建类族的办法之一。如果你想创建的类中没有init初始化的方法,那么这就是在暗示你该类的实例也许不应该由用户直接创建。总而言之,以后创建对象肯定不要被其的表象迷惑住了,你可能觉得自己创建了某个类的实例,然而实际上创建的却是其子类的实例。
Cocoa里的类族

系统框架中有很多类族,就用我们经常利用的NSArray和NSMutableArray来说,这样来看,它是两个抽象基类,但是他们两个拥有相同的方法,这个方法可能就是他们共同类族中的方法,而可变数组的特别方法就是只实用于可变数组的方法其他的共同方法可能就是类族中的方法
在利用NSArray的alloc方法来获取实例时,该方法首先会分配一个属于某个类的实例,此实例充当一个“占位数组”,也就是说,你把这个位置是先分配给其类族的,后来其类族才将这个位置分配给你创建的具体数据范例的。所以像这些类的背后其实是一个类族,在对一些if条件举行判定的时间肯定要留意,例如:
  1. id maybeAnArray = /* ... */;
  2. if ([maybeAnArray class] == [NSArray class]) {
  3.         //Will never be hit
  4. }
复制代码
利用这种方法来判定两个类是否属于同一类族很明显是错的,因为NSArray是一个类族,NSArray初始化返回的对象并非NSArray类,而是隐藏在类族公共接口中的某个内部范例。
OC中也提供了isKindOfClass方法来判定实例所属的类是否位于类族之中。
手动增加实体子类的规则

要点


第11条:明白objc_msgSend的作用

在对象上调用方法是OC中经常利用的功能。用OC中的术语来说,叫做“传递消息”。消息有“名称”或“选择子”,可以担当参数,而且可能有返回值。
OC是C的超集,这里我们先明白C语言的函数调用方式,C语言采用“静态绑定”,在编译期就能决定运行时所应调用的函数,如下图所示:

这样写的话,如果不思量“内联”,编译器在编译代码是就已经知道步伐中有printHello和printGoodbye两个函数了,就会直接生成调用这些函数的指令,而函数地址实际上是硬编码在指令中。
   内联是一种编译器优化技术。当一个函数被声明为内联函数时,编译器在编译过程中会实验将函数的代码直接嵌入到调用该函数的地方,而不是像普通函数调用那样通过函数指针举行跳转和返回。(这里笔者仅上网查询参考,相识不敷深入)
  

如果按照上图中的写法,就得利用“动态绑定”,因为所要调用的函数直到运行期才气确定。在这张图片中只有一个函数调用指令,待调用的函数地址无法编码在指令之中,而必要在运行期读取出来。
在OC中,如果向某对象传递消息,那就会利用动态绑定机制来决定必要调用的方法。在底层,所有的方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法完全于运行期决定,甚至可以在步伐运行时改变,这些特性让OC成为了一门真正的动态语言
给对象发送消息
  1. id returnValue = [someObject messageName:parameter];
复制代码
在上述例子中,someObject叫做“担当者”,messageName叫做“选择子”,选择子和参数喝起来称为“消息”。编译器看到这个消息以后,将其转化为一条标准的C语言函数调用,所调用的函数乃事消息传递机制中的核心函数,叫做objc_magSend,它的原型如下:
  1. void objc_magSend(id self, SEL cmd, ...)
复制代码
这是个“ 参数个数可变的两数” 。能担当两个或两个以上的参数。第一 个参数代表吸收者,第二个参数代表选择子(SEL 是选择子的范例),后续参数就是消息中的 那些参数,其顺序稳定。选择子指的就是方法的名字。“选择子〞与“方法” 这两个词经常瓜代利用。编译器会吧刚刚谁人例子中的消息转换成如下函数:
  1. id returnValue = objc_magSend(someObject, @selector(messageName:), parameter);
复制代码
objc _msgSend函数会依据吸收者与选择子的范例来调用得当的方法。为了完成此利用该方法必要在吸收者所属的类中征采其“方法列表”(list ofmethods),如果能找到与选择-名称符合的方法,就跳至其实现代码。若是找不到,那就沿着继承体系继承向上查找,等到合适的方法之后再跳转。如果最终还是找不到符合的方法,那就实行“消息转发”(messagforwarding)利用。消息转发将在第 12 条中详解。
界限情况:

要点:

第12条:明白消息转发机制

这一条我们主要明白当对象在收到无法解读的消息之后会发生什么情况
动态方法剖析

对象在收到无法解读的消息后,首先将调用其所属类的下列方法:
  1. + (BOOL)resolveInstanceMethod:(SEL)selector
复制代码
这个方法的参数就是谁人未知的选择子,其返回值为Boolean范例,表示这个类是否能新增一个实例方法用以处理此选择子。
如果尚未实现的方法是一个类方法,则会调用另一个方法:
  1. - (id)forwardingTargetForSelector:(SEL)aSelector;
复制代码
备援担当者

当前担当者另有第二次机会可以处理未知的选择子,在这一步中,运行期系统会问它:能不能把这条消息转给其他担当者来处理。与该步骤对应的处理方法如下:
  1. - (id)forwardingTargetForSelector:(SEL)selector
复制代码
若当前担当者能找到备援对象,则将其返回,若找不到,就返回nil。
完备的消息转发

如果已经来到这一步,就只能利用完备的消息转发机制。首先创建NSInvocation对象,把尚未处理的消息有关的全部细节都封于其中。包罗选择子,目的以及参数。在出发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派给目的对象。此步骤会调用下列方法来转发消息:
  1. - (void)forwardInvocation:(NSInvocation*)invocation
复制代码
全流程


要点


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4