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

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

内容简介:级别: ★★☆☆☆标签:「iOS」「OC」「Objective-C」作者:MrLiuQ

级别: ★★☆☆☆

标签:「iOS」「OC」「Objective-C」

作者:MrLiuQ

上一篇:

iOS 编写高质量Objective-C代码(一) 介绍了提高OC可读性和可维护性的几个小技巧。

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

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

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

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

~下文中,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 :拥有 gettersetter 方法
    • readonly :仅拥有 getter 方法
  3. 内存管理:
    • assign :对“纯量类型”做简单赋值操作( NSIntegerCGFloat 等)。
    • strong :强拥有关系,设置方法 保留新值,并释放旧值
    • weak :弱拥有关系,设置方法 不保留新值,不释放旧值 。当指针指向的对象销毁时,指针置 nil
    • copy :拷贝拥有关系,设置方法不保留新值,将其拷贝。
    • unsafe_unretained :非拥有关系,目标对象被释放,指针不置 nil ,这一点和 assign 一样。区别于 weak
  4. 方法名:
    getter=<name>
    setter=<name>
    
@property (nonatomic, getter=isOn) BOOL on;
复制代码

在iOS开发中,99.99..%的属性都会声明为nonatomic。 一是 atomic 会严重影响性能, 二是 atomic 只能保证读/写操作的过程是可靠的,并不能保证线程安全。 关于第二点可以参考我的博客: iOS 为什么属性声明为atomic依然不能保证线程安全?

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

  1. 实例变量( _属性名 )访问对象的场景:
    • initdealloc 方法中,总是应该通过访问实例变量读写数据
    • 没有重写 gettersetter 方法、也没有使用 KVO 监听
    • 好处:不走OC的方法派发机制,直接访问内存读写,速度快,效率高。

For Example:

- (instancetype)initWithDic:(NSDictionary *)dic {
    
    self = [super init];
    
    if (self) {
        
        _qi = dic[@"qi"];
        _share = dic[@"share"];
    }
    
    return self;
}
复制代码
  1. 用存取方法访问对象的场景:
    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] 的返回永远不会是 NSArrayNSArray 是一个类族,返回的值一直都是 NSArray的实体子类 。大部分collection类都是某个类族中的 抽象基类 所以上面的if想要有机会执行的话要改成

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

这样判断的意思是, maybeAnArray 这个对象是否是 NSArray 类族中的一员 使用类族的好处:可以把实现细节隐藏在一套简单的公共接口后面

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

先引入runtime类库

#import <objc/runtime.h>
复制代码

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

objc_AssociationPolicy(关联策略类型) 等效的@property属性
OBJC_ASSOCIATION_ASSIGN 等效于 assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC 等效于 nonatomic, retain
OBJC_ASSOCIATION_COPY_NONATOMIC 等效于 nonatomic, copy
OBJC_ASSOCIATION_RETAIN 等效于 retain
OBJC_ASSOCIATION_COPY 等效于 copy

三个方法管理关联对象:

  • 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
unrecognized selector sent to instance xxxx

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

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

<objc/runtime.h>里的两个常用的方法:

  • 获取给定类的指定实例方法:
/** 
 * 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;
复制代码

在<objc/runtime.h>中能看到他的实现:

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)。那什么是元类呢?元类是类对象的类。也可以换一种容易理解的说法:

runtime
runtime

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

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

可以总结如下:

  1. 每一个 Class 都有一个 isa指针 指向一个唯一的 Meta Class(元类)
  2. 每一个 Meta Classisa指针 都指向最上层的 Meta Class ,这个 Meta ClassNSObjectMeta Class 。(包括 NSObject的Meta Classisa指针 也是指向的 NSObjectMeta Class )
  3. 每一个 Meta Classsuper class 指针指向它原本 ClassSuper ClassMeta Class (这里最上层的 NSObjectMeta Classsuper class 指针还是指向自己)
  4. 最上层的 NSObject Classsuper class 指向 nil

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

关注我们的途径有:

QiShare(简书)

QiShare(掘金)

QiShare(知乎)

QiShare(GitHub)

QiShare(CocoaChina)

QiShare(StackOverflow)

QiShare(微信公众号)


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

The Intersectional Internet

The Intersectional Internet

Safiya Umoja Noble、Brendesha M. Tynes / Peter Lang Publishing / 2016

From race, sex, class, and culture, the multidisciplinary field of Internet studies needs theoretical and methodological approaches that allow us to question the organization of social relations that ......一起来看看 《The Intersectional Internet》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具