美文网首页
Objective-C 面试要点

Objective-C 面试要点

作者: LuckyZong | 来源:发表于2018-03-13 21:25 被阅读0次

    基础语法

    关键字

    问题

    1,关键字const有什么含意?修饰类呢?

    1.欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

    2.对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者同时指定为 const;

    3.在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

    4.对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成员变量;

    5.对于类的成员函数,有时候必须指定其返回值为 const 类型,以使得其返回值不为“左值”。

    2,#define和const变量有什么区别?

    #define在预处理阶段进行简单的替换,const在编译阶段使用

    #define不做类型检查,仅仅展开替换,const有数据类型,会执行类型检查

    #define不分配内存,仅仅展开替换,const会分配内存

    #define不能调试,const可以调试

    #define定义的常量在替换后运行过程中会不断地占用内存,而const定义的常量存储在数据段,只有一份copy,效率更高

    #definde可以定义一些简单的函数,const不可以

    作者:启发禅悟

    链接:https://www.jianshu.com/p/be03f141a9c5

    3,请用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

    #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)_U_LONG

    #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)

    懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。

    意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。

    如果你在你的表达式中用到_U_LONG(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

    作者:启发禅悟

    链接:https://www.jianshu.com/p/fa8c996d48e4

    4,写一个”标准"宏MIN ,这个宏输入两个参数并返回较小的一个。

    #define MIN(A,B) ((A) <= (B) ? (A) : (B))

    这个测试是为下面的目的而设的:

    标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。

    三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比 if-then-else 更优化的代码,了解这个用法是很重要的。

    懂得在宏中小心地把参数用括号括起来

    我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

    least = MIN(*p++, b);

    结果是:

    ((*p++) <= (b) ? (*p++) : (b))

    这个表达式会产生副作用,指针p会作两次++自增操作。

    作者:启发禅悟

    链接:https://www.jianshu.com/p/8263aa619576

    5,关键字static有什么作用?

    函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

    在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

    在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

    在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

    在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。

    作者:启发禅悟

    链接:https://www.jianshu.com/p/05444d9266d7

    6,请谈谈#include与#import的区别、#import与@class 的区别

    #include和#import 其效果相同,都是导入类中定义的行为(方法);

    #import 不会引起交叉编译,确保头文件只会被导入一次;

    @class 表明只定义了类的名称,而具体类的行为是未知的,一般用于.h 文件

    @class比#import编译效率更高。此外@class和#import的主要区别在于解决引用死锁的问题。

    作者:启发禅悟

    链接:https://www.jianshu.com/p/e67441a08994

    属性访问

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

    @property 的本质

    @property = ivar + getter + setter;

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

    “属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为 Objective-C 2.0的一部分。 而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示:

    编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。 所以你也可以这么说:

    @property = getter + setter;

    ivar、getter、setter 是如何生成并添加到这个类中的?

    自动合成( autosynthesis)

    完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为 _firstName与 _lastName。也可以在类的实现代码里通过@synthesize语法来指定实例变量的名字.

    我为了搞清属性是怎么实现的,曾经反编译过相关的代码,他大致生成了五个东西

    OBJC_IVAR_$类名$属性名称:该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。

    setter 与 getter 方法对应的实现函数

    ivar_list :成员变量列表

    method_list :方法列表

    prop_list:属性列表

    作者:启发禅悟

    链接:https://www.jianshu.com/p/e67441a08994

    2,用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

    因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.

    如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.

    copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

    作者:启发禅悟

    链接:https://www.jianshu.com/p/08348d1e1106Block

    Block

    1,什么是block

    2,谈谈block使用时的注意点?

    在block内部使用外部指针且会造成循环引用情况下,需要用__weak修饰外部指针__weak typeof(self) weakSelf = self;

    在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下__strong typeof(self) strongSelf = weakSelf;

    如果需要在block内部改变外部变量的话,需要在用__block修饰外部变量

    weakSelf的定义应该放在block外面。

    我不太清楚你这个weakSelf指向的self具体是什么?当前的ViewController?

    那么在这个ViewController被销毁前,很大程度上你是可以取得weakself的值的。但你可以想象下,如果在延时函数触发前,你的ViewController已经被关闭,那么你就不能取得weakSelf的值了。

    创建一个Student类

    //  Student.h

    #import

    @interface Student : NSObject

    @property(nonatomic, strong) NSString *name;

    @end

    //  Student.m

    #import "Student.h"

    @implementation Student

    @end

    随便在一个函数中调用,例如下面的函数中:

    - (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

    Student *student = [[Student alloc] init];

    student.name = @"Tom";

    __weak typeof(Student) *weakSelf = student;

    void(^block)()=^{

    NSLog(@"Student Name = %@",weakSelf.name);

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"Student Name(Delay) = %@",weakSelf.name);

    });

    };

    block();

    }

    输出

    2017-02-23 10:55:03.876 TestBlock[1904:336958] Student Name = Tom

    2017-02-23 10:55:05.876 TestBlock[1904:337002] Student Name(Delay) = (null)

    可以理解,dispatch_after函数在viewDidLoad函数结束后执行,此时student对象已经被销毁,所以weakSelf所引用的内容已经不存在,所以取得不到Student Name。

    因此对于Block内部的延时函数,为了保证延时之后Block所引用的对象还存在,需要用__strongSelf引用。上面的代码修改为:

    - (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

    Student *student = [[Student alloc] init];

    student.name = @"Tom";

    __weak typeof(Student) *weakSelf = student;

    void(^block)()=^{

    __strong typeof(Student) *strongSelf = weakSelf;

    NSLog(@"Student Name = %@",strongSelf.name);

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"Student Name(Delay) = %@",strongSelf.name);

    });

    };

    block();

    }

    输出为

    2017-02-23 10:56:31.181 TestBlock[1942:347739] Student Name = Tom

    2017-02-23 10:56:33.182 TestBlock[1942:348001] Student Name(Delay) = Tom

    3,使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?

    系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api 需要考虑:

    所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:

    [UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];

    [[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"

                                                      object:nil

                                                      queue:[NSOperationQueue mainQueue]

                                                  usingBlock:^(NSNotification * notification) {

                                                      self.someProperty = xyz; }];

    这些情况不需要考虑“引用循环”。

    但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:

    __weak __typeof__(self) weakSelf = self;

    dispatch_group_async(_operationsGroup, _operationsQueue, ^

    {

        __typeof__(self) strongSelf = weakSelf;

        [strongSelf doSomething];

        [strongSelf doSomethingElse];

    } );

    类似的:

    __weak __typeof__(self) weakSelf = self;

    _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"

                                                                  object:nil

                                                                  queue:nil

                                                              usingBlock:^(NSNotification *note) {

                                                                  __typeof__(self) strongSelf = weakSelf;

                                                                  [strongSelf dismissModalViewControllerAnimated:YES];

                                                              }];

    self --> _observer --> block --> self 显然这也是一个循环引用。

    KVC/KVO

    1,什么是KVC/KVO

    KVC,即是指 [NSKeyValueCoding], 一个非正式的 Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于 KVC 实现的关键技术之一。

    一个对象拥有某些属性。比如说,一个 Person 对象有一个 name 和一个 address 属性。以 KVC 说法,Person 对象分别有一个 value 对应他的 name 和 address 的 key。 key 只是一个字符串,它对应的值可以是任意类型的对象。从最基础的层次上看,KVC 有两个方法:一个是设置 key 的值,另一个是获取 key 的值。如下面的例子:

    void changeName(Person *p, NSString *newName)

    {

        // using the KVC accessor (getter) method

        NSString *originalName = [p valueForKey:@"name"];

        // using the KVC  accessor (setter) method.

        [p setValue:newName forKey:@"name"];

        NSLog(@"Changed %@'s name to: %@", originalName, newName);

    }

    现在,如果 Person 有另外一个 key 配偶(spouse),spouse 的 key 值是另一个 Person 对象,用 KVC 可以这样写:

    void logMarriage(Person *p)

    {

        // just using the accessor again, same as example above

        NSString *personsName = [p valueForKey:@"name"];

        // this line is different, because it is using

        // a "key path" instead of a normal "key"

        NSString *spousesName = [p valueForKeyPath:@"spouse.name"];

        NSLog(@"%@ is happily married to %@", personsName, spousesName);

    }

    key 与 key path 要区分开来,key 可以从一个对象中获取值,而 key path 可以将多个 key 用点号 “.” 分割连接起来,比如:

    [p valueForKeyPath:@"spouse.name"];

    相当于这样……

    [[p valueForKey:@"spouse"] valueForKey:@"name"];

    Key-Value Observing (KVO)

    Key-Value Observing (KVO) 建立在 KVC 之上,它能够观察一个对象的 KVC key path 值的变化。举个例子,用代码观察一个 person 对象的 address 变化,以下是实现的三个方法:

    watchPersonForChangeOfAddress: 实现观察

    observeValueForKeyPath:ofObject:change:context: 在被观察的 key path 的值变化时调用。

    dealloc 停止观察

    static NSString *const KVO_CONTEXT_ADDRESS_CHANGED = @"KVO_CONTEXT_ADDRESS_CHANGED"

    @implementation PersonWatcher

    -(void) watchPersonForChangeOfAddress:(Person *)p

    {

        // this begins the observing

        [p addObserver:self

            forKeyPath:@"address"

              options:0

              context:KVO_CONTEXT_ADDRESS_CHANGED];

        // keep a record of all the people being observed,

        // because we need to stop observing them in dealloc

        [m_observedPeople addObject:p];

    }

    // whenever an observed key path changes, this method will be called

    - (void)observeValueForKeyPath:(NSString *)keyPath

                          ofObject:(id)object

                            change:(NSDictionary *)change

                          context:(void *)context

    {

        // use the context to make sure this is a change in the address,

        // because we may also be observing other things

        if(context == KVO_CONTEXT_ADDRESS_CHANGED) {

            NSString *name = [object valueForKey:@"name"];

            NSString *address = [object valueForKey:@"address"];

            NSLog(@"%@ has a new address: %@", name, address);

        }

    }

    -(void) dealloc;

    {

        // must stop observing everything before this object is

        // deallocated, otherwise it will cause crashes

        for(Person *p in m_observedPeople){

            [p removeObserver:self forKeyPath:@"address"];

        }

        [m_observedPeople release];

        m_observedPeople = nil;

        [super dealloc];

    }

    -(id) init;

    {

        if(self = [super init]){

            m_observedPeople = [NSMutableArray new];

        }

        return self;

    }

    @end

    这就是 KVO 的作用,它通过 key path 观察对象的值,当值发生变化的时候会收到通知。

    Notification

    1,谈谈NSNotification 和 KVO 的使用场景?

    两者都是观察者模式,不同的是,KVO是被观察者直接发送消息给观察者,是对象间的交互,而通知则是观察者和被观察者通过通知中心对象之间进行交互,即消息由被观察者发送到通知中心对象,再由中心对象发给观察者,两者之间并不进行直接的交互

    NSNotification 通知中心

    苹果提供的一种消息机制, 观察者只要向消息中心注册, 即可接受其他对象发送来的消息,消息发送者和消息接受者两者可以互相一无所知,完全解耦。NSNotification可以应用于任意时间和任何对象,观察者可以有多个, 这也正是他跟delegate的区别.

    使用步骤:

    注册一个观察者

    给通知中心发送一个消息

    清除观察者

    应用场景:

    控制器与一个或多个任意的对象进行通信(监控)

    UIDevice通知

    键盘通知

    KVO

    Key-Value Observing,是Foundation框架提供的一种机制,使用KVO,可以方便地对指定对象的某个属性进行观察,当属性发生变化时,进行通知.

    实现步骤

    由被观察的对象调用方法, 添加观察者

    一旦被观察的属性发生改变, 系统会调用这个方法

    解除观察者身份


    相关文章

      网友评论

          本文标题:Objective-C 面试要点

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