iOS 编写高质量Objective-C代码(二)

栏目: Objective-C · 发布时间: 6年前

内容简介:上一篇:这篇将从面向对象的角度分析如何提高OC的代码质量。属性(@property)是OC的一项特性。

上一篇: iOS 编写高质量Objective-C代码(一)

这篇将从面向对象的角度分析如何提高OC的代码质量。

一、理解“ 属性 ”这一概念

属性(@property)是OC的一项特性。

@property:编译器会自动生成实例变量和getter和setter方法。

下文中,getter和setter方法合称为存取方法

For Example:

@property (nonatomic, strong) UIView *qiShareView;

等价于:

@synthesize qiShareView = _qiShareView;
- (UIView *)qiShareView;
- (void)setQiShareView:(UIView *)qiShareView;

如果不希望自动生成存取方法和实例变量,那就要使用@dynamic关键字

@dynamic qiShareView;

属性特质有四类:

1.原子性:默认为atomic

  • nonatomic:非原子性,读写时不加同步锁

  • atomic:原子性,读写时加同步锁

2.读写权限:默认为readwrite

  • readwrite:拥有getter和setter方法

  • readonly:仅拥有getter方法

3.内存管理:

  • assign:对“纯量类型”做简单赋值操作(NSInteger、CGFloat等)。

  • strong:强拥有关系,设置方法 保留新值,并释放旧值。

  • weak:弱拥有关系,设置方法 不保留新值,不释放旧值。当指针指向的对象销毁时,指针置nil。

  • copy:拷贝拥有关系,设置方法不保留新值,将其拷贝。

  • unsafe_unretained:非拥有关系,目标对象被释放,指针不置nil,这一点和assign一样。区别于weak

4.方法名:

  • getter= :指定get方法的方法名,常用

  • setter=

    :指定set方法的方法名,不常用

    例如:

@property (nonatomic, getter=isOn) BOOL on;

在iOS开发中,99.99..%的属性都会声明为nonatomic。

一是atomic会严重影响性能,

二是atomic只能保证读/写操作的过程是可靠的,并不能保证线程安全。

关于第二点可以参考我的博客:

iOS 为什么属性声明为atomic依然不能保证线程安全

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

1.实例变量( _属性名 )访问对象的场景:

  • 在init和dealloc方法中,总是应该通过访问实例变量读写数据

  • 没有重写getter和setter方法、也没有使用KVO监听

  • 好处:不走OC的方法派发机制,直接访问内存读写,速度快,效率高。

For Example:
- (instancetype)initWithDic:(NSDictionary *)dic {    
   self = [super init];    
   if (self) {

       _qi = dic[@"qi"];
       _share = dic[@"share"];
   }    
   return self;
}

2.用存取方法访问对象的场景:

  • 重写了getter/setter方法(比如:懒加载)

  • 使用了KVO监听值的改变

For Example:
- (UIView *)qiShareView {  
   if (!_qiShareView) {

       _qiShareView = [UIView new];
   }    return _qiShareView;
}

三、理解“对象等同性”

思考下面输出什么?

NSString *aString = @"iPhone 8";
NSString *bString = [NSString stringWithFormat:@"iPhone %i", 8];
NSLog(@"%d", [aString isEqual:bString]);
NSLog(@"%d", [aString isEqualToString:bString]);
NSLog(@"%d", aString == bString);

答案是110

==操作符只是比较了两个指针所指对象的地址是否相同,而不是指针所指的对象的值

所以最后一个为0

四、以类族模式隐藏实现细节

为什么下面这个例子的if永远为false?

id maybeAnArray = @[];
if ([maybeAnArray class] == [NSArray class]) {
     //Code will never be executed
}

因为[maybeAnArray class] 的返回永远不会是NSArray,NSArray是一个类族,返回的值一直都是NSArray的实体子类。大部分collection类都是某个类族中的抽象基类

所以上面的if想要有机会执行的话要改成

id maybeAnArray = @[];
if ([maybeAnArray isKindOfClass [NSArray class]) {
      // Code probably be executed
}

这样判断的意思是,maybeAnArray这个对象是否是NSArray类族中的一员

使用类族的好处:可以把实现细节隐藏在一套简单的公共接口后面

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

先引入runtime类库

#import 
  
    

objc_AssociationPolicy(对象关联策略类型):

iOS 编写高质量Objective-C代码(二)

三个方法管理关联对象:

objc_setAssociatedObject(设置关联对象)

/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 */
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)

objc_getAssociatedObject(获得关联对象)

/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 */
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

objc_removeAssociatedObjects(去除关联对象)

/** 
 * Removes all associations for a given object.
 * 
 * @param object An object that maintains associated objects.
 * 
 * @note The main purpose of this function is to make it easy to return an object 
 *  to a "pristine state”. You should not use this function for general removal of
 *  associations from objects, since it also removes associations that other clients
 *  may have added to the object. Typically you should use \c objc_setAssociatedObject 
 *  with a nil value to clear an association.
 * 
 */
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)

小结:

  • 可以通过“关联对象”机制可以把两个对象联系起来

  • 定义关联对象可以指定内存管理策略

  • 应用场景:只有在其他做法(代理、通知等)不可行时,才会选择使用关联对象。这种做法难于找bug。

  • 但也有具体应用场景:比如说前几篇说到 控制Button响应时间间隔 的demo中就遇到了:附上 链接

六、理解objc_msgSend(对象的消息传递机制)

首先我们要区分两个基本概念:

  • 1 .静态绑定(static binding):在编译期就能决定运行时所应调用的函数。代表语言:C、C++等

  • 2 .动态绑定 (dynamic binding):所要调用的函数直到运行期才能确定。代表语言:OC、swift等

OC是一门强大的动态语言,它的动态性体现在它强大的runtime机制上。

解释:在OC中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的 C语言 函数,然而对象收到消息后,由运行期决定究竟调用哪个方法,甚至可以在程序运行时改变,这些特性使得OC成为一门强大的动态语言。

底层实现:基于C语言函数实现。

实现的基本函数是objc_msgSend,定义如下:

void objc_msgSend(id self, SEL cmd, ...) 

这是一个参数个数可变的函数,第一参数代表接受者,第二个参数代表选择子(OC函数名),之后的参数就是消息中传入的参数。

举例:git提交

id return = [git commit:parameter];

上面的方法会在运行时转换成如下的OC函数:

id return = objc_msgSend(git, @selector(commit), parameter);

objc_msgSend函数会在接收者所属的类中搜寻其方法列表,如果能找到这个跟选择子名称相同的方法,就跳转到其实现代码,往下执行。若是当前类没找到,那就沿着继承体系继续向上查找,等找到合适方法之后再跳转 ,如果最终还是找不到,那就进入消息转发(下一条具体展开)的流程去进行处理了。

可是如果每次传递消息都要把类中的方法遍历一遍,这么多消息传递加起来肯定会很耗性能。所以以下讲解OC消息传递的优化方法。

OC对消息传递的优化:

快速映射表(缓存)优化:

objc_msgSend在搜索这块是有做缓存的,每个OC的类都有一块这样的缓存,objc_msgSend会将匹配结果缓存在快速映射表(fast map)中,这样以来这个类一些频繁调用的方法会出现在fast map 中,不用再去一遍一遍的在方法列表中搜索了。

尾调用优化:

原理:在函数末尾调用某个不含返回值函数时,编译器会自动把栈空间的内存重新进行分配,直接释放所有调用函数内部的局部变量,存储调转至另一函数需要的指令码,然后直接进入被调用函数的地址。(从而不需要为调用函数准备额外的“栈帧”(frame stack))

好处:最大限度的合理的分配使用的资源,避免过早发生栈溢出的现象。

(这一块偏底层,以上是小编的个人理解。路过的大神如果有更好的更深的见解,欢迎大神留言与我们讨论)

七、理解消息转发机制

首先区分两个基本概念:

  • 1 .消息传递:对象正常解读消息,传递过去(见上一条)。

  • 2 .消息转发:对象无法解读消息,之后进行消息转发。

消息转发完整流程图:

iOS 编写高质量Objective-C代码(二)

流程解释:

  • 第一步:调用resolveInstanceMethod:征询接受者(所属的类)是否可以添加方法以处理未知的选择子?(此过程称为动态方法解析)若有,转发结束。若没有,走第二步。

  • 第二步:调用forwardingTargetForSelector:询问接受者是否有其他对象能处理此消息。若有,转发结束,一切如常。若没有,走第三步。

  • 第三步:调用forwardInvocation:运行期系统将消息封装到NSInvocation对象中,再给接受者一次机会。

  • 最后:以上三步还不行,就抛出异常:unrecognized selector sent to instance xxxx

八、用“方法调配技术”调试“黑盒方法”

方法调配(Method Swizzling):使用另一种方法实现来替换原有的方法实现。(实际应用中,常用此技术向原有实现中添加新的功能。)

里的两个常用的方法:

获取给定类的指定实例方法:

/** 
 * Returns a specified instance method for a given class.
 * 
 * @param cls The class you want to inspect.
 * @param name The selector of the method you want to retrieve.
 * 
 * @return The method that corresponds to the implementation of the selector specified by 
 *  \e name for the class specified by \e cls, or \c NULL if the specified class or its 
 *  superclasses do not contain an instance method with the specified selector.
 *
 * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
 */
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)

交换两种方法实现的方法:

/** 
 * Exchanges the implementations of two methods.
 * 
 * @param m1 Method to exchange with second method.
 * @param m2 Method to exchange with first method.
 * 
 * @note This is an atomic version of the following:
 *  \code 
 *  IMP imp1 = method_getImplementation(m1);
 *  IMP imp2 = method_getImplementation(m2);
 *  method_setImplementation(m1, imp2);
 *  method_setImplementation(m2, imp1);
 *  \endcode
 */
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 

利用这两个方法就可以交换指定类中的指定方法。在实际应用中,我们会通过这种方式为既有方法添加新功能。

For Example:交换method1与method2的方法实现

Method method1 = class_getInstanceMethod(self, @selector(method1:));
Method method2 = class_getInstanceMethod(self, @selector(method2:));
method_exchangeImplementations(method1, method2);

九、理解“类对象”的用意

Objective-C类是由Class类型来表示的,实质是一个指向objc_class结构体的指针。它的定义如下:

typedef struct objc_class *Class;

中能看到他的实现:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;  //!< 指向metaClass(元类)的指针

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;  //!< 父类
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;  //!< 类名
    long version                                             OBJC2_UNAVAILABLE;  //!< 类的版本信息,默认为0
    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;
/* Use `Class` instead of `struct objc_class *` */

此结构体存放的是类的“元数据”(metadata),例如类的实例实现了几个方法,父类是谁,具备多少实例变量等信息。

这里的isa指针指向的是另外一个类叫做元类(metaClass)。那什么是元类呢?元类是类对象的类。也可以换一种容易理解的说法:

  1. 当你给对象发送消息时,runtime处理时是在这个对象的类的方法列表中寻找

  2. 当你给类发消息时,runtime处理时是在这个类的元类的方法列表中寻找

我们来看一个很经典的图来加深理解:

iOS 编写高质量Objective-C代码(二)

可以总结如下:

  1. 每一个Class都有一个isa指针指向一个唯一的Meta Class(元类)

  2. 每一个Meta Class的isa指针都指向最上层的Meta Class,这个Meta Class是NSObject的Meta Class。(包括NSObject的Meta Class的isa指针也是指向的NSObject的Meta Class)

  3. 每一个Meta Class的super class指针指向它原本Class的 Super Class的Meta Class (这里最上层的NSObject的Meta Class的super class指针还是指向自己)

  4. 最上层的NSObject Class的super class指向 nil

最后,特别致谢《Effective Objective-C 2.0》第二章

作者: MrLiuQ

审校: QiShare团队

链接:https://www.jianshu.com/p/373987700962


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Art and Science of CSS

The Art and Science of CSS

Jonathan Snooks、Steve Smith、Jina Bolton、Cameron Adams、David Johnson / SitePoint / March 9, 2007 / $39.95

Want to take your CSS designs to the next level? will show you how to create dozens of CSS-based Website components. You'll discover how to: # Format calendars, menus and table of contents usin......一起来看看 《The Art and Science of CSS》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具