美文网首页
iOS面试题-常规概念

iOS面试题-常规概念

作者: 洁简 | 来源:发表于2018-03-05 15:23 被阅读44次

    简要叙述OC语言的特点

    是根据C语言所衍生出来的语言,继承了C语言的特性,是扩充C的面向对象编程语言,所以具有面向对象的语言特性,如:封装、多态、继承。
    封装 是对象和类概念的主要特性。它是隐藏内部实现,提供外部接口。
    继承 它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
    多态 不同对象以自己的方式响应相同的消息的能力叫做多态。
    总的来说面向对象是一种思想,目的可以让代码重用,接口重用,避免重复代码,逻辑更加清晰,更容易维护,提高编程效率.
    
    另外OC具有动态特性:之所以叫做动态,是因为必须到运行时(runtime)才会做一些事情。(动态特性的三个方面:动态类型、动态绑定、动态加载)
    (1)动态类型:
             动态类型,(id类型)在编译器编译的时候不能被识别出,在运行时(run time),程序运行的时候才会根据语境来识别。
             静态类型,与动态类型相对。在编译的时候就能识别出来,明确的基本类型都属于静态类型。(int、NSString等)
    (2)动态绑定
              (关键词@selector)跳过编译,在运行时动态添加函数调用,运行时才决定调用什么方法,传递什么参数。
    (3)动态加载
             根据需求动态地加载资源。
    

    类别的作用?继承、类别和扩展在实现中有何区别?

    类别的作用:(1)将类的实现分散到多个不同文件或多个不同框架中。
    (2)创建对私有方法的前向引用。
    (3)向对象添加非正式协议。
    扩展:
    只有头文件没有实现文件。只能扩展方法,不能添加成员变量。扩展的方法只能在原类中实现
    类扩展只能针对自定义的类,不能给系统类增加类扩展;
    由于局限性比较多,个人在开发中没有用过.
    继承主要作用:
    1. 重写父类方法
    2. 在父类基础上增加属性,方法,协议
    category 可以在不获悉,不改变原来代码的情况下往里面添加新的方法,只能添加,不能删除修改。
    并且如果类别和原来类中的方法产生名称冲突,则类别将覆盖原来的方法,因为类别具有更高的优先级。
    继承可以增加,修改方法,并且可以增加属性。
    

    OC中类变量的@protected,@private,@public,@package区别

    @protected (默认)该类和所有子类中的方法可以直接访问这样的变量。
    @private 该类中的方法可以访问,子类不可以访问。
    @public   可以被所有的类访问
    @package 本包内使用,跨包不可以
    实际开发中基本都是默认,没有使用过其他的
    

    #import、#include、@class、#import<>和#import""的区别

    #include与#import都是导入头文件的关键字,完整地包含某个文件的内容,后者会自动导入一次,不会重复导入,不会引发交叉编译.
    @class仅仅是声明一个类名,并不会包含类的完整声明,编译效率高.可避免循环依赖,且使用后带来的编译错误.
    #import<>:用于对系统自带的头文件的引用,编译器会在系统文件目录下查找该文件。
    #import"":用户自定义的文件用双引号引用,引用时编译器首先会在用户目录下查找,然后去安装目录中查找,最后在系统文件目录中查找。
    另外:iOS7之后的新特性,可以使用@import 关键词来代理#import引入系统类库。
         使用@import引入系统类库,不需要到build phases中先添加添加系统库到项目中。
    

    什么是设计模式?聊聊你所知道的设计模式。

    1.代理模式
      当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。
      代理+协议的组合。实现1对1的反相传值操作。
    2.观察者模式
      Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。
      KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。
    3.MVC模式
      通过数据模型,控制器逻辑,视图展示将应用程序进行逻辑划分。
      Model View Control, 把模型 视图 控制器 层进行解耦合编写。
    4.单例模式
      确保程序运行期某个类,只有一份实例,用于进行资源共享控制
      系统实例:[UIApplication sharedApplication]。
    5.工厂模式
      通过一个类方法,批量的根据已有模板生产对象。
    在实际开发中,代理、观察者和单例大多数情况都是为了数据传递.
    而MVC可以使代码逻辑更加清晰,更容易维护.
    工厂模式则可以让常用方法重用,使项目更容易维护.
    

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

    1.@property的本质 = ivar (实例变量) + getter (取方法) + setter (存方法)
    “属性”(property)有两大概念:实例变量(ivar)、存取方法(getter + setter)
    2.ivar、 getter 、setter 是如何生成并添加到这个类中的
    这是编译器自动合成的,通过@synthesize 关键字指定,若不指定,默认为@synthesize  propertyName = _propertyName;若手动实现了getter/setter 方法,则不会自动合成。
    现在编译器已经默认为我们添加了@synthesize  propertyName = _propertyName;因此不再手动添加了,除非你真的要改变成员变量名字。
    生成getter方法时,会判断当前属性名是否有“_”,比如声明属性为@property(nonatomic,copy)NSString *_name;那么所生成的成员变量名就会变成“_name”,如果我们要手动生成getter 方法,就要判断是否以“_”开头了。
    

    @property常用属性及如何使用

    读写属性: (readwrite/readonly/setter = /getter = )
    setter语意:(assign/retain/copy)
    原⼦性: (atomic/nonatomic)
    ARC其中属性默认是:readwrite,strong, atomic
    atomic 是默认的属性,表示对象的操作属于原子操作(原子性指事务的一个完整操作。操作成功则提交,失败则回滚),主要是在多线程的环境下,提供多线程访问的安全。
      我们知道在多线程的下对对象的访问都需要先上锁访问后再解锁,保证不会同时有⼏个操作针对同⼀个对象。
      如果编程中不涉及到多线程,不建议使用,因为使用atomic比nonatomic更耗费系统资源。
      注意:atomic的作用只是给getter和setter加了个锁,atomic只能保证代码进入getter或者setter函数内部时是安全的,
      个人在实际开发中还真的没用过。
    nonatomic 表示访问器的访问不是原⼦操作,不支持多线程访问安全,但 是访问性能⾼。
    
    readwrite(默认):readwrite是默认值,表示该属性同时拥有setter和getter。
    readonly: readonly表示只有getter没有setter。
    
    retain 表⽰示对NSObject和及其⼦子类对象release旧值,再retain新值,使对象的应⽤计数增加1。
      该属性只能使⽤用于obejective-c类型对象,不能用于Core Foundation对象。
    assign 是基本数据类型的默认属性,setter方法将传入的参数赋值给实例变量,可以对基本数据类型(如CGFloat, NSInteger,Bool,int,代理,id对象)等使⽤。可以用非OC对象
      该方式会对象直接赋值而不会进行retain操作。
    weak 是修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。常用于delegate,weak必须用于OC对象
    strong 是在iOS引入ARC的时候引入的关键字,也是普通OC对象的默认属性,是retain的一个可选的替代。
      表示实例变量对传入的对象要有所有权关系,即强引用。
      strong跟retain的意思相同并产生相同的代码,但是语意上更好更能体现对象的关系。
    copy 表示重新建立一个新的计数为1的对象,然后释放掉旧的值。常用于NSString,block
    
    另外还有一些如Xcode 6.3新出的关键字nonnull,nullable,主要是为了与swift混编更加方便,注意只能修饰对象,不能修饰基本数据类型
    nullable 表示对象可以是NULL或nil
    nonnull  表示对象不应该为空
    null_resettable: get:不能返回空, set可以为空(注意:如果使用null_resettable,必须 重写get方法或者set方法,处理传递的值为空的情况
    null_unspecified:不确定是否为空
    

    为什么IBOutlet属性是weak的?

    因为既然有外链那么视图在xib或者storyboard中肯定存在,视图已经对它有一个强引用了。 
    IBoutlet 连线到控制器中作为视图的属性时用 weak 修饰就可以了, (用 strong 修饰也可以但是没有必要)
    

    简单描述weak实现原理

    现在都喜欢问一些实现原理,自己对底层的实现了解的不多,只能大概了解一下weak的实现原理.
    Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
    weak 的实现原理可以概括一下三步:
    1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
    2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
    3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,
      然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
    

    id 声明的对象有什么特性?

     id 声明的对象具有运行时的特性,在程序运行时才确定对象的类型。
    可以指向任意类型的OC的对象,与C中的void*万能指针相似。
    运行效率低,不可以使用点语法。
    

    id和nil代表什么(nil和NULL的区别)

    id类型:是一个独特的数据类型,可以转换为任何数据类型,id类型的变量可以存放任何数据类型的对象,
      在内部处理上,这种类型被定义为指向对象的指针,实际上是一个指向这种对象的实例变量的指针
    NULL是宏,是对于C语言指针而使用的,表示空指针
    nil是宏,是对于Objective-C中的对象而使用的,表示对象为空.当向nil发送消息时,不会有异常,程序将继续执行下去;
    Nil是宏,是对于Objective-C中的类而使用的,表示类指向空
    NSNull是类类型,是用于表示空的占位对象,与JS或者服务端的null类似的含意
    

    BOOL/bool/Boolean的区别

    BOOL:
    typedef signed char BOOL;
    #define YES (BOOL)1
    #define NO  (BOOL)0
    bool:
    C99标准定义了一个新的关键字_Bool,提供了布尔类型
    #define bool _Bool
    #define true 1  
    #define false 0
    Boolean:
    typedef unsigned char Boolean;
    enum DYLD_BOOL { FALSE, TRUE };
    

    如表所示:

    Name Typedef Header True Value False Value
    BOOL signed char objc.h YES NO
    bool _Bool (int) stdbool.h true false
    Boolean unsigned char MacTypes.h TRUE FALSE
    NSNumber __NSCFBoolean Foundation.h @(YES) @(NO)
    CFBooleanRef struct CoreFoundation.h kCFBooleanTrue kCFBooleanFalse

    OC的反射机制

    Objective-C语言中的OC对象,都继承自NSObject类。这个类为我们提供了一些基础的方法和协议,我们可以直接调用从这个类继承过来方法。大部分的动态反射支持来自NSObject 类。NSObject是所有类(除了一些很少见的例外)的根类。所以基本常用到的类应该都可以支持反射。

    //class反射:通过类名的字符串形式实例化对象
    Class class = NSClassFromString(@"user"); 
    User *user = [[class alloc] init];
    //将类名变为字符串
    Class class =[User class];
    NSString *className = NSStringFromClass(class);
    
    //SEL反射:通过方法的字符串形式实例化方法
    SEL selector = NSSelectorFromString(@"setName");  
    [stu performSelector:selector withObject:@"Song"];
    //将方法变成字符串
    NSStringFromSelector(@selector*(setName:));
    

    线程与进程的区别和联系?

    这个问题算是很经典了,也经常见到
    概念:
    1、进程是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独立运行的一段程序。    
    2、线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。
    联系:
    1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。    
    2、资源分配给进程,同一进程的所有线程共享该进程的所有资源
    3、线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。    
    4、处理机分给线程,即真正在处理机上运行的是线程。   
    5、线程是指进程内的一个执行单元,也是进程内的可调度实体。
    

    线程死锁的四个条件

    (1)互斥条件:一个资源每次只能被一个进程使用。
    (2)占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    (3)不可强行占有:进程已获得的资源,在末使用完之前,不能强行剥夺。
    (4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
    

    串行并行 同步异步

    队列是来管理线程的,线程里面放着很多的任务,来管理这些任务什么时候在哪些线程里去执行。
    队列分为 [串行队列] 和 [并行队列] :
    串行队列:队列中的线程按顺序执行(不会同时执行)
    并行队列:队列中的线程会并发执行(同时执行)。
    线程里面有非常多的任务(同步,异步)
    同步任务: 优先级高,在线程中有执行顺序,不会开启新的线程
    异步任务: 优先级低,在线程中执行没有顺序,看cpu闲不闲。在主队列中不会开启新的线程,其他队列会开启新的线程
    

    iOS内存管理

    在Objective-C的内存管理中,其实就是引用计数(reference count)的管理。且需要遵循黄金法则.
    iOS内存管理方法有两种:手动引用计数(Manual Reference Counting)和自动引用计数(Automatic Reference Counting)。
    从OS X Lion和iOS 5开始,不再需要程序员手动调用retain和release方法来管理Objective-C对象的内存,而是引入一种新的内存管理机制Automatic Reference Counting(ARC),
    简单来说,它让编译器来代替程序员来自动加入retain和release方法来持有和放弃对象的所有权。
    只有oc对象需要进行内存管理,因为创建对象时,指向对象的指针放在栈中,由系统维护,而指针指向的对象,则是放在堆中,需要开发人员维护
    非oc对象类型比如基本数据类型不需要进行内存管理,会放到栈中.
    对于内存管理可能产生的问题有:
    野指针错误:访问了一块坏的内存(已经被回收的,不可用的内存),会有EXC_BAD_ACCESS错误
    僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用(打开僵尸对象检测)。
    空指针:没有指向任何东西的指针(存储的东西是0,null,nil),给空指针发送消息不会报错。
    

    简述一下自动释放池底层怎么实现?

    说道内存管理就要说一下自动释放池了,当你创建一个新的自动释放池时,它将被添加到栈顶,当一个对象收到发送autorelease消息时,
    他被添加到当前线程的处于栈顶的自动释放池中,当自动释放池被回收时,他们从栈中被删除,并且会给池子里所有的对象都会做一次release操作。
    经典例子:这段代码有什么问题?如何修改?
    for (int i = 0; i < 1000000; i++) {
        NSString *str = @"Abc";
        str = [str lowercaseString];
        str = [str stringByAppendingString:@"xyz"];
        NSLog(@"%@",str);
     }
    带来的问题就是在每执行一次循环,就会有一个str加到当前NSRunloop中的自动释放池中,只有当自动释放池被release的时候,
    自动释放池中的标示了autorelease的这些数据所占用的内存空间才能被释放掉。由于循环次数过大,导致内存空间将被耗尽而没有被释放掉,所以就会出现内存忽然增高又忽然降低的情况,严重会导致内存溢出的现象。
    解决添加一个局部的自动释放池,那么每执行一次循环就会释放一次,则不会造成内存泄露:
    for (int i = 0; i < 1000000; i++) {
            @autoreleasepool {
                NSString *str = @"Abc";
                str = [str lowercaseString];
                str = [str stringByAppendingString:@"xyz"];
                NSLog(@"%@",str);
            }
    }
    新增的自动释放池可以减少内存用量,因为系统会在块的末尾把这些对象回收掉。而上述这些临时对象,正在回收之列。
    

    堆和栈的区别?

    堆栈是两种数据结构,堆,先进先出;  栈,先进后出
    按管理方式分
      对于栈来讲,是由系统编译器自动管理,不需要程序员手动管理
      对于堆来讲,释放工作由程序员手动管理,不及时回收容易产生内存泄露
    按分配方式分
      堆是动态分配和回收内存的,没有静态分配的堆
      栈有两种分配方式:静态分配和动态分配
      静态分配是系统编译器完成的,比如局部变量的分配
      动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同  的,它的动态分配也由系统编译器进行释放,不需要程序员手动管理
    

    沙盒机制的理解和使用

    处于安全考虑,iOS系统的沙盒机制规定每个应用都只能访问当前沙盒目录下面的文件(也有例外,比如在用户授权情况下访问通讯录,相册等)
    特点:1.每个应用程序的活动范围都限定在自己的沙盒里
        2.不能随意跨越自己的沙盒去访问别的应用程序沙盒中的内容(iOS8已经部分开放访问extension)
        3.在访问别人沙盒内的数据时需要访问权限。
    在开发中常常需要数据存储的功能,比如存取文件,归档解档等。
    沙盒根目录结构:Documents、Library、tmp以及新加入的SystemData。
    AppName.app目录:该目录iOS8之前是和Documents那几个在同一目录下,但iOS8之后移动到Container—>bundle—>Application中,它包含了应用程序本身的数据,包括资源文件和可执行文件等。
      程序启动以后,会根据需要从该目录中动态加载代码或资源到内存,这里用到了lazy loading的思想。
      由于应用程序必须经过签名,所以您在运行时不能对这个目录中的内容进行修改,否则可能会使应用程序无法启动。
    Documents:这个目录存放用户数据。存放用户可以管理的文件;iTunes备份和恢复的时候会包括此目录。
      APP的数据库表; 必要的一些图标本地缓存; 重要的plist文件,如当前登录人的信息等可存放到此目录下.
    Library 目录:这个目录下有两个子目录:
      Preferences 目录:NSUserDefaults的数据存放于此目录下。
      Caches 目录:用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息。比如网络请求的数据。但是它缓存数据在设备低存储空间时可能会被删除,
    Library可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份。
    tmp 目录:这个目录用于存放临时文件,保存应用程序再次启动过程中不需要的信息。该路径下的文件不会被iTunes备份。该目录下的东西随时有可能被系统清理掉
    SystemData目录:最近查看目录才发现的,新加入的一个文件夹, 存放系统的一些东西.具体没太研究.
    

    事件响应者链的概念

    响应者链表示一系列的响应者对象.事件被交给由第一响应者对象处理,如果第一响应者不处理,事件被沿着响应者链向上传递,交给下一响应者(next responder).
    一般来说,第一响应者是视图对象或者其子类对象,当其被触摸后事件被交由它处理,
    如果它不处理,事件就会被传递给它的视图控制器对象(如果存在),然后就是它的父视图(superView)对象(如果存在),以此类推,直到顶层视图.
    接下来会沿着顶层视图(top view)到窗口(UIWindow对象)再到程序(UIApplication对象).如果整个过程都没有响应这个事件,该事件就会被丢弃.
    一般情况下,在响应者中只要由对象处理事件,事件就停止传递.但有时候可以在视图响应方法中根据一些条件判断来决定是否需要继续传递事件.
    对于事件响应链在实际开发偶尔会用到,当遇到某个视图不能响应时首先就要考虑到响应链的传递问题,然后根据代码调整.
    比较重要的函数:hitTest:withEvent:方法和pointInside方法
    简单来说就是:事件的传递是从上到下(父控件到子控件),
    事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。
    

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

    1.@property有两个对应的词,一个是@synthesize,一个是@dynamic。
      如果@synthesize和@dynamic都没写,那么默认的就是@synthesize var = _var;
    2.@synthesize的语义是如果你没有手动实现setter方法和getter方法,
      那么编译器会自动为你加上这两个方法。
    3.@dynamic告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,
      然后你没有提供setter方法和getter方法,编译的时候没问题,
      但是当程序运行到instance.var = someVar,由于缺setter方法会导致程序崩溃;
      或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃。
      编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
    实际开发中我们常会用到重写getter方法来做懒加载,或者用setter方法来完成调用.很少会将两个同时重写.
    

    类方法和实例方法有什么区别?

    类方法:在OC类定义方法时以 + 开头的方法,又称为静态方法。
      它不用实例就可以直接调用的方法,一般是有返回值的,返回对应的实例(数组、字符串等),还有可能就是本身类的实例对象。
    实例方法:在OC定义中以 - 开头的方法,原理是向某个对象发送一条消息,如果对象中有相应的消息就会做出回应,OC用的就是这种消息模式.
    类方法:
    类方法属于类对象
    类方法只能通过类对象调用
    类方法中的self是类对象
    类方法可以调用其他类方法
    类方法中不能访问成员变量
    类方法不能直接调用对象方法
    实例方法:
    实例方法是属于实例对象的
    实例方法只能呢通过实例对象调用
    实例方法中的self是实例对象
    实例方法中可以访问成员变量
    实例方法中直接调用实例方法
    实例方法中也可以调用类方法(通过类名)
    

    浅拷贝和深拷贝的区别?

    浅拷贝:只复制指向对象的指针,而不复制引用对象本身。只是新创建了类的空间,然后将属性的值复制一遍;
      对于属性所指向的内存空间并没有重新创建;因此通过浅拷贝的新旧两个对象的属性其实还是指向一块相同的内存空间 
    深拷贝:复制引用和对象本身。不仅仅新创建了类的空间,还新创建了每一个属性对应的空间,所以深拷贝也称为完全拷贝;
      通过深拷贝得来的新对象和旧对象,两个对象的属性都是指向各自的内存空间,不再共享空间
    另外可变对象复制(copy,mutableCopy)的都是深拷贝;不可变对象copy是浅拷贝,mutableCopy是深拷贝。但是注意copy返回的都是不可变对象,如果对copy返回值去调用可变对象的接口就会crash. 
    

    UDP和TCP的区别是什么?

    TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)。
    UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。
    

    TCP/IP建立连接的过程?

    - 在TCP/IP 协议中,TCP协议提供可靠的连接服务,采用三次握手建立连接;
    - 第一次握手:建立连接时,客户端发送连接请求到服务器,并进入SYN_SEND状态,等待服务器确认;
    - 第二次握手:服务器收到客户端连接请求,向客户端发送允许连接应答,
      此时服务器进入SYN_RECV状态;
    - 第三次握手:客户端收到服务器的允许连接应答,向服务器发送确认,客户端和服务器进入通信状态,
      完成三次握手。
     (所谓的三次握手,就是要有三次连接信息的发送、接收过程。
       TCP连的建立需要进行三次连接信息的发送、接收。)
    

    相关文章

      网友评论

          本文标题:iOS面试题-常规概念

          本文链接:https://www.haomeiwen.com/subject/hsqhnxtx.html