以后题目多的话会进行分类,请持续关注
面试题20道:
1.#import和#include的区别,@class代表什么?
2.谈谈Objective-C的内存管理方式和过程?
3.Objective-C有私有方法吗?私有变量呢?
4.Objective-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?
5.类(class)和结构体(struct)有什么区别?
6.Swift是面向对象编程还是函数式的编程语言?
7.Objective-C中的函数式编程?
8.Swift中可选型(optioonal)?
9.OBjective-C当中有没有可选类型?
10.Swift中,什么是泛型(Generics)?
11.Swift说明比较关键字Open、Public、internal、File-private和private关键字
12.Swift中说明并比较关键字:Strong,Weak和Unownet
13.在Swift中,如何理解copy-on-write
14.在Swift中什么是属性观察(property observer)?
15.在Swift中mutating的作用。
16.在Swift中protocol怎么定义成员变量?
17.Swift在结构体中如果修改成员变量?
18.Swift如果实现(||)操作?
19.输入任意一个整数,输出的是输入整数的+2、+3、+4。。。 请定义一个函数来实现。
20.写一个函数,求0~100(包括0和100)中为偶数并且恰好是其他数字平方的数字?
1.#import和#include的区别,@class代表什么?
#import会代入头文件的所有信息,包括实例变量和方法等,
#include也是带入文件的所有信息,跟#import差异是在#import引用的文件只会被引用一次,不会递归包含的问题
@class代表的意思是,不需要导入文件内容,也不需要知道如何定义的,我只告诉编译器这个就是个类的名称,
2.谈谈Objective-C的内存管理方式和过程?
在App运行时创造了大量的对象,Objective-C中的对象时存储在堆中的,系统并不会自动释放堆中的内存(基本类型也就是值类型是由系统自己管理的,放在栈上),so,OBC的内存管理是需要开发去手动维护的,之前xcode4.2版本之前是MRC,之后则是ARC(主要由runtime和LLVM协作完成)。
一、引用计数
在Xcode4.2以后的版本中都是引入了ARC机制,程序编译时xcode可以自动给你的代码添加内存释放代码,但是如果编写释放内存的代码就会报错,所以需要在xcode中主动关闭ARC,这样才能有助于ObjC的理解。
在ObjC中,每个对象内部都会有一个retainCount整数,叫"引用计数",当一个对象创建之后它的引用计数为1,当调用这个对象的alloc、retain、new、copy方法之后引用计数会在动在原有的基础上加1,当调用这个对象的release方法之后它的引用计数会减1,如果一个对象的引用计数为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象,在手动管理内存的时候需要遵循一个原则谁创建、谁释放。
当用户调用方法的时候实际上是在向这个对象发送一条消息,且ObjC中允许向一个nil的对象发送消息,在释放完成以后最好直接给这个对象设置为nil,为了防止野指针的出现,当这个对象的引用计数为0的时候,系统会自动调用dealloc方法来销毁对象,可以在这里将所有的野指针设置为nil。
二、属性参数
@property自动实现你的属性的getter、setter方法,并且提供一些参数供选择
参数 | 详解 |
---|---|
atomic | 对属性加锁,多线程下线程安全,默认值 |
nonatomic | 对属性不加锁,多线程不安全,但是速度快 |
readwrite | 生成getter、setter方法,可读可写,默认值 |
readonly | 只生成getter方法,只读 |
assign | 直接赋值,默认值 |
weak | 相当于assign,多了一点就是对象被干掉时,weak引用自动设置为nil |
retain | 先release原来的值,再retain新值 |
Strong | 跟retain一样,在ARC中,不需要手动释放内存 |
copy | 先release原来的值,在copy新值 |
assign,setter方法
-(void)setNamenum:(int)num{
_Namenum=num;
}
retain,setter方法
-(void)setNamenum:(calss*)num{
if(_Namenum != num)
[_Namenum release];
_Namenum = [num retain];
}
copy,setter方法
-(void)setNamenum:(calss*)num{
if(_Namenum != num)
[_Namenum release];
_Namenum = [num copy];
}
自动释放池
在ObjC中还有一种自动释放机制, 使用的时候首先用@autoreleasepool关键字声明一个代码块,在代码块中初始化你的对象,如果对象在初始化的时候加入了autorelease方法,那么在这个代码块执行完成以后,在块中只要调用过autorelease方法的对象都会自动调用release方法。这就是自动释放池,这样release方法是一起呗调用的,可能有点用处。
@autoreleasepool {
UserModel *user1 = [[[UserModel alloc]init]autorelease];
user1.UserName = @"用户1";
UserModel *user2 = [[[UserModel alloc]init]autorelease];
user2.UserName = @"用户2";
}
打印结果
2019-01-25 23:26:53.031860+0800 MRC_demo[3264:662385] UserModel = (用户2)dealloc method.
2019-01-25 23:26:53.031967+0800 MRC_demo[3264:662385] UserModel = (用户1)dealloc method.
从结果来看自动释放池是一个队列,先进后出,压栈的形式。
根据理解又实验了2次:
- 在autorelease之后user2加上retain,实验证明user2没有被释放,说明autorelease只是调用了一次release
- 在autorelease之后user2加上release,程序崩溃Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)野指针,将user2 = nil就会解决,ObjC可以向nil对象发送消息。
自动释放池的总结
- autorelease不会改变retaincount的值,只是放入自动释放池
- 自动释放池,就是在代码块完成以后,调用autorelease的对象自动调用release方法(只是一次),如果对象的retaincount>1那么就没法释放
- 自动释放池是统一销毁,那么在这个代码块中占用的内存会相对来说多一点,可以使用多个自动释放池
3. Objective-C有私有方法吗?私有变量呢?
- 在ObjC中按照正常的规则来将是有私有方法,跟私有变量的,只要我不在.h文件声明就可以,只是在表面上是这样的,
- 因为ObjC是一个动态语言,我们可以通过runtime来动态的获取这些property、方法、ivar,得到名称或者直接修改值都是可以的。
代码如下:
/********************************************************/
对象类代码
/********************************************************/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface UserModel : NSObject
- (instancetype)initWithUserName:(NSString *)name;
@end
NS_ASSUME_NONNULL_END
@interface UserModel ()
{
NSString * userName;
}
@end
@implementation UserModel
- (instancetype)initWithUserName:(NSString *)name{
self = [super init];
if(self){
userName = name;
}
return self;
}
- (void)doing{
NSLog(@"这是我的私有方法");
}
@end
/********************************************************/
调用私有方法代码
当然这段代码是将所有的对象都得到然后都调用一遍
/********************************************************/
int Count;
Method *methodList = class_copyMethodList([UserModel class], &Count);
for (unsigned int i = 0; i < Count; i++) {
Method method = methodList[i];
NSLog(@"method --> %@", NSStringFromSelector(method_getName(method)));
objc_msgSend(user, NSSelectorFromString(method_getName(method)));
}
/********************************************************/
调用私有变量代码
这里是将所有的变量都取出来然后都修改成"我去"
/********************************************************/
int varcount;
Ivar *varlist = class_copyIvarList([user class], &varcount);
for (unsigned int i = 0; i < varcount; i++) {
Ivar ivar = varlist[i];
object_setIvarWithStrongDefault(user, ivar, @"我去");
NSLog(@"%@",object_getIvar(user, ivar));
}
当然runtime还是可以做到很多事情并不仅仅是这些,不过从题目来看已经够充分的说明了ObjC是否存在私有变量跟私有方法了。答案是严格意义上的私有方法、私有变量是不存在。
4.Objective-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?
Objc的类不可以多重继承,延伸问题有没有可能去实现多继承?
有的,两种方式:
1)伪继承消息转发,这样可以本类中没有这个方式有第二次机会可以去调用别的类中的方法
2)委托,也就是protocol的方式实现,当我需要某个方法的时候,我直接调用协议的方法,发送给实现方法的对象(也就是遵守协议的对象,这个对象我完全不必知道具体内容),这样就,通过委托的形式,也就没有必要去考虑多继承的问题,专心搞自己的类就可以。
Category是什么?重写一个类的方式用继承好还是用分类好?为什么?
Catogory是分类, 重写类的话,应该首先看一下具体的需求:
1)如果只是为了扩展一些方法,不修改类的实现、内容等,那么使用分类比较好,因为分类扩展方法仅限这个分类当中有效,并不会影响原有类的具体实现。
2)如果有目的性的需要修改类中的某一项内容、方法,那么最好的形式还是继承比较好。
5. Swift中类(class)和结构体(struct)有什么区别?
class是引用类型,struct是值类型。值类型在传递和赋值时将进行复制,而引用类型只会使用引用对象的一个"指向"。所以两者之间的区别就是两个类型的区别。
class中有struct没有的内容:
- 可以继承、这样子类可以使用父类的特性和方法。
- 类型转换可以在运行时检查和解释一下实例的类型。
- 可以使用deinit来释放资源。
- 一个类可以被多次引用。
struct的优势:
- 结构较小,适用于复制操作,相比一下class的实例被多次引用,struct更加安全。
- 无须担心内存泄漏或者多线程冲突问题。
6.Swift是面向对象编程还是函数式的编程语言?
Swift既是面向对象的编程语言,也是函数式的编程语言。
面向对象:因为支持类的封装、继承、多态,从这一点看,Swift是一个面向对象的语言
函数式:为什么说是函数式编程?因为Swift支持map、flatmap更加强调运算结果而不是中间过程。
7.Objective-C中的函数式编程?
在OC中典型的函数式编程式masonry这个框架。在OC中的函数式编程实现的原理:
- 如果想再去调用别的方法,那么就需要返回一个对象。
- 如果想用()去执行,那么需要返回一个block。
- 如果想让返回的block再调用对象的方法,那么这个block就需要返回一个对象(即返回值为一个对象的block)。
8.Swift中可选型(optioonal)?
在Swift中,可选型式为了表达当一个变量值为空的情况,当一个变量值为空时,他就是nil。在Swift中,无论变量时引用类型还是值类型,都可以时可选型变量。
! ?这两个的区别。?可选类型, !隐式解析可选类型
9.OBjective-C当中有没有可选类型?
OC当中没有明确提出可选型的概念,所有的引用类型都可以为nil,以此来表示变量值为空的情况,当时在Swift中就明确提出了可选型的概念,并扩大到了值类型。
10.Swift中,什么是泛型(Generics)?
泛型主要是为了增加代码的灵活性,它可以使对应的代码满足任意类型的变量,比如
func sum<T>(_ a: inout T,_ b: inout T){
(a,b) = (b,a)
}
// inout 关键字的含义是告诉编辑器,将值类型的对象跟引用类型对象一样都是按照引用传递,也就是地址传递。
这个方法T就是泛型,它既可以是任意对象,也可以是任意的值类型,但是必须a与b的类型保持一致,主要Swift是一个类型安全的语言,两个变量之间进行运算,赋值操作必须要类型一致。
11.Swift说明比较关键字Open、Public、internal、File-private和private关键字
上述几个关键字统称是:访问权限控制
Swift有5个级别的访问控制权限,从高到低依次为Open、Public、Internal、File-private、Private。
- Open是最高权限,修饰的类和方法可以在任意的Module中被访问和重写
- Public的权限仅次于Open。与Open唯一的区别在于,它修饰的对象可以在任意Module中被访问,但不能重写。
- Internal是默认的权限。它表示只能在当前定义的Module中访问和重写,它可以被一个Module中的多个文件访问,但不可以被其他的Module访问。
- File-private修饰的对象只能在当前文件中使用。
- private最低级别的访问权限,它修饰的对象只能在作用域中使用。即使在同一个文件中的其他作用域,也没法访问。
12.Swift中说明并比较关键字:Strong,Weak和Unownet
Strong代表强引用,是默认属。当一个对象被声明为Strong时,表示父层级对该对象有一个强引用的指向,该对象的引用计数+1
Weak代表弱引用,如果一个对象在被声明为Weak的时候,那么父层级的对象对该对象并没有指向,该对象的引用计数也不会+1,当该对象被释放掉的时候,这个对象会自动设置成nil,继续访问不会崩溃。
unownet与弱引用的本质一样。不同的是对象在被释放掉的时候,依然会有一个无效的引用指向该对象,它不是optional也不是nil,访问就会崩溃。
加入weak和unownet是为了防止strong带来的循环引用,简单的说当两个对象互相有一个强引用指向对方的时候。就会导致两个对象都无法在内存中得到释放从而导致的循环引用。
访问对象可能被释放的时候请用weak,比如是delegate,访问你对象一定不会被释放的时候请用unownet。
13.在Swift中,如何理解copy-on-write
在值类型在复制的时候,复制的对象和原来的对象实际上在内存上面都指向同一个对象,当且仅当修改复制后的对象时,才会在内存中重新创建一个新的对象
let array = [1,2,3]
var array1 = array
array1.append(4)
然后这两个对象的地址就会不一样了。以上
14.在Swift中什么是属性观察(property observer)?
当前类型内对特定的属性进行监听,并作出相应的行为,就是属性观察,简单的来说就是类型中的两个方法。
比方:
var name:String{
willSet{
//将要修改
}
didSet{
//修改完成
}
}
注意当初始化和在类型内部进行修改的时候是不会调用这两个方法的
15.在Swift中mutating的作用。
mutating的作用是可以在protocol、enum、struct中定义的方法修改其中的成员变量
16.在Swift中protocol怎么定义成员变量?
在protocol中可以声明成员变量、和静态成员变量,但是不能对其进行初始化的操作。并且只能是两种状态 get,Set、Get,
比如:
protocol MyProtocol{
var Str:String{ get } //只读
var changeStr:String {get set} //可读写
}
17.Swift在结构体中如果修改成员变量?
protocol MyProtocol{
var changeStr:String {get set} //可读写
}
struct UserModel:MyProtocol{
var changeStr:String
mutating func MyChangeStr(Name:String){
self.changeStr = Name
}
}
18.Swift如果实现(||)操作
或操作的本质是,当表达式左边的值为真的时候,无须计算表达式右边的值。
实现||的操作有三种方式:
一、普通的实现方式:
//实现
func huo(_ value1: Bool, _ value2: Bool) -> Bool {
if value1 {
return true
}
if value2 {
return true
}
return false
}
//调用
let res = huo(vLeft, getRightRes())
这种方式虽然可以实现或,但是并不高效,即便是左边的值等于true,右边的函数也会调用的。
如果右边的函数比较耗费性能或者费时操作,在已经确定左边的值为true的情况下,再去执行右边的函数是真没必要去执行的。
二、闭包概念的或:
实现
func huo(_ left:Bool, _ right:()->Bool)->Bool{
if left{
return true
}
return right()
}
//调用
let res = huo(true) { () -> Bool in
return getRightRes()
}
第二种方式解决了第一种方式中耗费性能耗时的操作。
三、@autoclosure 关键字优化。
//实现
func huo(_ value1: Bool, _ value2: @autoclosure() -> Bool) -> Bool {
if value1 {
return true
}
return value2()
}
//调用
let res = huo(true, getRightRes())
这种方式就是第二中方式的优化,加入@autoclosure后,调用时跟第一种方式时一样的形式,它将右边的计算函数隐性的放入了右边的闭包
19.输入任意一个整数,输出的是输入整数的+2、+3、+4。。。 请定义一个函数来实现。
func Add(_ num:Int)->(Int)->Int{
return { value in
return num + value
}
}
这体现了Swift的函数式编程的思想
20.写一个函数,求0~100(包括0和100)中为偶数并且恰好是其他数字平方的数字
在swift中我们需要写的方法是:
第一种方法:
func sqNums(_ from:Int,_ to:Int)->[Int] {
var array:[Int] = [Int]()
for num in from...to where num%2 == 0 { //查到偶数
if(from...to).contains(num*num){
array.append(num*num)
}
}
return array
}
这种方法如果在OC当中还是可以的,因为Swift是函数式编程思想的内容,我们换一种方式来思考,0 ~ 100 中找到N*N那么100是最大的,那么100 = 10*10,那么 0~10也就满足了N*N这个条件,然后在判断偶数,在使用Swift的map、filter方法
print({0...10}.map{$0*$0}.filter{$0%2==0}) 一行代码搞定