iOS 面试题(指针、KVO、单例、结构体等)

1,842 阅读34分钟

指针(nil Nil null NSNull / id instancetype void *)
KVO、KVC、Notification、代理(delegate)
单例 结构体
面试题(static、const、volatile、推送机制、Objective C中的selector等)

指针

指针本身不可变,但指向的数据结构可以改变。
指针本质就是指向一块内存地址,通过指针传递,可实现复杂的内存访问。

几种指针类型

  1. 指向一块内存的地址的指针。
  2. 函数指针:指向一个函数的入口地址。
  3. 指向指针:具体内容为一个指针的值,是一个地址。此时指针指向的变量长度也是4位。

nil Nil null NSNull 区别

nil 是宏,OC对象使用,表示对象为空;
Nil 是宏,OC类使用,表示类指向空;
NULL 是宏,C语言使用,表示空指针(比如SEL等);
NSNull 是类的类型,表示空的占位对象(比如NSArray NSDictionary中charity非nil的空值);

id、id 和 instancetype 的区别

id 是指向 struct objc_object 的一个指针,即 id 是一个指向任何一个继承了 Object(或者NSObject)类的对象。需要注意的是 id 是一个指针,所以你在使用 id 的时候不需要加星号。比如 id foo = nil 定义了一个 nil 指针,这个指针指向 NSObject 的一个任意子类。而 id *foo = nil 则定义了一个指针,这个指针指向另一个指针,被指向的这个指针指向 NSObject 的一个子类。

  • id 和 instancetype 的区别

    1. instancetype 只能作为函数或者方法的返回值。
    2. id 能作为函数或者方法的返回值、参数类型,也能用来定义变量。
    3. instancetype 在编译期确定实例的类型,而使用 id 的话,编译器不检查类型, 运行时才检查类型。
  • idvoid * 的区别
    定义上来看:
    id 表示“对未知类的某些随机Objective-C对象的引用”。
    void * 是 C 语言中常用的,意思是“对带有未类型化/未知内容的随机块内存的引用”,void*对于被调用者是完全不透明的。
    大致可以理解为 id 是指向 OC 对象的指针。void * 是任何东西的指针。 编译警告看:
    id 仅在编译器在 @interface 看到的任何声明中都没有声明被调用的方法时,才尝试警告该类型的方法。
    void * 永远不会收到编译警告。
    您可以使用 void * 代替 id,但不建议,因为您永远都不会收到任何编译器警告。
    作为参数:
    id 修饰参数是作为保留对象,void * 修饰代表一块内存,您可以随意将其用于任何目的。
    void * 一种常见且有效的用法是作为通过其他API传递的不透明数据引用。,如 NSArray 的 mySortFunc 方法:

    NSInteger mySortFunc(id left, id right, void *context) { ...; }
    

    在这种情况下,NSArray 只会将您作为 context 参数传入的任何内容作为参数传递给方法context。就 NSArray 而言,它是指针大小的数据的不透明块,您可以随意将其用于任何目的。

self. 跟 self-> 区别?

self. 是调用 get 方法或者 set 方法
self 是当前本身,是一个指向当前对象的指针
self-> 是直接访问成员变量

指针与地址的区别

  1. 指针意味着已经有一个指针变量存在,他的值是一个地址。指针变量本身也存放在一个长度为四个字节的地址当中,而地址概念本身并不代表有任何变量存在。
  2. 指针的值,如果没有限制,通常是可以变化的,也可以指向另外一个地址。地址表示内存空间的一个位置点,他是用来赋给指针的。
  3. 地址本身是没有大小概念。指针指向变量的大小,取决于地址后面存放的变量类型。

怎样防止指针的越界使用问题?

必须让指针指向一个有效的内存地址,具体:

  1. 防止数组越界
  2. 防止向一块内存中拷贝过多的内容
  3. 防止使用空指针
  4. 防止改变const修改的指针
  5. 防止改变指向静态存储区的内容
  6. 防止两次释放一个指针
  7. 防止使用野指针

KVO、KVC、Notification

KVC

KVC是键值编码(NSKeyValueCoding),一个非正式的 Protocol,提供一种机制来间接访问对象的属性。特点是通过指定表示要访问的属性名字的字符串标识符,可以进行类的属性读取和设置;

  • 对于kvc机制如何通过key寻找到value:
    当通过KVC调用对象时,比如:[self valueForKey:@”someKey”]时,程序会自动试图通过几种不同的方式解析这个调用。首先查找对象是否带有 someKey 这个方法,如果没找到,会继续查找对象是否带有someKey这个实例变量(iVar),如果还没有找到,程序会继续试图调用 -(id) valueForUndefinedKey:这个方法。如果这个方法还是没有被实现的话,程序会抛出一个NSUndefinedKeyException异常错误。(注:Key-Value Coding查找方法的时候,不仅仅会查找someKey这个方法,还会查找getsomeKey这个方法,前面加一个get,或者_someKey以及_getsomeKey这几种形式。)
    设计valueForUndefinedKey:方法的主要目的是当你使用-(id)valueForKey方法从对象中请求值时,对象能够在错误发生前,有最后的机会响应这个请求。

KVO

KVO是键值观察,就是基于 KVC 实现的关键技术之一。特点是利用键值观察可以注册成为一个对象的观察者,在该对象的某个属性变化时收到通知。
KVO使用:观察者添加 addobserver:forkeyPath:options:context:后,被观察者的keypath值发生变化(注意单纯改变值不会调用此方法,只有通过settter来改变值才会触发KVO),就会在观察者里调用方法observerValueForKeyPath:ofobject:change:context:因此实现此方法来对KVO发出的通知做出响应.

具体用到过的一个地方是对于按钮点击变化状态的的监控。
比如我自定义的一个button
[self addObserver:self forKeyPath:@"highlighted"options:0 context:nil];
#pragma mark KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if([keyPath isEqualToString:@"highlighted"] ) {
        [self setNeedsDisplay];
    }
}
对于系统是根据keypath去取的到相应的值发生改变,理论上来说是和kvc机制的道理是一样的。
  • KVO 内部实现原理:

    1. KVO是基于runtime实现的
    2. 当某个类的对象第一次被观察时,系统会在运行期动态的创建类的一个派生类,在这个派生类中重写基类中的任何被观察属性的setter方法。派生类在被重写的setter方法实现真正的通知机制(Person -> NSKVONotifying_Person)
  • 使用步骤:

    1. 注册观察者(为被观察这指定观察者以及被观察者属性)
    2. 实现回调方法
    3. 触发回调方法
    4. 移除观察者
  • KVO崩溃原因:
    被观察的对象销毁掉了(被观察的对象是一个局部变量)。
    观察者被释放掉了,但是没有移除监听(如pop等)。
    注册的监听没有移除掉,又重新注册了一遍监听。

KVO 代码举例
- (void)testKvo {
    HMPerson *p = [[HMPerson alloc] init];
    p.age = 20;
    
    [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    
    p.age = 30;
    p.age = 40;
    
    self.p = p;
}

- (void)dealloc {
    [self.p removeObserver:self forKeyPath:@"age"];
}

/**
 *  当监控的某个属性的值改变了就会调用
 *
 *  @param keyPath 属性名(哪个属性改了?)
 *  @param object  哪个对象的属性被改了?
 *  @param change  属性的修改情况(属性原来的值、属性最新的值)
 *  @param context void * == id
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"%@对象的%@属性改变了:%@", object, keyPath, change);
}

Notification

观察者模式,controller向defaultNotificationCenter添加自己的notification,其他类注册这个 notification就可以收到通知,这些类可以在收到通知时做自己的操作(多观察者默认随机顺序发通知给观察者们,而且每个观察者都要等当前的某个观察者的操作做完才能轮到他来操作,可以用NotificationQueue的方式安排观察者的反应顺序,也可以在添加观察者中设定反映时间,取消观察需要在viewDidUnload跟dealloc中都要注销)。

是否可以把比较耗时的操作放在通知中?

如果是在异步线程发的通知,那么就可以执行比较耗时的操作;
如果在主线程发的通知,那么就不可以执行比较耗时的操作。
因为在哪个线程发通知就在哪个线程执行操作。

协议(Protocol)和代理(delegate)

协议: 相当于一个方法列表,多个类可以共享的方法的列表。
代理: 它是一个设计模式,本质就是两个对象之间的调用id的范型。

协议(Protocol)
  • 作用

    1. 用来声明一些方法。
    2. 也就说,一个Protocol是由一系列的方法声明组成的。
    3. 任何类只要遵守了Protocol, 就相当于拥有了Protocol的所有方法声明。
  • 书写格式

    @protocol 协议名称
    // 方法声明列表
    // 协议中有2个关键字可以控制方法是否要实现(默认是@required,在大多数情况下,用途在于程序员之间的交流)
    @required // 这个方法必须要实现(若不实现,编译器会发出警告)
    @optional // 这个方法不一定要实现
    @end
    

    类遵守协议:

    @interface 类名 : ⽗父类 <协议名称1, 协议名称2,...>
    @end
    

    协议遵守协议:
    一个协议可以遵守其他多个协议,一个协议遵守了其他协议,就相当于拥有了其他协议中的方法声明。

    @protocol 协议名称 <协议1, 协议2>
    @end
    
  • 基协议
    NSObject是一个基类,最根本最基本的类,任何其他类最终都要继承它。还有名字也叫NSObject的协议,它是一个基协议,最根本最基本的协议。 NSObject协议中声明很多最基本的方法,descriptionretainrelease,建议每个新的协议都要遵守NSObject协议。

代理(delegate)

  • 代理的目的
    1. 两个对象之间传递数据和消息,代理的目的是改变或传递控制链。允许一个类在某些特定时刻通知到其他类,而不需要获取到那些类的指针。可以减少框架复杂度。另外一点,代理可以理解为java中的回调监听机制的一种类似。
    2. 解耦,拆分业务逻辑

代码举例

反向代理传值实现过程

委托方:.h
//制定协议
@protocol ViewController4Delegate <NSObject>
- (void)labelFontSize: (CGFloat)fontSize;
@end
@interface ViewController4 : FatherViewController
//拥有一个代理人
@property (assign, nonatomic)id <ViewController4Delegate>delegate;
@end
委托方:.m
//传值 如果拥有方法就执行方法
    if ([self.delegate respondsToSelector:@selector(labelFontSize:)]) {
        [self.delegate labelFontSize:_fontSize];
    }
代理方:.h
@interface ViewController3 : FatherViewController <ViewController4Delegate>//遵守协议
@end
代理方.m
#pragma mark 实现ViewController4Delegate的协议方法
- (void)labelFontSize:(CGFloat)fontSize{
    _label.font = [UIFont systemFontOfSize:fontSize];
}
事件
vc4.delegate = self;//设置代理人

Notification、KVO、代理 对比

通知和协议的不同之处?
  1. 协议有控制链(has-a)的关系,通知没有。
  2. 通过NSNotification可以给多个对象传递数据和消息。例如将Module层的变化,通知到多个Controller对象时,可以使用 NSNotification;
    protocol(代理模式)只能给一个对象传递数据和消息。在通知模式中,往往是一对多的关系。
  3. delegate 效率比 notification 高。
  4. delegate方法比notification更加直接,最典型的特征是,delegate方法往往需要关注返回值,也就是delegate方法的结果。
Notification和KVO的区别和用法是什么?

相同: 两者都是观察者模式

区别: KVO只能对属性做出反应,不会用来对方法或者动作做出反应。如果是只需要观察某个对象的某个属性,可以使用KVO。
KVO是被观察者直接发送消息给观察者,是对象间的直接交互。只能检测类中属性,并且属性名都是通过NSString来查找,编译器不会帮你检测对错和补全,纯手敲会比较容易出错。
通知则是两者都和通知中心对象交互,对象之间不知道彼此。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后,再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限与属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用灵活。

notification优缺点:
优势:

  1. 不需要写多少代码,实现比较简单
  2. 一个对象发出的通知,多个对象能进行反应,一对多的方式实现很简单
    缺点:
  3. 编译期不会接茬通知是否能被正确处理
  4. 释放注册的对象时候,需要在通知中心取消注册
  5. 调试的时候,程序的工作以及控制流程难跟踪
  6. 需要第三方来管理controller和观察者的联系
  7. controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况
  8. 通知发出后,发出通知的对象不能从观察者获得任何反馈。

KVO优缺点:
优点:

  1. 提供一个简单地方法来实现两个对象的同步
  2. 能对非我们创建的对象做出反应
  3. 能够提供观察的属性的最新值和先前值
  4. 用keypaths来观察属性,因此也可以观察嵌套对象
    缺点:
  5. 观察的属性必须使用string来定义,因此编译器不会出现警告和检查
  6. 对属性的重构将导致观察不可用
  7. 复杂的“if”语句要求对象正在观察多个值,这是因为所有的观察都通过一个方法来指向
Notification、KVO、代理 选择

通知比较灵活(1个通知能被多个对象接收, 1个对象能接收多个通知);
代理比较规范,但是代码多(默认是1对1);
KVO性能不好(底层会动态产生新的类),只能监听某个对象属性的改变, 不推荐使用(1个对象的属性能被多个对象监听, 1个对象能监听多个对象的其他属性)

KVO、通知、协议文章推荐

深度解析三者的不同
手动触发KVO

其他

循环引用
  • 委托和委托方双方的property声明用什么属性?为什么?
    委托和委托方双方的property声明属性都是assign而不是retain。
    为了避免循环引用造成的内存泄露。

  • 为什么很多内置类,如UITableViewController的delegate属性都是assign而不是retain?
    会引起循环引用。
    tableView的代理一般都是它所属的控制器,控制器会对它内部的view做一次retain操作,假设tableView也对代理(控制器)做一次retain操作,那么就出现循环retain问题。
    这里delegate我们只是想得到实现了它delegate方法的对象,然后拿到这个对象的指针就可以了,我们不期望去改变它或者做别的什么操作,所以我们只要用assign拿到它的指针就可以了。而用retain的话,计数器加1。我们有可能在别的地方期望释放掉delegate这个对象,然后通过一些判断比如说它是否已经被释放,做一些操作。但是实际上它retainCount还是1,没有被释放掉,要在UITableViewController的dealloc里面才被释放掉(这里我只是举个例子,一般retain的对象都是在dealloc里被释放)。这里就会造成一些问题出现。而如果你确定不会有冲突的问题出现的话,或者你也希望用到delegate的这个对象,直到你不用它为止,那么用retain也未尝不可,只是需要最后release一次。

  • 循环引用的问题这样理解:
    创建了两个类A和B,现在引用计数都是1。现在让A和B互相引用(A有一个属性是B对象,属性说明是retain;B有一个属性是A对象,属性说明是retain),现在两个对象的引用计数都增加了1,都变成了2。现在执行[A release]; [B release];,这时你发现A和B将无法释放,因为要想释放A必须先释放B,在B的dealloc方法中再释放A。同理,要想释放B必须先释放A,在A的dealloc方法中再释放B。所以这两个对象将一直存在在内存中而不释放。这就是所谓的循环引用的问题。要想解决这个问题,一般的方法可以将引用的属性设置为assign,而不是retain来处理。

iOS中回调机制,并作简单的比较
  1. 目标动作对:当两个对象之间有比较紧密的关系时,如视图控制器与其下的某个视图。
  2. 代理:也叫委托,当某个对象收到多个事件,并要求同一个对象来处理所有事件时。委托机制依赖于某个协议定义的方法来发送消息。
  3. 通知:当需要多个对象或两个无关对象处理同一个事件时。
  4. Block:适用于回调只发生一次的简单任务。

单例

Foundation 和 Application Kit 框架中的一些类只允许创建单件对象,即这些类在当前进程中的唯一实例。举例来说,NSFileManager 和 NSWorkspace 类在使用时都是基于进程进行单件对象的实例化。当向这些类请求实例的时候,它们会向您传递单一实例的一个引用,如果该实例还不存在,则首先进行实例的分配和初始化。单件对象充当控制中心的角色,负责指引或协调类的各种服务。如果类在概念上只有一个实例(比如NSWorkspace),就应该产生一个单件实例,而不是多个实例。

怎样实现一个单例模式的类,给出思路,不写代码。 · 首先必须创建一个全局实例,通常存放在一个全局变量中,此全局变量设置为nil · 提供工厂方法对该全局实例进行访问,检查该变量是否为nil,如果nil就创建一个新的实例,最后返回全局实例 · 全局变量的初始化在第一次调用工厂方法时会在+allocWithZone:中进行,所以需要重写该方法,防止通过标准的alloc方式创建新的实例 · 为了防止通过copy方法得到新的实例,需要实现-copyWithZone方法 · 只需在此方法中返回本身对象即可,引用计数也不需要进行改变,因为单例模式下的对象是不允许销毁的,所以也就不用保留 · 因为全局实例不允许释放,所以retain,release,autorelease方法均需重写

使用dispatch_once实现单例

很多人实现单例会这样写:

@implementation XXClass
+ (id)sharedInstance {
    static XXClass *sharedInstance = nil;
    @synchronized(self) {
        if (!sharedInstance) {
            sharedInstance = [[self alloc] init];
        }
    }
    return sharedInstance;
}

相比之下:

@implementation XXClass
+ (id)sharedInstance {
    static XXClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!sharedInstance) {
            sharedInstance = [[self alloc] init];
        }
    });
    return sharedInstance;
}

使用dispatch_once可以保证线程安全,开发者无需担心加锁或同步。此外,dispatch_once更高效,它没有使用重量级的同步机制。相反,此函数采用“原子访问”来查询标记,以判断其所对应的代码原来是否已经执行过。在64位Mac OS X上测试,后者的执行速度要比前者快一倍。

结构体

常用结构体

typedef struct _NSRange {
    NSUInteger location;
    NSUInteger length;
} NSRange;
struct CGPoint {
  CGFloat x;
  CGFloat y;
};
typedef struct CGPoint CGPoint;
typedef CGPoint NSPoint;

构建函数: NSMakePoint(10,9); CGPointMake(10,9);(常用) 快速打印使用函数: NSStringFromPoint(point)

struct CGSize {
  CGFloat width;
  CGFloat height;
};
typedef struct CGSize CGSize;
typedef CGSize NSSize;

表示宽度和高度,同样有如下方法: NSMakeSize(10, 8); CGSizeMake(10, 8);(常用) NSStringFromSize(size);

struct CGRect {
  CGPoint origin;
  CGSize size;
};
typedef struct CGRect CGRect;
typedef CGRect NSRect;

存储位置和尺寸,也就是一个矩形范围。
创建:
NSMakeRect(10, 10, 80, 80);
CGRectMake(10, 10, 80, 80);(常用)
打印:
NSStringFromRect(rect)

结构体和类有什么区别

  1. 结构体只能封装属性,而类不仅能封装属性还可以封装方法。
  2. 结构体分配在栈,类分配在堆(栈效率高,空间小,堆反之)。
  3. 结构体赋值是直接赋值,类(对象)赋值是指针赋值(对象的地址)。

面试题

一些声明

  • @property
    @property 实际上是 getter 和 setter,@synthesize 是合成这2个方法。

  • @class作用
    在头文件中,一般只需要知道被引用的类的名称就可以了,不需要知道其内部的实体变量和方法,所以在头文件中一般使用@class来声明这个名称是类的名称。而在实现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用#import来包含这个被引用类的头文件。
    @class 的作用是告诉编译器,有这么一个类。
    @class 还可以解决循环依赖的问题,例如 A.h 导入了 B.h,而 B.h 导入了 A.h,每一个头文件的编译都要让对象先编译成功才行,使用@class就可以避免这种情况的发生。

  • @interface
    创建某个特定类的对象之前,objective-c编译器需要一些有关该类的信息。他必须知道该对象的数据成员和它提供的特性。可以使用@interface指令把这种信息传递给编译器。

  • @implementation
    编译器指令,表明将为某个类提供代码。类名出现 @implementation 之后。

static

  • static 用途
    1. 限制变量的作用域
    2. 设置变量的存储域
  • static 修饰变量或者函数
    1. 函数体内 static 变量:作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
    2. 模块内的 static 变量:全局变量,可以被模块内所用函数访问,但不能被模块外其它函数访问,这个函数的使用范围被限制在声明它的模块内;
    3. 类中的 static 变量:成员变量,属于整个类所拥有,只会初始化一次,并且在程序退出时才会回收内存。可以通过 extern 定义外部变量,作为类的拓展供其他外部类访问,也可作为传值使用。
    4. 类中的 static 成员函数:属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的 static 成员变量。

const

const 只读,修饰符。防止代码被无意修改。
定义 const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了。
对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者同时指定为 const;
在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成员变量;

  • 修饰字符串比较

    // " *coder "不能被修改, "coder"能被修改
    const NSString *coder = @"Hello world!";
    // " *coder "不能被修改, "coder"能被修改
    NSString const *coder = @"Hello world!";
    // "coder"不能被修改," *coder "能被修改
    NSString * const coder = @"Hello world!";
    
  • 面试题

    const int a;
    int const a;
    const int *a;
    int const *a;
    int * const a;
    int const * const a;
    
    1. 前两个的作用是一样:a 是一个常整型数
    2. 第三、四个意味着 a 是一个指向常整型数的指针(整型数是不可修改的,但指针可以)
    3. 第五个的意思:a 是一个指向整型数的常指针(指针指向的整型数是可以修改的,但指针是不可修改的)
    4. 最后一个意味着:a 是一个指向常整型数的常指针(指针指向的整型数是不可修改的,同时指针也是不可修改的)

    具体:
    const int *a:const修饰int,而int定义的又是一个整型数,a是一个指向常整型数的指针。所以整型数不能被修改,即*a不能被重新赋值,而指针可以被修改,使其指向另一个地址空间。

    const int *a = 0;// 此时输出*a,结果为0
    int b = 1;
    a = &b; // 此时输出*a,结果为1
    b = 20; // 此时输出*a,结果为20
    *a = 20; // 这是不被允许的,会报错,因为*a被const修饰,不能重新赋值
    

    int *const a:const修饰a,a是一个指向整型数的常指针。所以指针不能被修改,但所指向的地址的值是可以被修改的。

    int b = 1;
    int *const a = &b; // 此时输出*a,结果为1
    b = 20; // 此时输出*a,结果为20。可以使用*a = 20来代替
    a = &b; // 这是不被允许的,会报错,因为指针a被const修饰,指针不能被修改
    

    int const * const a:a是一个指向常整型数的常指针,所以指针不可被修改,指针指向的整型数也是不可以被修改的。

    int b = 1;
    int c = 2;
    float d = 3.0;
    int const * const a = &b;//此时输出*a,结果为1
    b = 20; // 此时输出*a,结果为20
    a = &c; // 这是不被允许的,会报错,因为指针a不能被改变
    *a =  d; // 这也是不被允许的,会报错,因为指针a指向的整型数也是不能被修改的
    int const * const a也可以写成const int* const a,它们的作用是一样的。
    
  • define与const的区别?

    1. 编译器处理方式不同
      define 宏是在预处理阶段展开。
      const 常量是编译运行阶段使用。
    2. 类型和安全检查不同
      define 宏没有类型,不做任何类型检查,仅仅是展开。
      const 常量有具体的类型,在编译阶段会执行类型检查。
    3. 存储方式不同
      define 宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
      const 常量会在内存中分配(可以是堆中也可以是栈中)。
    4. const 可以节省空间,避免不必要的内存分配。
      例如:const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象 #define 一样给出的是立即数,所以 const 定义的常量在程序运行过程中只有一份拷贝,而 #define 定义的常量在内存中有若干个拷贝。
    5. 提高了效率
      编译器通常不为普通 const 常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
    6. const 相比 #define 的优点:
      const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。

volatile关键字

volatile 的变量是说这变量可能会被意想不到地改变,这样编译器就不会去假设这个变量的值了。优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

  • volatile 变量的几个例子

    1. 并行设备的硬件寄存器(如:状态寄存器)
    2. 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
    3. 多线程应用中被几个任务共享的变量
  • 一个参数既可以是 const 还可以是 volatile 吗?解释为什么。
    是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为程序不应该试图去修改它。

  • 一个指针可以是 volatile 吗?解释为什么。
    是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个 buffer 的指针时。

atomic 锁是怎么加的

@synthesize name = _name;
- (void)setName:(NSString *)name {
    @synchronized(self) {
        _name = [name copy];
    }
}
- (NSString *)name {
    @synchronized(self) {
        return _name;
    }
}

推送机制

  • iOS消息推送的工作机制
    iOS消息推送的工作机制可以简单的用下图来概括:
    Provider是指某个iPhone软件的Push服务器;
    APNS是Apple Push Notification Service的缩写,是苹果的服务器。
    第一阶段:应用程序把要发送的消息、目的iPhone的标识打包,发给APNS。
    第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发送到iPhone。
    第三阶段:iPhone把发来的消息传递给相应的应用程序,并且按照设定弹出Push通知。

  • 整个推送流程

    从上图我们可以看到:

    1. 应用程序注册消息推送。
    2. iOS从APNS Server获取device token,应用程序接收device token。
    3. 应用程序将device token发送给PUSH服务端程序。
    4. 服务端程序向APNS服务发送消息。
    5. APNS服务将消息发送给iPhone应用程序。

Objective C中的selector 是什么?

你可以理解 @selector()就是取类方法的编号,他的行为基本可以等同C语言的中函数指针,只不过C语言中,可以把函数名直接赋给一个函数指针,而 Objective-C的类不能直接应用函数指针,这样只能做一个@selector语法来取.它的结果是一个SEL类型。这个类型本质是类方法的编号 (函数地址)。 方法和选择器有何不同? 答案:selector是一个方法的名字,method是一个组合体,包含了名字和实现。通过一个selector可以找到方法地址,进而调用一个方法

其他

  • [[NSMutableArray alloc]init][NSMutableArray array]

    • [[NSMutableArray alloc]init] alloc 分配内存,init 初始化,需要手动释放;
    • [NSMutableArray array] 不需要手动 release,遵循 autoreleasepool 机制
    • 在ARC(自动引用计数)中两种方式并没什么区别
  • newalloc/init 两种方式创建对象现在基本上一样,区别就是使用 new 只能默认 init 进行初始化,alloc 方式可以使用其它的 init 开头的方法进行初始化。

  • 有些图片加载的比较慢怎么处理?你是怎么优化程序的性能的?

    1. 图片的下载放在异步的线程
    2. 图片的下载过程使用占位图片
    3. 如果图片较大,可以考虑多线程断点下载
  • Foundation对象与Core Foundation对象有什么区别

    • Foundation对象是OC的,Core Foundation对象是C对象
    • 数据类型之间的转换
      ARC: __bridge_retained__bridge_transfer
      非ARC: __bridge
  • 写 个“标准”宏,这个宏输出两个参数并返回较大的 #define MIN(X,Y) ((X)>(Y)?(Y):(X))

  • iPhone OS中有没有垃圾回收? 没有

  • self.name=“object” 和 name=“object”有什么区别?
    前者实际上是调用了set 方法给变量赋值, 后者是直接给变量赋值

  • Objective-c中有私有方法吗?私有变量呢?
    没有私有法,但可以将方法直接实现在.m文件中不在.h 件中声明时,外部也不能访问。
    有私有变量

  • Objective-c中有多重继承么?不是的话有声明替代?
    没有多继承,可以通过协议模拟多继承

  • 多态性
    答案:不同对象以自己的方式响应相同的消息的能力叫做多态。意思就是假设生物类(life)都用有一个相同的方法-eat;那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。
    多态。 主要是将数据类型的确定由编译时,推迟到了运行时。比如运行的时候修改某个方法的实现,或者枚举某个对象都有哪些属性等等。也就是不同的对象以自己的方式响应了相同的消息(响应了eat这个选择器)。因此也可以说,运行时机制是多态的基础。
    多态,子类指针可以赋值给父类。关于多态,继承和封装基本最好都有个自我意识的理解。
    站在编程的角度来说,就是以C语言的视角看待OC中的对象、类、方法等等 也就是可以理为类、对象、方法被映射成了一些结构体、函数、指针等等 比如你要枚举某个对象有哪些属性,就可以调运行时里的一些函数。 JSonModel、YYModel就是基于运行时实现的,其通过枚举model里有哪些属性、及属性的类型,去对应的json(数组和字典)来找相应的字段,进而完成解析

  • obj-c的优缺点

    • 优点
      1. Cateogies
      2. Posing
      3. 运行时特性
      4. 指标计算
      5. 弹性讯息传递
      6. 不是一个过度复杂的 C 衍生语言
      7. Objective-C 与 C++ 可混合编程
    • 缺点:
      1. 不支援命名空間,对于命名冲突,可以使用长命名法或特殊前缀解决,如果是引入的第三方库之间的命名冲突,可以使用link命令及flag解决冲突
      2. 不支持运算符重载
      3. 不支持多重继承
      4. 使用动态运行时类型,所有的方法都是函数调用,所以很多编译时优化方法都用不到。(如内联函数等),性能低劣。
  • 静态语言:你写的源代码编译之后完全变成了机器码 效率高
    动态语言:你写的源代码没有完全编译成机器码

  • 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
    全局变量储存在静态数据库,局部变量在堆栈

  • id 声明的变量有什么特性?

    1. 没有 * 号
    2. 动态数据类型
    3. id声明的变量能指向任何OC对象(设置是nil),而不关心其具体类型
    4. 在运行时检查其具体类型
    5. 可以对其发送任何(存在的)消息
  • 苹果的安全机制:

    1. 没经过用户同意,你不能随便获取用户信息。
    2. 所有的程序都在沙盒里运行,B程序不能进入A程序的运行范围。
    3. 如果跟钱有关,比如说支付宝,这些底层的实现都是保密的,只提供接口供开发者调用,这样的话安全性得到保障。
    4. 如果要防止代码被反编译,可以将自己的代码中的.m文件封装成静态库(.a文件)或者是framework文件,只提供给其它人.h文件。这样就保证了个人代码的安全性。
    5. 网络登录的话跟用户名跟密码相关要发送POST请求,如果是GET请求的话密码会直接在URL中显示。然后同时要对帐号密码采用加密技术,加一句:我们公司用的是MD5,但是现在MD5有一个专门的网站来破解,为了防止这个,可以采用加盐技术。
  • 写一个NSString类的实现

    + (id)stringWithCString: (c*****t char*)nullTerminatedCString  encoding: (NSStringEncoding)encoding { 
      NSString  *obj; 
      obj = [self allocWithZone: NSDefaultMallocZone()]; 
      obj = [obj initWithCString: nullTerminatedCString encoding: encoding]; 
      return AUTORELEASE(obj); 
    } 
    
  • 什么是平衡二叉树?
    左右子树都是平衡二叉树且左右子树的深度差值的绝对值不大于1

  • 局部变量能否和全局变量重名?
    能,局部会屏蔽全局。要用全局变量,需要使用"::"
    局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内

  • 如何引用一个已经定义过的全局变量?
    答:可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变量,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。

  • NSString 和 NSMutableString 有什么区别:
    NSString 相当于一个 const char* 不可以改变。
    NSMutableString 相当于 char* 可以改变内部的内容。

  • 图片操作:
    imageNamed: 优点是当加载时会缓存图片。图片使用频繁的使用比较好,一般用于加载小图片。
    iamgeWithContentsFile: 大图片。每次调用,会占缓存。
    imageWithContentsOfFile:仅加载图片,图像数据不会缓存。因此对于较大的图片以及使用情况较少时,那就可以用该方法,降低内存消耗。

  • 类实例(成员)变量的 @protected , @private , @public 声明各有什么含义?
    @protected :受保护的,该实例变量只能在该类和其子类内访问,其他类内不能访问。
    @private :私有的,该实例变量只能在该类内访问,其他类内不能访问。
    @public :共有的,该实例变量谁都可以访问。

  • @dynamic 和 @synthesize
    @dynamic: 的意思是告诉编译器,属性的获取与赋值方法由用户自己实现, 不自动生成。对于只读属性需要提供 setter,对于读写属性需要提供 setter 和 getter。
    @synthesize: 意思是,除非开发人员已经做了,否则由编译器生成 getter 和 setter 属性声明。

  • UIscrollVew 到了什么设计模式?还能再foundation库中找到类似的吗?
    组合模式(composition):所有的container view都用了这个模式
    观察者模式(observer):所有的UIResponder都用了这个模式。
    模板模式(Template):所有datasource和delegate接口都是模板模式的典型应用

  • _btn.frame.origin.y = 10 错误
    原因:OC语法规定不允许直接修改某个对象的结构体属性的成员。_btn 是个对象,frame是个结构体。
    对象和结构体是不一样的,结构体是C语言中的,里面可以定义许多属性,但是不能定义方法,而对象是即可以定义属性又可以定义方法的,是典型的面向对象语法。
    如何改变对象中结构体属性的成员:

    // 先取出结构体
    CGRect frame = _btn.frame;
    // 修改结构体
    frame.origin.y -= 10;
    // 将修改后的结构体重新赋值回去
    _btn.frame = frame;
    

    或者

    // 先取出y值
    CGFloat y = _btn.frame.origin.y;
    // 修改y值
    y -= 10;
    // 重新设置_btn的y值,其他属性和_btn保持不变
    _btn.frame = CGRectMake(_btn.frame.origin.x, y, _btn.frame.size.width, _btn.frame.size.height);
    
  • 如果想让同一个控件同时即改变位置的移动,又放大。这样设置是无效果的。这样操作是创建新的transform然后赋值,给按钮的transform,第二次赋值的会把之前赋值的给覆盖,所以会达不到想要的效果。

    _btn.transform = CGAffineTransformMakeTranslation(0, 100);
    _btn.transform = CGAffineTransformMakeScale(1.2, 1.2);
    

    解决方法:

    _btn.transform = CGAffineTransformMakeTranslation(0, 100);
    // 在之前的transform情况下,继续添加缩放的形变。
    _btn.transform = CGAffineTransformScale(_btn.transform, 1.2, 1.2);
    
  • 四舍五入问题

    float i = 1.7;
    // 会自动四舍五入,不保留小数
    NSLog(@"%0.f",i); // 打印结果2
    // 强转类型不会四舍五入
    int j = (int)i;
    NSLog(@"%d",j); // 打印结果1
    
  • 常见的object-c的数据类型有那些,和C的基本数据类型有什么区别?如:NSInteger和int
    答:object-c的数据类型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,这些都是class,创建后便是对象,而C语言的基本数据类型int,只是一定字节的内存空间,用于存放数值;NSInteger是基本数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。NSInteger是基本数据类型Int或者Long的别名(NSInteger的定义typedef long NSInteger),它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是Long。