iOS面试题.docx
- 文档编号:15210306
- 上传时间:2023-07-02
- 格式:DOCX
- 页数:74
- 大小:3.29MB
iOS面试题.docx
《iOS面试题.docx》由会员分享,可在线阅读,更多相关《iOS面试题.docx(74页珍藏版)》请在冰点文库上搜索。
iOS面试题
1.风格纠错题
修改方法有很多种,现给出一种做示例:
下面对具体修改的地方,分两部分做下介绍:
硬伤部分和优化部分 。
因为硬伤部分没什么技术含量,为了节省大家时间,放在后面讲,大神请直接看优化部分。
优化部分
1)enum建议使用NS_ENUM和NS_OPTIONS宏来定义枚举类型,参见官方的 AdoptingModernObjective-C 一文:
1
2
3
4
5
//定义一个枚举
typedef NS_ENUM(NSInteger, CYLSex) {
CYLSexMan,
CYLSexWoman
};
2)age属性的类型:
应避免使用基本类型,建议使Foundation数据类型,对应关系如下:
1
2
3
4
int -> NSInteger
unsigned -> NSUInteger
float -> CGFloat
动画时间 -> NSTimeInterval
同时考虑到age的特点,应使用NSUInteger,而非int。
这样做的是基于64-bit适配考虑,详情可参考出题者的博文《64-bitTips》。
3)如果工程项目非常庞大,需要拆分成不同的模块,可以在类、typedef宏命名的时候使用前缀。
4)doLogIn方法不应写在该类中:
虽然LogIn的命名不太清晰,但笔者猜测是login的意思,而登录操作属于业务逻辑,观察类名UserModel,以及属性的命名方式,应该使用的是MVC模式,并非MVVM,在MVC中业务逻辑不应当写在Model中。
(如果是MVVM,抛开命名规范,UserModel这个类可能对应的是用户注册页面,如果有特殊的业务需求,比如:
login对应的应当是注册并登录的一个Button,出现login方法也可能是合理的。
)
5)doLogIn方法命名不规范:
添加了多余的动词前缀。
请牢记:
如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用do,does这种多余的关键字,动词本身的暗示就足够了。
6)-(id)initUserModelWithUserName:
(NSString*)namewithAge:
(int)age;方法中不要用with来连接两个参数:
withAge:
应当换为age:
,age:
已经足以清晰说明参数的作用,也不建议用andAge:
:
通常情况下,即使有类似withA:
withB:
的命名需求,也通常是使用withA:
andB:
这种命名,用来表示方法执行了两个相对独立的操作(从设计上来说,这时候也可以拆分成两个独立的方法),它不应该用作阐明有多个参数,比如下面的:
1
2
3
4
5
6
//错误,不要使用"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初始化方法的初始化方法。
举例说明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// .m文件
//
//
//
@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属性,最终给出的修改代码应该是:
.h中暴露designated初始化方法,是为了方便子类化(想了解更多,请戳--》《禅与Objective-C编程艺术(ZenandtheArtoftheObjective-CCraftsmanship中文翻译)》。
)
9)按照接口设计的惯例,如果设计了“初始化方法”(initializer),也应当搭配一个快捷构造方法。
而快捷构造方法的返回值,建议为instancetype,为保持一致性,init方法和快捷构造方法的返回类型最好都用instancetype。
10)如果基于第一种修改方法:
既然该类中已经有一个“初始化方法”(initializer),用于设置“姓名”(Name)、“年龄”(Age)和“性别”(Sex)的初始值:
那么在设计对应@property时就应该尽量使用不可变的对象:
其三个属性都应该设为“只读”。
用初始化方法设置好属性值之后,就不能再改变了。
在本例中,仍需声明属性的“内存管理语义”。
于是可以把属性的定义改成这样
1
2
3
@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*)namewithAge:
(int)age;方法中方法名与参数之间多了空格。
而且-与(id)之间少了空格。
9)-(id)initUserModelWithUserName:
(NSString*)namewithAge:
(int)age;方法中方法名与参数之间多了空格:
(NSString*)name前多了空格。
10)-(id)initUserModelWithUserName:
(NSString*)namewithAge:
(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此特质表明该属性定义了一种“非拥有关系”(nonowningrelationship)。
为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。
此特质同assign类似,然而在属性所指的对象遭到摧毁时,属性值也会清空(nilout)。
而assign的“设置方法”只会执行针对“纯量类型”(scalartype,例如CGFloat或NSlnteger等)的简单赋值操作。
2)assigin 可以用非OC对象,而weak必须用于OC对象
3.怎么用copy关键字?
用途:
1)NSString、NSArray、NSDictionary等等经常使用copy关键字,是因为他们有对应的可变类型:
NSMutableString、NSMutableArray、NSMutableDictionary;
2)block也经常使用copy关键字,具体原因见官方文档:
ObjectsUsePropertiestoKeepTrackofBlocks:
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。
一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”(threadsafety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。
例如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读到不同的属性值。
因此,开发iOS程序时一般都会使用nonatomic属性。
但是在开发MacOSX程序时,使用atomic属性通常都不会有性能瓶颈。
5.如何让自己的类用copy修饰符?
如何重写带copy关键字的setter?
若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。
如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopyiog与NSMutableCopying协议。
具体步骤:
1)需声明该类遵从NSCopying协议
2)实现NSCopying协议。
该协议只有一个方法:
1
- (id)copyWithZone:
(NSZone*) zone
注意:
一提到让自己的类用copy修饰符,我们总是想覆写copy方法,其实真正需要实现的却是“copyWithZone”方法。
以第一题的代码为例:
然后实现协议中规定的方法:
但在实际的项目中,不可能这么简单,遇到更复杂一点,比如类对象中的数据结构可能并未在初始化方法中设置好,需要另行设置。
举个例子,假如CYLUser中含有一个数组,与其他CYLUser对象建立或解除朋友关系的那些方法都需要操作这个数组。
那么在这种情况下,你得把这个包含朋友对象的数组也一并拷贝过来。
下面列出了实现此功能所需的全部代码:
//.m文件
以上做法能满足基本的需求,但是也有缺陷:
如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。
【注:
深浅拷贝的概念,在下文中有介绍,详见下文的:
用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?
如果改用strong关键字,可能造成什么问题?
】
在例子中,存放朋友对象的set是用“copyWithZooe:
”方法来拷贝的,这种浅拷贝方式不会逐个复制set中的元素。
若需要深拷贝的话,则可像下面这样,编写一个专供深拷贝所用的方法:
1
2
3
4
5
6
7
8
9
- (id)deepCopy {
CYLUser *copy = [[[self copy] allocWithZone:
zone]
initWithName:
_name
age:
_age
sex:
sex];
copy->_friends = [[NSMutableSet alloc] initWithSet:
_friends
copyItems:
YES];
return copy;
}
至于如何重写带copy关键字的setter这个问题,
如果抛开本例来回答的话,如下:
1
2
3
- (void)setName:
(NSString *)name {
_name = [name copy];
}
如果单单就上文的代码而言,我们不需要也不能重写name的setter:
由于是name是只读属性,所以编译器不会为其创建对应的“设置方法”,用初始化方法设置好属性值之后,就不能再改变了。
(在本例中,之所以还要声明属性的“内存管理语义”--copy,是因为:
如果不写copy,该类的调用者就不知道初始化方法里会拷贝这些属性,他们有可能会在调用初始化方法之前自行拷贝属性值。
这种操作多余而低效。
)。
那如何确保name被copy?
在初始化方法(initializer)中做:
1
2
3
4
5
6
7
8
9
10
11
- (instancetype)initWithName:
(NSString *)name
age:
(int)age
sex:
(CYLSex)sex {
if(self = [super init]) {
_name = [name copy];
_age = age;
_sex = sex;
_friends = [[NSMutableSet alloc] init];
}
return self;
}
6.@property的本质是什么?
ivar、getter、setter是如何生成并添加到这个类中的。
@property的本质是什么?
@property=ivar+getter+setter;
下面解释下:
“属性”(property)有两大概念:
ivar(实例变量)、存取方法(accessmethod=getter+setter)。
“属性”(property)作为Objective-C的一项特性,主要的作用就在于封装对象中的数据。
Objective-C对象通常会把其所需要的数据保存为各种实例变量。
实例变量一般通过“存取方法”(accessmethod)来访问。
其中,“获取方法”(getter)用于读取变量值,而“设置方法”(setter)用于写入变量值。
这个概念已经定型,并且经由“属性”这一特性而成为Objective-C2.0的一部分。
而在正规的Objective-C编码风格中,存取方法有着严格的命名规范。
正因为有了这种严格的命名规范,所以Objective-C这门语言才能根据名称自动创建出存取方法。
其实也可以把属性当做一种关键字,其表示:
编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。
所以你也可以这么说:
@property=getter+setter;
例如下面这个类:
1
2
3
4
@interface Person :
NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
上述代码写出来的类与下面这种写法等效:
1
2
3
4
5
6
@interface Person :
NSObject
- (NSString *)firstName;
- (void)setFirstName:
(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:
(NSString *)lastName;
@end
ivar、getter、setter是如何生成并添加到这个类中的?
“自动合成”(autosynthesis)
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。
需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesizedmethod)的源代码。
除了生成方法代码getter、setter之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。
在前例中,会生成两个实例变量,其名称分别为_firstName与_lastName。
也可以在类的实现代码里通过@synthesize语法来指定实例变量的名字.
1
2
3
4
@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = myLastName;
@end
我为了搞清属性是怎么实现的,曾经反编译过相关的代码,大致生成了五个东西:
1)OBJC_IVAR_$类名$属性名称:
该属性的“偏移量”(offset),这个偏移量是“硬编码”(hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
2)setter与getter方法对应的实现函数
3)i
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- iOS 试题
![提示](https://static.bingdoc.com/images/bang_tan.gif)