1、1. 风格纠错题 修改方法有很多种,现给出一种做示例: 下面对具体修改的地方,分两部分做下介绍: 硬伤部分和优化部分 。因为硬伤部分没什么技术含量,为了节省大家时间,放在后面讲,大神请直接看优化部分。 优化部分 1) enum 建议使用 NS_ENUM 和 NS_OPTIONS 宏来定义枚举类型,参见官方的 Adopting Modern Objective-C 一文: 1 2 3 4 5 /定义一个枚举 typedef NS_ENUM(NSInteger, CYLSex) CYLSexMan, CYLSexWoman ; 2) age 属性的类型:应避免使用基本类型,建议使 Foundati
2、on 数据类型,对应关系如下: 1 2 3 4 int - NSInteger unsigned - NSUInteger float - CGFloat 动画时间 - NSTimeInterval 同时考虑到 age 的特点,应使用 NSUInteger,而非 int。这样做的是基于 64-bit 适配考虑,详情可参考出题者的博文 64-bit Tips。 3)如果工程项目非常庞大,需要拆分成不同的模块,可以在类、 typedef 宏命名的时候使用前缀。 4) doLogIn 方法不应写在该类中:虽然 LogIn 的命名不太清晰,但笔者猜测是 login 的意思,而登录操作属于业务逻辑,观察
3、类名 UserModel,以及属性的命名方式,应该使用的是 MVC模式,并非 MVVM,在 MVC 中业务逻辑不应当写在 Model 中。(如果是 MVVM,抛开命名规范, UserModel 这个类可能对应的是用户注册页面,如果有特殊的业务需求,比如: login对应的应当是注册并登录的一个 Button,出现 login 方法也可能是合理的。) 5) doLogIn 方法命名不规范:添加了多余的动词前缀。请牢记: 如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用 do, does 这种多余的关键字,动词本身的暗示就足够了。 6) -(id)initUserModelWith
4、UserName: (NSString*)name withAge:(int)age;方法中不要用 with来连接两个参数 :withAge:应当换为 age:, age:已经足以清晰说明参数的作用,也不建议用andAge::通常情况下,即使有类似 withA:withB:的命名需求,也通常是使用 withA:andB:这种命名,用来表示方法执行了两个相对独立的操作(从设计上来说,这时候也可以拆分成两个独立的方法),它不应该用作阐明有多个参数,比如下面的: 1 2 3 4 5 6 /错误,不要使用 “and“来连接参数 - (int)runModalForDirectory:(NSString
5、 *)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)由于字符串值可能会改变,所以要把相关
6、属性的 “内存管理语义 ”声明为 copy。 (原因在下文有详细论述:用 property 声明的 NSString(或 NSArray, NSDictionary)经常使用 copy关键字,为什么? ) 8) “性别 ”(sex)属性的:该类中只给出了一种 “初始化方法 ” (initializer)用于设置 “姓名 ”(Name)和 “年龄 ”(Age)的初始值,那如何对 “性别 ”(Sex)初始化? Objective-C 有 designated 和 secondary 初始化方法的观念。 designated 初始化方法是提供所有的参数, secondary 初始化方法是一个或多个,
7、并且提供一个或者更多的默认参数来调用 designated 初始化方法的初始化方法。举例说明: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 / .m文件 / http:/ / https:/ / implementation CYLUser - (instancetype)initWithName:(NSString *)name age:(int)age sex:(CYLSex)sex if(self = super init) _name = name copy; _age = age; _sex = sex; return sel
8、f; - (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)属性。那么最终的修改代码就是上文给出
9、的第一种修改方法。2:不打算初始时初始化 “性别 ”(sex)属性,打算后期再修改,如果是这种情况,那么应该把 “性别 ”(sex)属性设为 readwrite 属性,最终给出的修改代码应该是: .h 中暴露 designated 初始化方法,是为了方便子类化(想了解更多,请戳 - 禅与 Objective-C 编程艺术( Zen and the Art of the Objective-C Craftsmanship 中文翻译) 。) 9)按照接口设计的惯例,如果设计了 “初始化方法 ” (initializer),也应当搭配一个快捷构造方法。而快捷构造方法的返回值,建议为 instance
10、type,为保持一致性, init 方法和快捷构造方法的返回类型最好都用 instancetype。 10)如果基于第一种修改方法:既然该类中已经有一个 “初始化方法 ” (initializer),用于设置“姓名 ”(Name)、 “年龄 ”(Age)和 “性别 ”(Sex)的初始值 : 那么在设计对应 property 时就应该尽量使用不可变的对象:其三个属性都应该设为 “只读 ”。用初始化方法设置好属性值之后,就不能再改变了。在本例中,仍需声明属性的 “内存管理语义 ”。于是可以把属性的定义改成这样 1 2 3 property (nonatomic, copy, readonly) N
11、SString *name; property (nonatomic, assign, readonly) NSUInter age; property (nonatomic, assign, readonly) CYLSex sex; 由于是只读属性,所以编译器不会为其创建对应的 “设置方法 ”, 即便如此,我们还是要写上这些属性的语义,以此表明初始化方法在设置这些属性值时所用的方式。要是不写明语义的话,该类的调用者就不知道初始化方法里会拷贝这些属性,他们有可能会在调用初始化方法之前自行拷贝属性值。这种操作多余而且低效。 11) initUserModelWithUserName 如果改为
12、initWithName 会更加简洁,而且足够清晰。 12) UserModel 如果改为 User 会更加简洁,而且足够清晰。 13) UserSex 如果改为 Sex 会更加简洁,而且足够清晰。 硬伤部分 1)在 -和 (void)之间应该有一个空格 2) enum 中驼峰命名法和下划线命名法混用错误:枚举类型的命名规则和函数的命名规则相同:命名时使用驼峰命名法,勿使用下划线命名法。 3) enum 左括号前加一个空格,或者将左括号换到下一行 4) enum 右括号后加一个空格 5) UserModel :NSObject 应为 UserModel : NSObject,也就是 :右侧少了
13、一个空格。 6) interface 与 property 属性声明中间应当间隔一行。 7)两个方法定义之间不需要换行,有时为了区分方法的功能也可间隔一行,但示例代码中间隔了两行。 8) -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中方法名与参数之间多了空格。而且 - 与 (id)之间少了空格。 9) -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中方法名与参数之间多了空格: (NSString*)name 前多了空格
14、。 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 中 ,在有可能出现 循环引用的时候 ,往往要通过让其中一端使用
15、weak 来解决 ,比如 :delegate 代理属性 2)自身已经对它进行一次强引用 ,没有必要再强引用一次 ,此时也会使用 weak,自定义IBOutlet 控件属性一般也使用 weak;当然,也可以使用 strong。在下文也有论述: IBOutlet连出来的视图属性为什么可以被设置成 weak? 不同点: 1) weak 此特质表明该属性定义了一种 “非拥有关系 ” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似,然而在属性所指的对象遭到摧毁时,属性值也会清空 (nil out)。而 assign
16、 的 “设置方法 ”只会执行针对“纯量类型 ” (scalar type,例如 CGFloat 或 NSlnteger 等 )的简单赋值操作。 2) assigin 可以用非 OC 对象 ,而 weak 必须用于 OC 对象 3. 怎么用 copy 关键字? 用途: 1) NSString、 NSArray、 NSDictionary 等等经常使用 copy 关键字,是因为他们有对应的可变类型: NSMutableString、 NSMutableArray、 NSMutableDictionary; 2) block 也经常使用 copy 关键字,具体原因见官方文档: Objects Use
17、 Properties to Keep Track of Blocks: block 使用 copy 是从 MRC 遗留下来的 “传统 ”,在 MRC 中 ,方法内部的 block 是在栈区的 ,使用 copy 可以把它放到堆区 .在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy操作。 下面做下解释: copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将 其 “拷贝 ” (copy)。当属性类型为 NSString 时,经常
18、用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString类的实例。这个类是 NSString的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份 “不可变 ” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是 “可变的 ” (mutable),就应该在设置新属性值时拷贝一份。 用 property 声明 NSString、 NSArray、 NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类
19、型: NSMutableString、 NSMutableArray、 NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。 该问题在下文中也有论述:用 property 声明的 NSString(或 NSArray, NSDictionary)经常使用 copy 关键字,为什么?如果改用 strong 关键字,可能造成什么问题? 4. 这个写法会出什么问题: property (copy) NSMutableArray *array; 两个问题: 1、添加 ,删除 ,修改数组内的元素的时候 ,程序会因为找不到对
20、应的方法而崩溃 .因为 copy 就是复制一个不可变 NSArray 的对象; 2、使用了 atomic 属性会严重影响性能。 第 1 条的相关原因在下文中有论述用 property 声明的 NSString(或 NSArray,NSDictionary)经常使用 copy 关键字,为什么?如果改用 strong 关键字,可能造成什 么问题?以及上文怎么用 copy 关键字?也有论述。 第 2 条原因,如下: 该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明 nonatomic 可以节省这些虽然很小但是不必要额外开销。 在默认情况下,由编译器所
21、合成的方法会通过锁定机制确保其原子性 (atomicity)。如果属性具备 nonatomic 特质,则不使用同步锁。请注意,尽管没有名为 “atomic”的特质 (如果某属性不具备 nonatomic 特质,那它就是 “原子的 ”(atomic)。 在 iOS 开发中 ,你会发现,几乎所有属性都声明为 nonatomic。 一般情况下并不要求属性必须是 “原子的 ”,因为这并不能保证 “线程安全 ” ( thread safety),若要实现 “线程安全 ”的操作,还需采用更为深层的锁定机制才行。例如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为 at
22、omic,也还是会读到不同的属性值。 因此,开发 iOS 程序时一般都会使用 nonatomic 属性。但是在开发 Mac OS X 程序时,使用 atomic 属性通常都不会有性能瓶颈。 5. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter? 若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopyiog 与 NSMutableCopying 协议。 具体步骤: 1)需声明该类遵从 NSCopying 协议 2)实现 NSCopying 协议。该协议只有一个方法 : 1 - (id)copyWithZone: (NSZone*) zone 注意: 一提到让自己的类用 copy 修饰符,我们总是想覆写 copy 方法,其实真正需要实现的却是 “copyWithZone”方法。 以第一题的代码为例: 然后实现协议中规定的方法: 但在实际的项目中,不可能这么简单,遇到更复杂一点,比如类对象中的数据结构可能并未在初始化方法中设置好,需要另行设置。举个例子,假如 CYLUser 中含有一个数组,与其他 CYLUser 对象建立或解除朋友关系的那些方法都需要操作这个数组。那么在这种情况下,