【二零一七年新型】iOS面试题及答案

设计形式是何等?
你了然咋样设计形式,并简要叙述? 

设计格局是一种编码经验,就是用相比较成熟的逻辑去处理某一种档次的作业。

1).
MVC格局:Model View Control,把模型 视图 控制器
层举办解耦合编写。

2).
MVVM形式:Model View ViewModel 把模型 视图 业务逻辑
层举办解耦和编排。

3).
单例形式:通过static关键词,表明全局变量。在全方位经过运行期间只会被赋值一遍。

4).
观察者情势:KVO是百里挑一的布告格局,观望某个属性的情状,状态暴发变化时通报观望者。

5).
委托格局:代理+协议的三结合。实现1对1的反向传值操作。

6).
工厂形式:通过一个类措施,批量的按照已有模板生产目的。

 

MVC 和 MVVM
的区别 

1).
MVVM是对胖模型举办的拆分,其本质是给控制器减负,将有些弱业务逻辑放到VM中去处理。

2).
MVC是所有计划的底子,所有新的设计模式都是按照MVC进行的精益求精。

 

#import跟
#include 有如何分别,@class呢,#import<> 跟
#import””有什么样分别? 

答:

1).
#import是Objective-C导入头文件的重中之重字,#include是C/C++导入头文件的首要字,使用#import头文件会自动只导入三遍,不会再一次导入。

2).
@class告诉编译器某个类的扬言,当执行时,才去查看类的落实公文,可以缓解头文件的相互包含。

3).
#import<>用来含有系统的头文件,#import””用来含有用户头文件。

 

frame 和 bounds
有什么不同? 

frame指的是:该view在父view坐标体系中的地点和分寸。(参照点是父view的坐标体系)

bounds指的是:该view在自家坐标体系中的地点和尺寸。(参照点是本人坐标连串)

 

Objective-C的类可以多重继承么?可以兑现五个接口么?Category是何等?重写一个类的艺术用连续好仍旧分类好?为何? 

答:Objective-C的类不可以多重继承;可以实现多个接口(协议);Category是项目;

貌似景色用分类好,用Category去重写类的法门,仅对本Category有效,不会潜移默化到任何类与原有类的涉及。

 

@property
的面目是怎样?ivar、getter、setter
是哪些转移并添加到这一个类中的 

@property
的五指山真面目是怎么?

@property =
ivar + getter + setter;

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

 

“属性”
(property)作为 Objective-C
的一项特征,重要的效果就在于包装对象中的数据。

Objective-C
对象平日会把其所需要的多大将军存为各种实例变量。实例变量一般通过“存取方法”(access
method)来拜会。

其间,“获取格局” (getter)用于读取变量值,而“设置模式”
(setter)用于写入变量值。

 

@property中有怎么样属性关键字?/ @property
后边可以有怎么着修饰符? 

特性可以具备的特质分为四类:

1.原子性—
nonatomic 特质

2.读/写权限—readwrite(读写)、readonly
(只读)

3.内存管理语义—assign、strong、
weak、unsafe_unretained、copy

4.方法名—getter=<name>
、setter=<name>

5.不常用的:nonnull,null_resettable,nullable

 

性能关键字
readwrite,readonly,assign,retain,copy,nonatomic
各是何等效果,在这种状态下用? 

答:

1). readwrite
是可读可写特性。需要生成getter方法和setter方法。

2). readonly
是只读特性。只会生成getter方法,不会生成setter方法,不期望属性在类外改变。

3). assign
是赋值特性。setter方法将盛传参数赋值给实例变量;仅设置变量时,assign用于中央数据类型。

4).
retain(MRC)/strong(ARC)
表示所有特性。setter方法将盛传参数先保留,再赋值,传入参数的retaincount会+1。

5). copy
表示拷贝特性。setter方法将盛传对象复制一份,需要完全一份新的变量时。

6). nonatomic
非原子操作。决定编译器生成的setter和getter方法是否是原子操作,

atomic表示多线程安全,一般选用nonatomic,效能高。

 

怎么情况拔取weak 关键字,相比 assign 有如何不同? 

1.在 ARC
中,在有可能出现循环引用的时候,往往要透过让里面一端应用 weak 来缓解,比如:
delegate 代理属性。

2.自己已经对它举行五次强引用,没有必要再强引用四次,此时也会使用
weak,

自定义 IBOutlet
控件属性一般也使用
weak;当然,也能够动用strong。

 

IBOutlet连出来的视图属性为何可以被安装成weak?

因为父控件的subViews数组已经对它有一个强引用。

 

不同点:

assign 可以用非
OC 对象,而 weak 必须用于 OC 对象。

weak
阐明该属性定义了一种“非拥有关系”。在性质所指的对象销毁时,属性值会自动清空(nil)。

 

怎么用 copy
关键字? 

用途:

 1.
NSString、NSArray、NSDictionary
等等平日应用copy关键字,

是因为她俩有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;

 

 2. block
也时时应用 copy 关键字。

 

 说明:

 block 使用
copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block
是在栈区的,

采纳 copy
能够把它内置堆区.在 ARC 中写不写都行:对于 block 使用 copy 如故 strong
效果是千篇一律的,

但写上 copy
也无伤大雅,还是能时刻指示我们:编译器自动对 block 举行了 copy
操作。

假诺不写 copy
,该类的调用者有可能会忘记或者根本不精晓“编译器会自行对 block 举办了
copy 操作”,

他们有可能会在调用往日自行拷贝属性值。这种操作多余而失效。

 

用@property讲明的 NSString / NSArray / NSDictionary 日常采取copy 关键字,为何?假使改用strong关键字,可能导致什么问题? 

答:用
@property 讲明 NSString、NSArray、NSDictionary 平时采取 copy
关键字,

是因为她们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,

他们中间或许举办赋值操作(就是把可变的赋值给不可变的),为确保目的中的字符串值不会无意变动,

应该在装置新属性值时拷贝一份。

 

1.
因为父类指针可以针对子类对象,使用 copy
的目的是为了让本对象的特性不受外界影响,

采取 copy
无论给自己传入是一个可变对象仍旧不行对象,我本身持有的就是一个不可变的副本。

2.
尽管大家应用是 strong
,那么这些特性就有可能指向一个可变对象,假诺这多少个可变对象在表面被修改了,那么会潜移默化该属性。

 

//总结:使用copy的目标是,制止把可变类型的目的赋值给不可变类型的靶羊时,

可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。

 

浅拷贝和深拷贝的区分? 

答:

浅拷贝:只复制指向对象的指针,而不复制引用对象自我。

深拷贝:复制引用对象自我。内存中存在了两份独立对象自我,当修改A时,A_copy不变。

 

系统对象的 copy
与 mutableCopy 方法 

随便是集合类对象(NSArray、NSDictionary、NSSet …
之类的靶子),

抑或非集合类对象(NSString, NSNumber …
之类的靶子),接收到copy和mutableCopy信息时,

都遵照以下规则:

  1. copy
    再次回到的是不可变对象(immutableObject);假若用copy重回值调用mutable对象的点子就会crash。

  2. mutableCopy
    再次来到的是可变对象(mutableObject)。

 

一、非集合类对象的copy与mutableCopy

 
在非集合类对象中,对不可变对象开展copy操作,是指针复制,mutableCopy操作是内容复制;

 
对可变对象举办copy和mutableCopy都是内容复制。用代码简单表示如下:

NSString *str = @”hello
word!”;

NSString *strCopy = [str copy] //
指针复制,strCopy与str的地址一样

NSMutableString *strMCopy = [str mutableCopy]
//
内容复制,strMCopy与str的地方不雷同

 

NSMutableString *mutableStr = [NSMutableString
stringWithString: @”hello word!”];

NSString *strCopy = [mutableStr copy] //
内容复制

NSMutableString *strMCopy = [mutableStr
mutableCopy] // 内容复制

 

二、集合类对象的copy与mutableCopy
(同上)

 
在集合类对象中,对不可变对象开展copy操作,是指针复制,mutableCopy操作是内容复制;

 
对可变对象开展copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象自我,

对聚集内的对象元素仍旧是指针复制。(即单层内容复制)

    NSArray
*arr = @[@[@”a”, @”b”], @[@”c”, @”d”];

    NSArray
*copyArr = [arr copy]; // 指针复制

   
NSMutableArray *mCopyArr = [arr mutableCopy];
//单层内容复制

 
 

   
NSMutableArray *array = [NSMutableArray
arrayWithObjects:[NSMutableString
stringWithString:@”a”],@”b”,@”c”,nil];

    NSArray
*copyArr = [mutableArr copy]; //
单层内容复制

   
NSMutableArray *mCopyArr = [mutableArr mutableCopy]; //
单层内容复制

  
 

【总括一句话】:

除非对不可变对象举行copy操作是指针复制(浅复制),此外情形都是内容复制(深复制)!

 

其一写法会出什么问题:@property (nonatomic, copy)
NSMutableArray *arr; 

问题:添加,删除,修改数组内的因素的时候,程序会因为找不到相应的不二法门而夭折。

//如:-[__NSArrayI removeObjectAtIndex:]: unrecognized
selector sent to instance 0x7fcd1bc30460

//
copy后重临的是不可变对象(即 arr 是 NSArray 类型,NSArray
类型对象不可以调用 NSMutableArray
类型对象的办法)

原因:是因为
copy 就是复制一个不得变 NSArray 的靶子,无法对 NSArray
对象开展添加/修改。

 

咋样让祥和的类用 copy 修饰符?如何重写带 copy 关键字的
setter? 

若想令自己所写的靶子具备拷贝功用,则需兑现 NSCopying
协议。

倘若自定义的靶子分为可变版本与不可变版本,

那么快要同时落实 NSCopying 与 NSMutableCopying
协议。

 

具体步骤:

  1. 需阐明该类坚守 NSCopying
    协议

  2. 落实 NSCopying
    协议的情势。

    //
    该协议只有一个格局: 


    (id)copyWithZone:(NSZone
    *)zone;

    // 注意:使用 copy
    修饰符,调用的是copy方法,其实真的需要贯彻的是 “copyWithZone”
    方法。

 

写一个 setter
方法用于完成 @property (nonatomic, retain) NSString
*name,

写一个 setter
方法用于完成 @property (nonatomic, copy) NSString *name

答:

//
retain


(void)setName:(NSString *)str {

  [str
retain];

  [_name
release];

  _name =
str;

}

//
copy


(void)setName:(NSString *)str {

  id t = [str
copy];

  [_name
release];

  _name =
t;

}

 

@synthesize 和
@dynamic 分别有什么效益? 

@property有四个照应的词,一个是@synthesize(合成实例变量),一个是@dynamic。

倘使@synthesize和@dynamic都不曾写,那么默认的就是 @synthesize
var = _var;

//
在类的贯彻代码里通过 @synthesize
语法可以来指定实例变量的名字。(@synthesize var =
_newVar;)

  1. @synthesize
    的语义是如果你未曾手动实现setter方法和getter方法,那么编译器会活动为您加上这多少个章程。

  2. @dynamic
    告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成(如,@dynamic
    var)。

 

 常见的 Objective-C
的数据类型有那么些,和C的中坚数据类型有哪些分别?如:NSInteger和int 

答:

Objective-C的数据类型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,

那个都是class,创建后便是目标,而C语言的主导数据类型int,只是必定字节的内存空间,

用来存放数值;NSInteger是着力数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。

NSInteger是主旨数据类型Int或者Long的别名(NSInteger的定义typedef
long NSInteger),它的区别在于,

NSInteger会依照系统是32位仍旧64位来决定是自我是int如故long。

 

152 id
注解的对象有哪些特点?

答:id
声明的对象拥有运行时的特性,即可以本着任意档次的Objcetive-C的靶子。

 

154 Objective-C
如何对内存管理的,说说你的眼光和缓解形式? 

答:Objective-C的内存管理重要性有两种艺术ARC(自动内存计数)、手动内存计数、内存池。

1).
自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。

2).
手动内存计数MRC:遵守内存什么人申请、何人释放;什么人添加,何人释放的基准。

3).
内存释放池Release
Pool:把需要自由的内存统一放在一个池塘中,

当池子被抽干后(drain),池子中装有的内存空间也被电动释放掉。内存池的假释操作分为自动和手动。

机动释放受runloop机制影响。

 

 Objective-C
中开创线程的措施是怎么?要是在主线程中执行代码,方法是何等?假使想延时执行代码、方法又是何许? 

答:线程创设有两种格局:使用NSThread成立、使用GCD的dispatch、使用子类化的NSOperation,

下一场将其投入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,

假如想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:

 

 Category(系列)、
Extension(增加)和继续的区分 

区别:

1.
分类知名字,类增加没有分类名字,是一种特有的分类。

2.
分类只好扩展方法(属性仅仅是宣称,并没真正落实),类扩充可以扩展属性、成员变量和措施。

3.
持续可以追加,修改或者去除方法,并且可以增添属性。

 

 我们说的OC是动态运行时语言是何许意思? 

答:首即使将数据类型的规定由编译时,推迟到了运转时。简单的说,
运行时机制使我们直到运行时才去决定一个对象的品类,以及调用该品种对象指定方法。

 

为什么大家常见的delegate属性都用是week而不是retain/strong? 

答:是为着以防万一delegate两端暴发不必要的轮回引用。

@property
(nonatomic, weak) id<UITableViewDelegate>
delegate;

 

啥时候用delete,哪一天用Notification? 

Delegate(委托形式):1对1的反向消息公告功用。

Notification(通知形式):只想要把消息发送出去,告知某些状态的变动。然则并不关注何人想要知道这多少个。

 

什么是 KVO 和 KVC? 

1).
KVC(Key-Value-Coding):键值编码
是一种通过字符串直接访问对象的方法(即给属性赋值)

   举例表明:

   stu.name = @”张三” //
点语法给属性赋值

   [stu setValue:@”张三” forKey:@”name”]; //
通过字符串使用KVC形式给属性赋值

   stu1.nameLabel.text =
@”张三”;

   [stu1 setValue:@”张三”
forKey:@”nameLabel.text”]; //
跨层赋值

2).
KVO(key-Value-Observing):键值观看机制
他提供了考察某一性能变化的格局,极大的简化了代码。

    
KVO只可以被KVC触发,包括利用setValue:forKey:方法和点语法。

   //
通过下方方法为属性添加KVO观察

   –
(void)addObserver:(NSObject *)observer

              
      forKeyPath:(NSString *)keyPath

              
     
options:(NSKeyValueObservingOptions)options

              
      context:(nullable void *)context;

   //
当被寓目的特性发送变化时,会活动触发下方方法                 
 

   –
(void)observeValueForKeyPath:(NSString
*)keyPath

               
              ofObject:(id)object

               
                  change:(NSDictionary
*)change

              
                  context:(void *)context{}

 

KVC 和 KVO 的
keyPath 可以是性质、实例变量、成员变量。

 

KVC的底层实现? 

当一个目标调用setValue方法时,方法内部会做以下操作:

1).
检查是不是留存对应的key的set方法,倘诺存在,就调用set方法。

2).
假设set方法不设有,就会招来与key相同名称并且带下划线的积极分子变量,如果有,则一贯给成员变量属性赋值。

3).
即便没有找到_key,就会招来相同名称的属性key,如若有就从来赋值。

4).
假使还尚未找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。

这多少个点子的默认实现都是抛出相当,我们可以依照需要重写它们。

 

KVO的最底层实现? 

KVO基于runtime机制实现。

 

 ViewController生命周期 遵照执行顺序排列:

1.
initWithCoder:通过nib文件起首化时接触。

2.
awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的信息到nib文件中的每个对象。
     

3.
loadView:先导加载视图控制器自带的view。

4.
viewDidLoad:视图控制器的view被加载成功。  

5.
view威尔Appear:视图控制器的view将要展现在window上。

6.
updateViewConstraints:视图控制器的view起初更新AutoLayout约束。

7.
view威尔LayoutSubviews:视图控制器的view将要更新内容视图的职位。

8.
viewDidLayoutSubviews:视图控制器的view已经更新视图的岗位。

9.
viewDidAppear:视图控制器的view已经彰显到window上。 

10.
view威尔(Will)Disappear:视图控制器的view将要从window上消灭。

11.
viewDidDisappear:视图控制器的view已经从window上消失。

 

办法和选拔器有何不同? 

selector是一个艺术的名字,方法是一个组合体,包含了名字和促成。

 

您是否接触过OC中的反射机制?简单聊一下定义和利用 

1).
class反射

通过类名的字符串格局实例化对象。

Class class =
NSClassFromString(@”student”); 

Student *stu =
[[class alloc] init];

将类名变为字符串。

Class class
=[Student class];

NSString
*className =
NSStringFromClass(class);

2).
SEL的反射

通过措施的字符串情势实例化方法。

SEL selector =
NSSelectorFromString(@”setName”);
 

[stu
performSelector:selector
withObject:@”Mike”];

将艺术成为字符串。

NSStringFromSelector(@selector*(setName:));

 

调用方法有二种方法: 

1).
直接通过艺术名来调用。[person show];

2).
直接的经过SEL数据来调用 SEL aaa = @selector(show); [person
performSelector:aaa];

  

怎么对iOS设备举办性能测试? 

答:
Profile-> Instruments ->Time Profiler

 

开发品种时你是怎么检查内存泄露? 

1). 静态分析
analyze。

2).
instruments工具里面有个leak可以动态解析。

 

怎么是懒加载? 

答:懒加载就是只在运用的时候才去先河化。也得以了解成延时加载。

自我觉着最好也最简单易行的一个例证就是tableView中图纸的加载显示了,
一个延时加载,

制止内存过高,一个异步加载,避免线程堵塞进步用户体验。

 

类变量的 @public,@protected,@private,@package
表明各有什么样意义? 

@public
任哪里方都能访问;

@protected
该类和子类中走访,是默认的;

@private
只好在本类中做客;

@package
本包内拔取,跨包不得以。

 

怎么样是谓词? 

谓词就是透过NSPredicate给定的逻辑条件作为约束原则,完成对数码的筛选。

//定义谓词对象,谓词对象中富含了过滤条件(过滤条件比较多)

NSPredicate
*predicate = [NSPredicate
predicateWithFormat:@”age<%d”,30];

//使用谓词条件过滤数组中的元素,过滤之后回来查询的结果

NSArray *array
= [persons
filteredArrayUsingPredicate:predicate];

 

isa指针问题 

isa:是一个Class 类型的指针.
每个实例对象有个isa的指针,他针对性对象的类,

而Class里也有个isa的指针,
指向meteClass(元类)。元类保存了类模式的列表。

当类方法被调
用时,先会从本人查找类方法的贯彻,假设没有,元类会向他父类查找该模式。

而且注意的是:元类(meteClass)也是类,它也是目的。

元类也有isa指针,它的isa指针最后指向的是一个根元类(root
meteClass)。

根元类的isa指针指向我,这样形成了一个查封的内循环。

 

怎么访问并修改一个类的私家属性? 

1).
一种是通过KVC获取。

2).
通过runtime访问并修改私有总体性。

 

一个objc对象的isa的指针指向哪些?有什么样效率? 

答:指向她的类对象,从而可以找到目标上的主意。

 

上面的代码输出什么? 

@implementation
Son : Father

  • (id)init
    {

   if (self =
[super init]) {

      
NSLog(@”%@”, NSStringFromClass([self class])); //
Son

      
NSLog(@”%@”, NSStringFromClass([super class])); //
Son

  
}

   return
self;

}

@end

//
解析:

self
是类的隐藏参数,指向当前调用方法的这些类的实例。

super是一个Magic
Keyword,它实质是一个编译器标示符,和self是指向的同一个音讯接收者。

今非昔比的是:super会告诉编译器,调用class那些法猴时,要去父类的法门,而不是本类里的。

地方的例证不管调用[self class]还是[super
class],接受音信的目标都是时下 Son *obj
这些目的。

 

写一个完好的代办,包括注脚、实现 

//
创建

@protocol
MyDelagate

@required

-(void)eat:(NSString
*)foodName; 

@optional

-(void)run;

@end

 

//  声明
.h

@interface
person: NSObject<MyDelagate>

 

@end

 

//  实现
.m

@implementation
person


(void)eat:(NSString *)foodName { 

  
NSLog(@”吃:%@!”, foodName);

  • (void)run
    {

  
NSLog(@”run!”);

}

 

@end

 

isKindOfClass、isMemberOfClass、selector功能分别是咋样 

isKindOfClass:效率是某个对象属于某个项目或者接续自某类型。

isMemberOfClass:某个对象确切属于某个项目。

selector:通过艺术名,获取在内存中的函数的入口地址。

 

delegate 和 notification
的区别 

1).
二者都用于传递信息,不同之处首要在于一个是一对一的,另一个是一对多的。

2).
notification通过珍爱一个array,实现一对多音信的转向。

3).
delegate需要两者之间必须建立联系,不然没法调用代理的方法;notification不需要两者之间有关系。

 

什么是block? 

闭包(block):闭包就是获取其他函数局部变量的匿名函数。

 

block反向传值 

  • 在决定器间传值可以运用代理或者block,使用block相对来说简洁。 

  • 在前一个控制器的touchesBegan:方法内实现如下代码。   //
    OneViewController.m

  •  
    TwoViewController *twoVC = [[TwoViewController alloc]
    init];

  •  
    twoVC.valueBlcok = ^(NSString *str) {

  •   NSLog(@”OneViewController拿到值:%@”,
    str); 

  •  
    };

  •   [self
    presentViewController:twoVC animated:YES
    completion:nil];

  •   //
    TwoViewController.h  
    (在.h文件中声称一个block属性)

  •   @property
    (nonatomic ,strong) void(^valueBlcok)(NSString
    *str);

  •   //
    TwoViewController.m  
    (在.m文件中实现模式)


  • (void)touchesBegan:(NSSet<UITouch *> *)touches
    withEvent:(UIEvent *)event {

  • //
    传值:调用block

  • if (_valueBlcok)
    {

  • _valueBlcok(@”123456″);

  • }

  • }

block的专注点 

1).
在block内部使用外部指针且会造成循环引用情状下,需要用__week修饰外部指针:

__weak typeof(self) weakSelf =
self; 

2).
在block内部假如调用了延时函数还拔取弱指针会取不到该指针,

因为早已被销毁了,需要在block内部再将弱指针重新强引用一下。

__strong typeof(self) strongSelf =
weakSelf;

3).
如若急需在block内部改变外部栈区变量的话,需要在用__block修饰外部变量。

 

BAD_ACCESS在咋样动静下出现? 

答:这种问题在开发时通常碰到。原因是访问了野指针,比如访问已经出狱对象的积极分子变量或者发信息、死循环等。

 

lldb(gdb)常用的控制台调试命令? 

1). p
输出骨干类型。是打印命令,需要指定项目。是print的简写

p (int)[[[self view] subviews]
count]

2). po
打印对象,会调用对象description方法。是print-object的简写

po [self
view]

3). expr
能够在调节时动态执行指定表达式,并将结果打印出来。常用于在调节过程中修改变量的值。

4).
bt:打印调用堆栈,是thread
backtrace的简写,加all可打印所有thread的仓库

5). br
l:是breakpoint list的简写

 

您相似是怎么用Instruments的? 

Instruments里面工具很多,常用:

1). 提姆(Tim)e
Profiler: 性能分析

2).
Zombies:检查是不是访问了僵尸对象,但是那一个工具只可以从上往下检查,不智能。

3).
Allocations:用来检查内存,写算法的那批人也用那一个来检查。

4).
Leaks:检查内存,看是不是有内存泄露。

 

iOS中常用的数量存储情势有哪些? 

数码存储有四种方案:NSUserDefault、KeyChain、file、DB。

内部File有两种方法:plist、Archive(归档)

DB包括:SQLite、FMDB、CoreData

 

iOS的沙盒目录结构是哪些的? 

沙盒结构:

1).
Application:存放程序源文件,上架前透过数字签名,上架后不足修改。

2).
Documents:常用目录,iCloud备份目录,存放数据。(这里不可能存缓存文件,否则上架不被通过)

3).
Library:

Caches:存放体积大又不需要备份的数据。(常用的缓存路径)

Preference:设置目录,iCloud会备份设置音信。

4).
tmp:存放临时文件,不会被备份,而且那个文件下的数量有可能每日被破除的可能。

 

iOS多线程技术有哪两种格局? 

答:pthread、NSThread、GCD、NSOperation

 

GCD 与 NSOperation
的区别: 

GCD 和
NSOperation 都是用来落实多线程:

GCD
基于C语言的底层API,GCD首要与block结合使用,代码简洁高效。

NSOperation
属于Objective-C类,是按照GCD更高一层的包装。复杂任务一般用NSOperation实现。

 

写出利用GCD形式从子线程回到主线程的法子代码 

答:dispatch_sync(dispatch_get_main_queue(), ^{
});

 

哪些用GCD同步若干个异步调用?(如基于若干个url异步加载多张图片,然后在都下载完成后合成一张整图) 

// 使用Dispatch
Group追加block到Global Group
Queue,这多少个block假使一切举办完毕,

就会执行Main
Dispatch Queue中的截至处理的block。

//
成立队列组

dispatch_group_t group =
dispatch_group_create();

//
获取全局并发队列

dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0);

dispatch_group_async(group, queue, ^{ /*加载图片1 */
});

dispatch_group_async(group, queue, ^{ /*加载图片2 */
});

dispatch_group_async(group, queue, ^{ /*加载图片3 */
}); 

//
当并发队列组中的任务履行完毕后才会执行这里的代码

dispatch_group_notify(group, dispatch_get_main_queue(),
^{

        //
合并图片

});

 

dispatch_barrier_async(栅栏函数)的效能是什么样? 

函数定义:dispatch_barrier_async(dispatch_queue_t queue,
dispatch_block_t block);

作用:

1.在它前边的职责履行完毕后它才实施,它背后的天职要等它执行到位后才会先导举办。

2.防止数据竞争

//
1.创建并发队列

dispatch_queue_t queue = dispatch_queue_create(“myQueue”,
DISPATCH_QUEUE_CONCURRENT);

//
2.向队列中丰盛任务

dispatch_async(queue, ^{  //
1.2是互相的

   
NSLog(@”任务1, %@”,[NSThread
currentThread]);

});

dispatch_async(queue, ^{

   
NSLog(@”任务2, %@”,[NSThread
currentThread]);

});

dispatch_barrier_async(queue,
^{

   
NSLog(@”任务 barrier, %@”, [NSThread
currentThread]);

});

dispatch_async(queue, ^{   //
这三个是还要推行的

   
NSLog(@”任务3, %@”,[NSThread
currentThread]);

});

dispatch_async(queue, ^{

   
NSLog(@”任务4, %@”,[NSThread
currentThread]);

});

// 输出结果:
任务1
任务2 ——》 任务
barrier ——》任务3 职责4 

//
其中的职责1与任务2,任务3与任务4
出于是并行处理先后顺序不定。

以下代码运行结果怎么样?

  • (void)viewDidLoad
    {

    [super
viewDidLoad];

   
NSLog(@”1″);

   
dispatch_sync(dispatch_get_main_queue(),
^{

       
NSLog(@”2″);

   
});

   
NSLog(@”3″);

}

//
只输出:1。(主线程死锁)

 

什么是 RunLoop 

从字面上讲就是运行循环,它里面就是do-while循环,在这多少个轮回之中不断地处理各样任务。

一个线程对应一个RunLoop,基本职能就是保持程序的不断运转,处理app中的各样风波。

透过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。

主线程的run
loop默认是启动的。iOS的应用程序里面,程序启动后会有一个之类的main()函数

int main(int
argc, char * argv[]) {

@autoreleasepool
{

    return UIApplicationMain(argc, argv, nil,
NSStringFromClass([AppDelegate
class]));

}

}

 

什么是 Runtime 

Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的为主之一,

大家一直编写的OC代码,底层都是基于它来促成的。

 

Runtime实现的体制是何许,怎么用,一般用来干嘛? 

1).
使用时索要导入的头文件 <objc/message.h>
<objc/runtime.h>

2). Runtime
运行时机制,它是一套C语言库。

3).
实际上我们编辑的拥有OC代码,最终都是转成了runtime库的东西。

比如:

类转成了 Runtime
库里面的结构体等数据类型,

方法转成了 Runtime
库里面的C语言函数,

平时调模式都是转成了 objc_msgSend
函数(所以说OC有个音信发送机制)

//
OC是动态语言,每个方法在运行时会被动态转为信息发送,即:objc_msgSend(receiver,
selector)。

// [stu show]; 
在objc动态编译时,会被转意为:objc_msgSend(stu,
@selector(show));

4).
由此,可以说 Runtime
是OC的底部实现,是OC的骨子里执行者。

有了Runtime库,能做哪些工作呢?

Runtime库里面富含了跟类、成员变量、方法有关的API。

比如:

   
(1)获取类里面的兼具成员变量。

   
(2)为类动态增长成员变量。

   
(3)动态改变类的不二法门实现。

   
(4)为类动态增长新的法子等。

因而,有了Runtime,想怎么改就怎么改。

 

哪些是 Method
Swizzle(黑魔法),什么状态下会接纳? 

1).
在未曾一个类的兑现源码的状态下,想改变其中一个主意的贯彻,除了延续它重写、

和倚重性序列重名方法暴力超越之外,还有更为灵活的办法 Method
Swizzle。

2). Method
Swizzle
指的是改变一个已存在的接纳器对应的兑现的长河。

OC中艺术的调用可以在运作时通过改变,通过改变类的调度表中选取器到最后函数间的投射关系。

3).
在OC中调用一个艺术,其实是向一个目标发送音信,查找消息的绝无仅有按照是selector的名字。

采取OC的动态特性,可以实现在运作时偷换selector对应的章程实现。

4).
每个类都有一个模式列表,存放着selector的名字和方法实现的投射关系。

IMP有点类似函数指针,指向具体的法子实现。

5).
我们得以行使 method_exchangeImplementations
来互换2个措施中的IMP。

6).
我们能够使用 class_replaceMethod 来修改类。

7).
我们得以采取 method_setImplementation
来直接设置某个方法的IMP。

8).
归根结底,都是偷换了selector的IMP。

 

_objc_msgForward
函数是做什么的,直接调用它将会爆发哪些? 

答:_objc_msgForward是 IMP
类型,用于信息转发的:当向一个目的发送一条音讯,

但它并从未落实的时候,_objc_msgForward会尝试做音信转发。

 

什么是 TCP / UDP ?

TCP:传输控制协议。

UDP:用户数据协议。

TCP
是面向连接的,建立连接需要经验一回握手,是轻而易举的传输层协议。

UDP
是面向无连接的,数据传输是不可靠的,它只管发,不管收不收拿到。

简短的说,TCP注重数量安全,而UDP数据传输快点,但安全性一般。

 

通信底层原理(OSI七层模型) OSI采取了分层的结构化技术,共分七层:

物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

 

介绍一下XMPP?

XMPP是一种以XML为底蕴的开放式实时通信协议。

概括的说,XMPP就是一种协议,一种规定。就是说,在网络上传东西,XMM就是规定你上传大小的格式。

 

OC中创建线程的不二法门是怎么?假如在主线程中举办代码,方法是何等? 

//
创设线程的法子

  • [NSThread
    detachNewThreadSelector:nil toTarget:nil
    withObject:nil]

  • [self
    performSelectorInBackground:nil
    withObject:nil];

  • [[NSThread
    alloc] initWithTarget:nil selector:nil
    object:nil];


dispatch_async(dispatch_get_global_queue(0, 0),
^{});


[[NSOperationQueue new]
addOperation:nil];

//
主线程中施行代码的点子

  • [self
    performSelectorOnMainThread:nil withObject:nil
    waitUntilDone:YES];


dispatch_async(dispatch_get_main_queue(),
^{});


[[NSOperationQueue mainQueue]
addOperation:nil];

 

tableView的重用机制? 

答:UITableView
通过录取单元格来达到节省内存的目标:
通过为每个单元格指定一个录取标识符,

即指定了单元格的项目,当屏幕上的单元格滑出屏幕时,系统会把这些单元格添加到重用队列中,

等候被引用,当有新单元格从屏幕外滑入屏幕内时,从录取队列中找看有没有可以引用的单元格,

设若有,就拿过来用,假使没有就创办一个来利用。

 

用伪代码写一个线程安全的单例格局 

static id
_instance;

+
(id)allocWithZone:(struct _NSZone *)zone {

  static
dispatch_once_t onceToken;

 
dispatch_once(&onceToken, ^{

     
_instance = [super allocWithZone:zone];

 
});

  return
_instance;

}

+
(instancetype)sharedData {

  static
dispatch_once_t onceToken;

 
dispatch_once(&onceToken, ^{

     
_instance = [[self alloc] init];

 
});

  return
_instance;

}


(id)copyWithZone:(NSZone *)zone {

  return
_instance;

}

 

如何落实视图的变形? 

答:通过修改view的 transform
属性即可。

 

在手势对象基础类UIGestureRecognizer的常用子类手势类型中哪六个手势爆发后,响应只会执行一回? 

答:UITapGestureRecognizer,UISwipeGestureRecognizer是一回性手势,手势暴发后,响应只会执行一回。

 

字符串常用方法: 

NSString *str
= @”abc*123″;

NSArray *arr =
[str componentsSeparatedByString:@”*”];

//以目的字符串把原字符串分割成两有的,存到数组中。@[@”abc”,
@”123″];

 

怎么着高性能的给 UIImageView
加个圆角? 

不好的化解方案:使用下边的办法会强制Core
Animation提前渲染屏幕的离屏绘制,

而离屏绘制就会给性能带来负面影响
,会有卡顿的场景出现。

self.view.layer.cornerRadius =
5.0f;

self.view.layer.masksToBounds =
YES;

正确的化解方案:使用绘图技术 – (UIImage *)circleImage
{

    //
NO代表透明

   
UIGraphicsBeginImageContextWithOptions(self.size, NO,
0.0);

    //
得到上下文

   
CGContextRef ctx =
UIGraphicsGetCurrentContext();

    //
添加一个圆

    CGRect rect
= CGRectMake(0, 0, self.size.width,
self.size.height);

   
CGContextAddEllipseInRect(ctx, rect);

    //
裁剪

   
CGContextClip(ctx);

    //
将图片画上去

    [self
drawInRect:rect];

    UIImage
*image =
UIGraphicsGetImageFromCurrentImageContext();

    //
关闭上下文

   
UIGraphicsEndImageContext();

    return
image;

}

再有一种方案:使用了贝塞尔曲线”切割”个那么些图形, 给UIImageView
添加了的圆角,

其实也是经过绘图技术来实现的。

UIImageView *imageView
= [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100,
100)];

imageView.center = CGPointMake(200,
300);

UIImage
*anotherImage = [UIImage
imageNamed:@”image”];

UIGraphicsBeginImageContextWithOptions(imageView.bounds.size,
NO, 1.0);

[[UIBezierPath
bezierPathWithRoundedRect:imageView.bounds cornerRadius:50] addClip];

[anotherImage
drawInRect:imageView.bounds];

imageView.image

UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

[self.view
addSubview:imageView];

你是怎么封装一个view的 

1).
可以经过纯代码或者xib的办法来封装子控件

2).
建立一个跟view相关的模型,然后将模型数据传给view,通过模型上的数额给view的子控件赋值

/**


纯代码起先化控件时一定会走这一个主意

*/


(instancetype)initWithFrame:(CGRect)frame {

    if(self =
[super initWithFrame:frame]) {

        [self
setupUI];

   
}

    return
self;

}

/**


通过xib起始化控件时必定会走这些艺术

*/


(id)initWithCoder:(NSCoder *)aDecoder {

    if(self =
[super initWithCoder:aDecoder]) {

        [self
setupUI];

   
}

    return
self;

}

  • (void)setupUI
    {

    //
起首化代码

}

 

HTTP协议中 POST 方法和 GET
方法有那个区别? 

1.
GET用于向服务器请求数据,POST用于提交数据

2.
GET请求,请求参数拼接形式表露在地址栏,而POST请求参数则位居请求体里面,

就此GET请求不吻合用来评释密码等操作

3.
GET请求的URL有长度限制,POST请求不会有长度限制

 

请简单的介绍下APNS发送系统音讯的编制 

APNS优势:杜绝了仿佛安卓这种为了承受通告不停在后台唤醒程序保持长连接的一言一行,

由iOS系统和APNS举办长连接替代。

APNS的原理:

1).
应用在通告主旨注册,由iOS系统向APNS请求重返设备令牌(device
Token)

2).
应用程序接收到设备令牌并发送给自己的后台服务器

3).
服务器把要推送的内容和设施发送给APNS

4).
APNS依据设备令牌找到设备,再由iOS依照APPID把推送内容显示

其三方框架

AFNetworking 底层原理分析 

AFNetworking紧虽然对NSURLSession和NSURLConnection(iOS9.0放任)的包裹,其中首要有以下类:

1).
AFHTTPRequestOperationManager:内部封装的是 NSURLConnection,
负责发送网络请求,
使用最多的一个类。(3.0丢掉)

2).
AFHTTPSessionManager:内部封装是 NSURLSession,
负责发送网络请求,使用最多的一个类。

3).
AFNetworkReachabilityManager:实时监测网络状态的工具类。当前的网络环境暴发变动将来,这一个工具类就足以检测到。

4).
AFSecurityPolicy:网络安全的工具类, 重如果指向 HTTPS
服务。

5).
AFURLRequestSerialization:体系化工具类,基类。上传的多少转换成JSON格式

(AFJSONRequestSerializer).使用不多。

6).
AFURLResponseSerialization:反连串化工具类;基类.使用相比较多:

7).
AFJSONResponseSerializer;
JSON解析器,默认的解析器.

8).
AFHTTPResponseSerializer; 万能解析器;
JSON和XML之外的数据类型,直接重返二进

制数据.对服务器重返的数目不做其他处理.

9).
AFXMLParserResponseSerializer; XML解析器;

 

讲述下SDWebImage里面给UIImageView加载图片的逻辑 

SDWebImage 中为
UIImageView 提供了一个分类UIImageView+WebCache.h,

其一分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,

会在真实图片出现前会先出示占位图片,当真实图片被加载出来后再交替占位图片。   

 

加载图片的过程大约如下:

1.先是会在 SDWebImageCache
中摸索图片是否有照应的缓存, 它会以url
作为数据的索引先在内存中搜寻是否有对应的缓存

2.假诺缓存未找到就会采用通过MD5处理过的key来继续在磁盘中查询相应的数码,
假若找到了,
就会把磁盘中的数据加载到内存中,并将图片显示出来

3.只要在内存和磁盘缓存中都尚无找到,就会向远程服务器发送请求,起初下载图片

4.下载后的图纸会加盟缓存中,并写入磁盘中

5.所有获取图片的经过都是在子线程中推行,获取到图片后回到主线程将图片突显出来

 

SDWebImage原理:

调用类另外不二法门:

1.
从内存(字典)中找图片(当以此图片在此次使用程序的过程中已经被加载过),找到直接利用。

2.
从沙盒中找(当以此图形在前头使用程序的长河中被加载过),找到使用,缓存到内存中。

3.
从网络上拿到,使用,缓存到内存,缓存到沙盒。

 

友盟总结接口总括的享有效用 

APP启动速度,APP停留页面时间等

算法

永不中间变量,用二种方法互换A和B的值 

//
1.中间变量

void swap(int
a, int b) {

  int temp =
a;

  a =
b;

  b =
temp;

}

//
2.加法

void swap(int
a, int b) {

  a = a +
b;

  b = a –
b;

  a = a –
b;

}

//
3.异或(相同为0,不同为1.
可以领会为不进位加法)

void swap(int
a, int b) {

  a = a ^
b;

  b = a ^
b;

  a = a ^
b;

}

求最大公约数 

/**
1.从来遍历法 */

int
maxCommonDivisor(int a, int b) {

    int max =
0;

    for (int i
= 1; i <=b; i++) {

        if (a %
i == 0 && b % i == 0) {

            max
= i;

       
}

   
}

    return
max;

}

/**
2.辗转相除法 */

int
maxCommonDivisor(int a, int b) {

    int
r;

    while(a % b
> 0) {

        r = a %
b;

        a =
b;

        b =
r;

   
}

    return
b;

}

//
增加:最小公倍数 = (a * b)/最大公约数

模拟栈操作  

/**

 * 
栈是一种数据结构,特点:先进后出

 * 
训练:使用全局变量模拟栈的操作

 */

#include
<stdio.h>

#include
<stdbool.h>

#include
<assert.h>

//敬重全局变量:在全局变量前加static后,这一个全局变量就不得不在本文件中动用

static int
data[1024];//栈最多能保存1024个数据

static int
count =
0;//最近曾经放了稍稍个数(相当于栈顶地方)

//数据入栈
push

void push(int
x){

 
assert(!full());//防止数组越界

data[count++] =
x;

}

//数据出栈
pop

int
pop(){

assert(!empty());

return
data[–count];

}

//查看栈顶元素
top

int
top(){

assert(!empty());

return
data[count-1];

}

//查询栈满
full

bool full()
{

if(count >= 1024)
{

    return 1;

}

    return
0; 

}

//查询栈空
empty

bool empty()
{

if(count <= 0)
{

return
1;

}

    return
0;

}

int
main(){

   
//入栈

    for (int i
= 1; i <= 10; i++) {

       
push(i);

   
}

 

   
//出栈

   
while(!empty()){

       
printf(“%d “, top()); //栈顶元素

        pop();
//出栈

   
}

   
printf(“\n”);

  
 

    return
0;

}

排序算法 

采取排序、冒泡排序、插入排序三种排序算法可以总括为如下: 

都将数组分为已排序部分和未排序部分。

1.
取舍排序将已排序部分定义在左端,然后选拔未排序部分的微乎其微元素和未排序部分的首先个要素沟通。

2.
冒泡排序将已排序部分定义在右端,在遍历未排序部分的进程执行交流,将最大因素互换来最右端。

3.
插入排序将已排序部分概念在左端,将未排序部分元的第一个因素插入到已排序部分合适的职位。

采用排序 

/** 

*
【接纳排序】:最值出现在开头端

*

*
第1趟:在n个数中找到最小(大)数与第一个数交流地方

*
第2趟:在剩下n-1个数中找到最小(大)数与第二个数互换地方

*
重复这样的操作…依次与第六个、第多少个…数交换地方

*
第n-1趟,最后可实现数据的升序(降序)排列。

*

*/

void
selectSort(int *arr, int length) {

    for (int i
= 0; i < length – 1; i++) { //趟数

        for
(int j = i + 1; j < length; j++) {
//相比次数

            if
(arr[i] > arr[j]) {

               
int temp = arr[i];

               
arr[i] = arr[j];

               
arr[j] = temp;

           
}

       
}

   
}

}

冒泡排序 

/** 

*
【冒泡排序】:相邻元素两两相比,相比较完一趟,最值出现在最终

*
第1趟:依次相比较相邻的六个数,不断交流(小数放前,大数放后)逐个推进,最值最终出现在第n个元素地点

*
第2趟:依次相比较相邻的多少个数,不断沟通(小数放前,大数放后)逐个推进,最值最终出现在第n-1个因素地点

* ……   ……

*
第n-1趟:依次相比较相邻的多少个数,不断沟通(小数放前,大数放后)逐个推进,最值最终出现在第2个元素地点

*/

void
bublleSort(int *arr, int length) {

    for(int i =
0; i < length – 1; i++) { //趟数

        for(int
j = 0; j < length – i – 1; j++) {
//比较次数

           
if(arr[j] > arr[j+1]) {

               
int temp = arr[j];

               
arr[j] = arr[j+1];

               
arr[j+1] = temp;

           
}

       

   
}

}

折半搜寻(二分查找) 

/**

*
折半物色:优化查找时间(不用遍历全体数量)

*

*
折半招来的原理:

*   1>
数组必须是稳步的

*   2>
必须已知min和max(知道限制)

*   3>
动态统计mid的值,取出mid对应的值举行比较

*   4>
假诺mid对应的值大于要寻找的值,那么max要变小为mid-1

*   5>
假设mid对应的值小于要寻找的值,那么min要变大为mid+1

*

*/ 

//
已知一个不变数组, 和一个key,
要求从数组中找到key对应的索引地点 

int findKey(int
*arr, int length, int key) {

    int min =
0, max = length – 1, mid;

    while (min
<= max) {

        mid =
(min + max) / 2; //统计中间值

        if (key
> arr[mid]) {

            min
= mid + 1;

        } else
if (key < arr[mid]) {

            max
= mid – 1;

        } else
{

           
return mid;

       
}

   
}

    return
-1;

}

编码格式(优化细节)

在 Objective-C
中,enum 指出接纳 NS_ENUM 和 NS_OPTIONS 宏来定义枚举类型。 //定义一个枚举(相比较紧凑)

typedef
NS_ENUM(NSInteger, BRUserGender) {

   
BRUserGenderUnknown, //
未知

   
BRUserGenderMale, //
男性

   
BRUserGenderFemale, //
女性

   
BRUserGenderNeuter //
无性

};

@interface BRUser :
NSObject<NSCopying>

@property
(nonatomic, readonly, copy) NSString *name;

@property
(nonatomic, readonly, assign) NSUInteger
age;

@property
(nonatomic, readonly, assign) BRUserGender
gender;


(instancetype)initWithName:(NSString *)name age:(NSUInteger)age
gender:(BRUserGender)gender;

@end

//说明:

//既然该类中早已有一个“先导化方法” ,用于安装 name、age 和
gender 的开头值: 那么在设计对应

@property
时就应有尽可能利用不可变的靶子:其六个特性都应该设为“只读”。用初阶化方法设置好属性值之后,就不可以再转移了。

//属性的参数应该依照下面的顺序排列:
(原子性,读写,内存管理)

避免采纳C语言中的基本数据类型,指出采取 Foundation
数据类型,对应涉及如下: int ->
NSInteger

unsigned ->
NSUInteger

float ->
CGFloat

卡通时间 ->
NS提姆eInterval

其他知识点

HomeKit,是苹果2014年通知的智能家居平台。 

什么是 OpenGL、Quartz 2D?

Quatarz 2d 是Apple提供的大旨图形工具库。只是适用于2D图形的绘图。

OpenGL,是一个跨平台的图形开发库。适用于2D和3D图形的绘图。

 

ffmpeg框架:

ffmpeg
是音视频处理工具,既有音视频编码解码功用,又足以看作播放器使用。 

 

谈谈 UITableView 的优化

1).
正确的复用cell。

2).
设计统一规范的Cell

3).
提前总括并缓存好中度(布局),因为heightForRowAtIndexPath:是调用最频繁的情势;

4).
异步绘制,遭受复杂界面,境遇性能瓶颈时,可能就是突破口;

4).
滑动时按需加载,这些在大气图纸突显,网络加载的时候很管用!

5).
减弱子视图的层级关系

6).
尽量使拥有的视图不透明化以及做切圆操作。

7).
不要动态的add 或者 remove
子控件。最好在起初化时就添加完,然后通过hidden来控制是否出示。

8).
使用调试工具分析问题。

 

何以履行cell的动态的行高

倘诺愿意每条数据呈现我的行高,必须设置六个属性,1.预估行高,2.自定义行高。

设置预估行高
tableView.estimatedRowHeight = 200。

设置定义行高
tableView.estimatedRowHeight =
UITableViewAutomaticDimension。 

只要要让自定义行高有效,必须让容器视图有一个自下而上的封锁。

 

说说你对 block 的明亮

栈上的活动复制到堆上,block 的习性修饰符是
copy,循环引用的规律和解决方案。

 

说说您对 runtime 的知晓

一言九鼎是办法调用时怎样寻找缓存,怎样找到方法,找不到形式时怎么转车,对象的内存布局。

 

何以是野指针、空指针?

野指针:不明白针对了啥地方的指针叫野指针。即指针指向不确定,指针存的地方是一个垃圾值,未开端化。

空指针:不指向其他地方的指针叫空指针。即指针没有对准,指针存的地址是一个空地址,NULL。

 

什么是 OOA / OOD / OOP ?

OOA(Object Oriented Analysis)  
–面向对象分析

OOD(Object
Oriented Design)     –面向对象设计

OOP(Object Oriented
Programming)–面向对象编程

相关文章