Objective-C 面试要点

基础语法

关键字

问题

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,可以方便地对指定对象的某个属性进行观察,当属性发生变化时,进行通知.


实现步骤


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


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


解除观察者身份



©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 5,803评论 0 9
  • 禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C C...
    GrayLand阅读 5,560评论 1 10
  • 1.设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类...
    司马DE晴空阅读 5,113评论 0 7
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 14,735评论 0 38
  • 就好像你知道那个一直陪着你,不管喜怒哀乐都陪着你的那个人早晚会离开,你也知道你终究不是TA的良人,只是会舍不得,你...
    burning0717阅读 988评论 0 0