ios代码规范问题

news/2024/7/20 13:25:34 标签: 移动开发, 内存管理, runtime

为什么80%的码农都做不了架构师?>>>   hot3.png

首先 看一下代码

094300_wiXk_2429434.jpg

博主表示 一般没人会关心一些细节问题 ,但是程序员的话最好有点代码洁癖,这样不管对自己还是对以后维护自己开发过项目的人都有好处,废话不多说,直接开始看问题,首先这段代码肯定有问题,修改一下可以改成这样子:

094946_Uux6_2429434.jpg

下面对具体修改的地方,分两部分做下介绍:硬伤部分和优化部分 。因为硬伤部分没什么技术含量,为了节省大家时间,放在后面讲,大神请直接看优化部分。

优化部分

1)enum建议使用 NS_ENUM 和 NS_OPTIONS 宏来定义枚举类型,参见官方的 Adopting Modern Objective-C 一文:

//定义一个枚举
typedef NS_ENUM(NSInteger, CYLSex) {
    CYLSexMan,
    CYLSexWoman
};

2)age属性的类型:应避免使用基本类型,建议使Foundation数据类型,对应关系如下:

int -> NSInteger
  unsigned -> NSUInteger
  float -> CGFloat
  动画时间 -> NSTimeInterval

同时考虑到age的特点,应使用NSUInteger,而非int。 这样做的是基于64-bit 适配考虑,详情可参考出题者的博文《64-bit Tips》。

3)如果工程项目非常庞大,需要拆分成不同的模块,可以在类、typedef宏命名的时候使用前缀。

4)doLogIn方法不应写在该类中:虽然LogIn的命名不太清晰,但笔者猜测是login的意思,而登录操作属于业务逻辑,观察类名UserModel,以及属性的命名方式,应该使用的是MVC模式,并非MVVM,在MVC中业务逻辑不应当写在Model中。(如果是MVVM,抛开命名规范,UserModel这个类可能对应的是用户注册页面,如果有特殊的业务需求,比如:login对应的应当是注册并登录的一个Button,出现login方法也可能是合理的。)

5)doLogIn方法命名不规范:添加了多余的动词前缀。 请牢记:

如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用do,does这种多余的关键字,动词本身的暗示就足够了。

6)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中不要用with来连接两个参数:withAge:应当换为age:,age:已经足以清晰说明参数的作用,也不建议用andAge::通常情况下,即使有类似withA:withB:的命名需求,也通常是使用withA:andB:这种命名,用来表示方法执行了两个相对独立的操作(从设计上来说,这时候也可以拆分成两个独立的方法),它不应该用作阐明有多个参数,比如下面的:

//错误,不要使用"and"来连接参数
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
//错误,不要使用"and"来阐明有多个参数
- (instancetype)initWithName:(CGFloat)width andAge:(CGFloat)height;
//正确,使用"and"来表示两个相对独立的操作
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;

 

7)由于字符串值可能会改变,所以要把相关属性的“内存管理语义”声明为copy。(原因在下文有详细论述:用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?)

8)“性别”(sex)属性的:该类中只给出了一种“初始化方法” (initializer)用于设置“姓名”(Name)和“年龄”(Age)的初始值,那如何对“性别”(Sex)初始化?

Objective-C 有 designated 和 secondary 初始化方法的观念。 designated 初始化方法是提供所有的参数,secondary 初始化方法是一个或多个,并且提供一个或者更多的默认参数来调用 designated 初始化方法的初始化方法。举例说明:

@implementation CYLUser
  - (instancetype)initWithName:(NSString *)name
                           age:(int)age
                           sex:(CYLSex)sex {
      if(self = [super init]) {
          _name = [name copy];
          _age = age;
          _sex = sex;
      }
      return self;
  }
  - (instancetype)initWithName:(NSString *)name
                           age:(int)age {
      return [self initWithName:name age:age sex:nil];
  }
  @end

上面的代码中initWithName:age:sex: 就是 designated 初始化方法,另外的是 secondary 初始化方法。因为仅仅是调用类实现的 designated 初始化方法。

因为出题者没有给出.m文件,所以有两种猜测:1:本来打算只设计一个designated 初始化方法,但漏掉了“性别”(sex)属性。那么最终的修改代码就是上文给出的第一种修改方法。2:不打算初始时初始化“性别”(sex)属性,打算后期再修改,如果是这种情况,那么应该把“性别”(sex)属性设为readwrite属性,最终给出的修改代码应该是:

095309_X2wP_2429434.jpg

.h中暴露 designated 初始化方法,是为了方便子类化 (想了解更多,请戳--》 《禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship 中文翻译)》。)

9)按照接口设计的惯例,如果设计了“初始化方法” (initializer),也应当搭配一个快捷构造方法。而快捷构造方法的返回值,建议为instancetype,为保持一致性,init方法和快捷构造方法的返回类型最好都用instancetype。

10)如果基于第一种修改方法:既然该类中已经有一个“初始化方法” (initializer),用于设置“姓名”(Name)、“年龄”(Age)和“性别”(Sex)的初始值: 那么在设计对应@property时就应该尽量使用不可变的对象:其三个属性都应该设为“只读”。用初始化方法设置好属性值之后,就不能再改变了。在本例中,仍需声明属性的“内存管理语义”。于是可以把属性的定义改成这样

@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSUInter age;
@property (nonatomic, assign, readonly) CYLSex sex;

由于是只读属性,所以编译器不会为其创建对应的“设置方法”,即便如此,我们还是要写上这些属性的语义,以此表明初始化方法在设置这些属性值时所用的方式。要是不写明语义的话,该类的调用者就不知道初始化方法里会拷贝这些属性,他们有可能会在调用初始化方法之前自行拷贝属性值。这种操作多余而且低效。

11)initUserModelWithUserName如果改为initWithName会更加简洁,而且足够清晰。

12)UserModel如果改为User会更加简洁,而且足够清晰。

13)UserSex如果改为Sex会更加简洁,而且足够清晰。

硬伤部分

1)在-和(void)之间应该有一个空格

2)enum中驼峰命名法和下划线命名法混用错误:枚举类型的命名规则和函数的命名规则相同:命名时使用驼峰命名法,勿使用下划线命名法。

3)enum左括号前加一个空格,或者将左括号换到下一行

4)enum右括号后加一个空格

5)UserModel :NSObject 应为UserModel : NSObject,也就是:右侧少了一个空格。

6)@interface与@property属性声明中间应当间隔一行。

7)两个方法定义之间不需要换行,有时为了区分方法的功能也可间隔一行,但示例代码中间隔了两行。

8)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中方法名与参数之间多了空格。而且- 与(id)之间少了空格。

9)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中方法名与参数之间多了空格:(NSString*)name前多了空格。

10)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中(NSString*)name,应为(NSString *)name,少了空格。

11)doLogIn方法命名不清晰:笔者猜测是login的意思,应该是粗心手误造成的。

12)第二个@property中assign和nonatomic调换位置。

2. 什么情况使用 weak 关键字,相比 assign 有什么不同?

什么情况使用 weak 关键字?

1)在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性

2)自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong。在下文也有论述:《IBOutlet连出来的视图属性为什么可以被设置成weak?》

不同点:

1)weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。

2)assigin 可以用非OC对象,而weak必须用于OC对象

3. 怎么用 copy 关键字?

用途:

1)NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;

2)block也经常使用copy关键字,具体原因见官方文档:Objects Use Properties to Keep Track of Blocks:

block使用copy是从MRC遗留下来的“传统”,在MRC中,方法内部的block是在栈区的,使用copy可以把它放到堆区.在ARC中写不写都行:对于block使用copy还是strong效果是一样的,但写上copy也无伤大雅,还能时刻提醒我们:编译器自动对block进行了copy操作。

下面做下解释: copy此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为NSString时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个NSMutableString类的实例。这个类是NSString的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

用@property声明 NSString、NSArray、NSDictionary 经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

该问题在下文中也有论述:用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

4. 这个写法会出什么问题: @property (copy) NSMutableArray *array;

两个问题:
1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为copy就是复制一个不可变NSArray的对象;
2、使用了atomic属性会严重影响性能。

第1条的相关原因在下文中有论述《用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?》 以及上文《怎么用 copy 关键字?》也有论述。

第2条原因,如下:

该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明nonatomic可以节省这些虽然很小但是不必要额外开销。

在默认情况下,由编译器所合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备nonatomic特质,则不使用同步锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备nonatomic特质,那它就是“原子的”(atomic))。

在iOS开发中,你会发现,几乎所有属性都声明为nonatomic。

一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ( thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读到不同的属性值。

因此,开发iOS程序时一般都会使用nonatomic属性。但是在开发Mac OS X程序时,使用 atomic属性通常都不会有性能瓶颈。

5. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?

若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopyiog与NSMutableCopying协议。

6. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的。

@property 的本质是什么?

@property = ivar + getter + setter;

下面解释下:

“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)。

“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为Objective-C 2.0的一部分。 而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示:

编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。 所以你也可以这么说:

@property = getter + setter;

7. @protocol 和 category 中如何使用 @property

1)在protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性

2)category 使用 @property 也是只会生成setter和getter方法的声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数:

①objc_setAssociatedObject

②objc_getAssociatedObject

8. runtime 如何实现 weak 属性

要实现weak属性,首先要搞清楚weak属性的特点:

weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

那么runtime如何实现weak变量的自动置nil?

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

我们可以设计一个函数(伪代码)来表示上述机制:

objc_storeWeak(&a, b)函数:

objc_storeWeak函数把第二个参数--赋值对象(b)的内存地址作为键值key,将第一个参数--weak修饰的属性变量(a)的内存地址(&a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,

你可以把objc_storeWeak(&a, b)理解为:objc_storeWeak(value, key),并且当key变nil,将value置nil。

在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。

而如果a是由assign修饰的,则: 在b非nil时,a和b指向同一个内存地址,在b变nil时,a还是指向该内存地址,变野指针。此时向a发送消息极易崩溃。

9. @property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?

属性可以拥有的特质分为四类:

  • 原子性---nonatomic特质

在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备nonatomic特质,则不使用同步锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备nonatomic特质,那它就是“原子的” ( atomic) ),但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。

  • 读/写权限---readwrite(读写)、readooly (只读)

  • 内存管理语义---assign、strong、 weak、unsafe_unretained、copy

  • 方法名---getter=、setter=

getter=的样式:

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

( setter=这种不常用,也不推荐使用。故不在这里给出写法。)

  • 不常用的:nonnull,null_resettable,nullable

10. weak属性需要在dealloc中置nil么?

不需要。

在ARC环境无论是强指针还是弱指针都无需在deallco设置为nil,ARC会自动帮我们处理。

即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil:

11. @synthesize和@dynamic分别有什么作用?

1)@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;

2)@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

3)@dynamic告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺setter方法会导致程序崩溃;或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

转载于:https://my.oschina.net/rainwz/blog/511799


http://www.niftyadmin.cn/n/601836.html

相关文章

在Hadoop上发布spark作业

这次的例子是计算航空公司的平均延迟时间,并画图 直接上代码: import csv import matplotlib.pyplot as plt import matplotlib as mpl mpl.use("TkAgg") # Use TKAgg to show figures from StringIO import StringIO from datetime import d…

SVG入门

资源&#xff1a;www.chinasvg.com0. 传统图形图像的缺点&#xff1a;只读、文件较大、不同设备上显示效果不同矢量图形规范->SVG(W3C)/可扩展矢量图形规范&#xff08;Scalable Vector Graphics&#xff09;<由于是矢量的&#xff0c;不象PNG、JPEG等逐像素描述&#xf…

hive连接mysql之疯狂踩坑

这次真的是非常吐血&#xff0c;虽然网上有很多排错教程&#xff0c;但介于我踩的坑实在太多&#xff0c;所以记录一下整个汇总信息 故事要从hive启动开始&#xff0c;如果你的hive启动不起来&#xff0c; 是因为你的$HADOOP_HOME/etc/hadoop/hadoop-env.sh 当中的HADOOP_CLAS…

XSL入门

1.什么是XSL&#xff1f; XSL(EXtensible Stylesheet Language)可扩展样式表语言XSL之于 XML 就像 CSS 之于 HTML (由于XML中的Tag可以由用户来自定义&#xff0c;而XML文档通常只是树形结构而没有格式化的输出&#xff0c;因此需要XSL来定义XML格式化输出)2.XSL 包含两个部分&…

Spring 基于Java的Bean声明

Spring 基于Java的Bean声明 使用Configuration进行设置&#xff1b; Xml&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <beans xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xmlns"http://www.springframework.…

XSL教程1

跟我学XSL&#xff08;一&#xff09; 随着Internet的发展&#xff0c;越来越多的信息进入互联网&#xff0c;信息的交换、检索、保存及再利用等迫切的需求使HTML这种最常用的标记语言已越来越捉襟见肘。 HTML将数据内容与表现融为一体&#xff0c;可修改性、数据可检索性差&am…

软件项目中各角色的心理分析

“软件业&#xff0c;以人为本”。参与软件开发过程的各类人员的共通心理表现&#xff0c;对整个开发过程起着举足轻重的影响。作为一名项目管理者&#xff0c;非常有必要对其进行一一剖析&#xff0c;找出心结&#xff0c;然后对症下药&#xff0c;加大沟通力度&#xff0c;提…

【手把手机器学习入门到放弃】从线性回归开始

终于开新坑了&#xff5e; 线性回归是指将数据拟合成 ya1x1a2x2a3x3...anxnbϵya_1x_1a_2x_2a_3x_3...a_nx_nb \epsilonya1​x1​a2​x2​a3​x3​...an​xn​bϵ的形式 通过训练模型获得参数 a1,a2,...,an,ba_1, a_2, ..., a_n, ba1​,a2​,...,an​,b 从而对新的x值&#x…