iOS-有效编写高质量Objective-C方法-三

欢迎你们关注个人公众号,我会按期分享一些我在项目中遇到问题的解决办法和一些iOS实用的技巧,现阶段主要是整理出一些基础的知识记录下来
编程

文章也会同步更新到个人博客:
ppsheep.com安全

本篇文章,主要是对OC中的一些原理的讲解,可能会有一些枯燥,可是真正当你理解时,会有一种豁然开朗的感受。这里会涉及到 对象,属性,消息以及运行期的一些介绍,只有咱们真正理解了这些原理以后,咱们的开发水平才会进一步提高,而不是止步于view的简单编写,view的编写想要写得好,也须要了解这一些原理。框架

理解属性(property)这一律念

在开始讲属性以前,咱们先来理解一下几个概念:函数

  • 对象:对象是"基本的构造单元",在OC中,咱们常用对象来储存和传递数据
  • 消息传递:在对象之间传递数据并执行任务的过程就叫作消息传递
  • OC Runtime:当应用程序运行起来,为其提供相关支持的代码叫作"运行环境(Runtime)",它提供了一些使得对象之间可以传递数据的函数,而且包含了建立类实例须要的全部逻辑

上面三个概念,在OC编程中尤为重要,虽然如今可能你没有很深入的理解,可是随着学习深刻,你确定可以体会到。性能

属性

"属性(property)"相信你们都很熟悉,是OC中用来存储对象的数据的实例变量。实例变量通常是经过"存取方法"来访问,"设置方法"来设置实例变量。在以前咱们已经讲过,对于实例变量,若是是自己访问,那么读取最好是直接读取(采用下划线方式直接访问),而设置最好使用属性来设置。具体的,能够参见上一篇(iOS-有效编写高质量Objective-C方法-二)。学习

对于一些简单的概念性的东西我就不讲了,给出结论就行:atom

  • 不必定要在接口中或者说是声明文件中定义好全部的实例变量,能够在实现文件中定义,以保护与类实现相关的内部信息
  • 属性按照标准的命名方式,在编译器编译期间会自动加上存取方法

接下来,咱们来讲几个关键字:spa

@synthesize线程

咱们能够在代码中经过这个关键字来指定咱们想要的实例变量代理

例如:在头文件中

@interface : NSObject

@property(nonatomic, copy) NSString *name;

@end复制代码

这个属性,在咱们运行期环境下,生成的实例变量为_name,可是咱们在.m中并不想使用这个名称,那么咱们在实现文件里就能够这样写:

@implementation

@synthesize name =  _myName

@end复制代码

那么咱们在.m实现文件中,均可以直接使用_myName来操做属性name

不过为了书写的规范,和团队之间协做,我仍是建议按照规范的OC代码风格来编写代码,团队成员之间,一看就可以看清楚代码

@dynamic

这个关键字 是用来阻止编译器自动合成存取方法,不过这个关键字我都用的不多,上面的关键字一样的,也使用较少。

这个关键字的意思是:阻止编译器合成属性所对应的实例变量,也不要合成实例变量的存取方法

这里讲一下实例变量就是带下划线的_name而属性是经过property声明的name

这两个须要区分开来

属性特质

nonatomic

所谓的属性特质,就是指咱们在申明属性的时候,property括号中跟的一些关键字

@property(nonatomic, readwrite, copy);复制代码

其中的nonatomic,readwrite,copy这些都是属性特质,咱们先来讲nonatomic

这个关键字叫作属性的原子性,通俗来讲,这个关键字主要是来控制属性的同步锁
同步锁:不一样的线程在读取属性的时候,若是属性是经过atomic来声明的,那么这两个线程老是可以读到属性的有效值(注意这里是有效的属性值,并无说是正确的属性值),若是属性是经过nonatomic声明的,那么不一样的线程读取属性值时,若是有线程正在修改该属性的值,另外的线程正在读取属性值,那么就可能将还未修改完成的属性值读取出来(这里是还没有修改完成的属性值,有可能读出一个彻底没有任何意义的属性值)

那么又要来讲一说,为何咱们老是看到在编写iOS程序时,属性老是使用nonatomic来声明的呢,这是由于在iOS中使用同步锁的开销太大,这会带来性能问题。在通常状况下,咱们并不要求属性必须具备原子性,由于这个原子性并非说就是"线程安全了",若是咱们须要实现线程安全,那么还须要使用更为底层的同步锁定机制才行,即使是使用atomic来声明,不一样的线程仍是可能读取到不一样的属性值,只是说这个属性值是有效的,有意义的。

因此咱们在开发iOS程序时,仍是使用nonaomic来声明,可是在macOS程序开发中,却不会遇到这种性能瓶颈,性能配置不同嘛

readwrite/readonly

这个属性特质,咱们根据字面意思就能看出来,就是声明属性权限的,这个也没什么好说的了。

strong/copy/assign/weak

这个多是咱们平时用的最多的,也是思考最多的,其实平时咱们怎么用,都是知道的,可是为何这么用呢?

assign:"设置方法"只会针对"纯量类型"进行赋值,例如CGFloat、NSInteger这种

strong:此特质象征了一种拥有关系,在"设置方法"中,这种属性是先保留新值,而且释放旧值,而后将新值设置上去

copy:这种方法和strong类型有点类似,可是它并非保留新值,而是直接就想新值拷贝,当属性为NSString类型时,咱们常用这种,那么为何咱们在NSString常用拷贝呢,觉得咱们在设置时,可能会传进来一个NSMutableString对象,这个对象是NSString的子类,是能够赋值给NSString的,若是咱们不使用拷贝,那么当外部改变NSMultableString值时,咱们的属性值也会直接被修改掉,因此这时,咱们就须要拷贝一份不可变的

weak:这个是一种弱引用,为这种方法设置时,既不会保留新值,也不会释放旧值,当属性所指的对象销毁时,属性值也会被清空,在ViewController中定义view时咱们常常会使用到weak,可是咱们常常仍是将view声明为strong,固然这使用起来不会有很大的影响,可是咱们的应用在运行过程当中,就会产生不少的没用view属性值没有被释放掉,占用无效内存。因此建议你们在使用view时,仍是声明为weak

@property(nonatomic,weak) UILable *lable;

//初始化lable时

UILable * lable = [[UILable alloc] init];
[self.view addSubview: lable];
self.lable = lable;复制代码

方法名

在咱们定义的属性为Boolean值时,咱们的习惯是获取方法,通常是"is"
开头,那么咱们就能够在声明时,这样书写

@property(nonatomic,getter=isOn) Bool on;复制代码

属性的获取方法,就成了isOn;

咱们在属性中定义了属性特质,那么咱们在书写赋值时,就应该严格按照属性特质赋值,例如,咱们有一个初始方法,须要对咱们的属性NSString name赋值

- (instancetype)initWithName:(NSString *)name{
    if(self = [super init]){
    //此处就应该使用copy来对name赋值
        _name = [name copy];
    }
}复制代码

以"类族模式"隐藏实现细节

类族是一种隐藏抽象基类背后实现细节的颇有用的模式。。那么什么叫作类族呢?

举个例子:

在UIKit中有一个名叫UIButton的类,若是想要建立按钮,咱们能够调用一个类方法

+ (UIButton *)buttonWithType:(UIButtonType)type;复制代码

该方法返回的对象,取决于传入按钮的类型,然而,无论传入的是什么类型,返回的类都是继承自同一个基类:UIButton。 这样,全部继承自UIButton的类组成了一个类族。

在系统框架中,使用到了不少的类族。。

那么为何要这样作呢?
UIButton这个例子,在实现时,使用者无需关心建立出来的按钮是属于哪个类,也不用考虑按钮的绘制细节,我只须要知道,我怎么建立按钮,如何设置标题。如何增长点击操做等。

建立类族

咱们如今来假设一个处理雇员的类,每一个雇员都拥有本身的名字和薪水这两个属性,管理者能够命令器执行平常工做。可是,各类雇员的工做内容却不一样,经理在带领雇员作项目的时候,无需关心每一个人怎样完成本身的工做,只须要指示其开工便可。

首先咱们定义一个基类:

typedef NS_ENUM(NSUinteger, PPSEmployeeType){
    PPSEmployeeTypeDeveloper,
    PPSEmployeeTypeDesigner,
    PPSEmployeeTypeFinance,
}

@interface PPSEmployee    :    NSObject

@property (nonatomic, copy) NSStirng *name;
@property (nonamotic, assign) NSUInteger salary;

//建立方法
+ (PPSEmployee *)employeeWithType:(PPSEmployeeType) type;

//指示开工
- (void)doADaysWork;

@end复制代码
@implementation PPSEmployee

+ (PPSEmployee *)employeeWithType:(PPSEmployeeType) type{
    switch (type){
        case PPSEmployeeTypeDeveloper:
            return [PPSEmployeeDeveloper new];
            break;
        case PPSEmployeeTypeDesigner:
            return [PPSEmployeeDesigner new];
            break;
        case PPSEmployeeTypeFinance:
            return [PPSEmployeeFinance new];
            break;
    }
}

- (void)doADaysWork{
    // 子类覆写
}

@end复制代码

每一个实体子类都是从基类继承而来

@interface PPSEmployeeDeveloper    :    PPSEmployee
@end复制代码
@implementation PPSEmployeeDeveloper

- (void)doADaysWork{
    [self coding];
}

@end复制代码

咱们上面实现的方式,是根据雇员的类别,建立雇员类的实例,这实际上是一种工厂模式。在Java中,咱们知道这种方式通常是经过抽象类来实现,可是OC中没有抽象类这一说,因而开发者一般会在文档中写明使用方法。

在Cocoa中 有不少的类族 大部分的集合类型都是类族 咱们在一个对象是不是属于某一个类时,若是咱们采用下面的方式,每每得不到咱们想要的效果:

id myArr = @[@"a",@"b"];
if([myArr class] == [NSArray class]){
    //这里永远不会跑到 由于 咱们知道这里[myArr class]返回的永远是NSArr的一个子类,NSArray只是一个基类
}复制代码

固然,咱们要作这个判断的时候,应该都知道是使用isKindOfClass 这个方法,这个方法实际上是用来判断是不是同一类族,而不是某个类

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

有时候咱们须要在对象中存放相关的信息,这时候咱们能想到的方法就是,建立一个子类,而后咱们使用的时候 直接使用子类。 可是并非全部状况都可以这样作,有时候类的实例多是因为某种机制所创建的,咱们开发者是没有办法建立出本身创建的子类的实例。幸亏,咱们能够经过OC的一项强大的特性"关联对象"来解决这个问题。

那么什么事关联对象呢?

咱们能够给某个对象关联许多其余对象,这些对象经过"键"来区分。存储值的时候,能够经过指明存储策略来维护内存,存储策略就是你存储的是一个NSString啊,那你就该把从存储策略改成copy,相似于这种

下面是对象关联类型:

关联类型 等效的@property属性
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic,copy
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic,retain
OBJC_ASSOCIATION_COPY copy
OBJC_ASSOCIATION_RETAIN retain

管理关联对象的相关方法:

  • void objc _ setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) 使用此方法 以给定的键和策略为某对象设置关联对象
  • void objc _ getAssociatedObject(id object, void *key) 使用此方法根据给定的键从某对象中获取相关的关联对象的值
  • void objc_removeAssociatedObject(id object)使用此方法移除指定对象的所有关联对象

使用这种方法时,咱们能够把某对象想象成一个NSDictionary,把关联到该对象的值理解为字典中的条目,那么这些关联对象,就至关于设置字典里的值和获取字典里的值了

使用举例

在iOS中,咱们若是想要使用UIAlertView 咱们须要这样定义

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"你肯定吗?" 
                                     message:@"可能没那么肯定吧" 
                                     delegate:self
                                     cancelButtonTitle:@"取消" 
                                     otherButtonTitles:@"继续", nil];
 [alert show];复制代码

而后咱们须要实现UIAlertView的代理,来进行操做的识别判断

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    if (buttonIndex == 0) {
        [self doCancle];
    }else{
        [self doContinue];
    }
}复制代码

这样写,如今看来是没有什么问题的,可是若是咱们须要在当前的一个类中,处理多个警告信息,那么代码将会变得复杂,咱们须要在delegate中判断当前的UIAlertView的信息,根据不一样的信息实行不一样的操做。

若是 咱们能在建立UIAlertView的时候 就将这些操做作好,那么,在delegate中咱们就不须要判断UIAlertView了。事实上这种方法是可行的。

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"你肯定吗?" 
                                 message:@"可能没那么肯定吧" 
                                 delegate:self 
                                 cancelButtonTitle:@"取消" 
                                 otherButtonTitles:@"继续", nil];
void (^block) (NSInteger) = ^(NSInteger buttonIndex){
    if (buttonIndex == 0) {
        NSLog(@"cancle");
    }else{
        NSLog(@"continue");
    }
};

//将block设置为UIAlertView的关联对象
objc_setAssociatedObject(alert, PPSMyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alert show];复制代码

咱们只须要在delegate中拿到block就行

void (^block)(NSInteger) =  objc_getAssociatedObject(alertView, PPSMyAlertViewKey);
 block(buttonIndex);复制代码
相关文章
相关标签/搜索