object-c 经典实用面试题+自己总结的答案(1)

11 篇文章 0 订阅
3 篇文章 0 订阅

1. 如看看待 OC 是一门动态语言。

(转载)OC做为一门面向对象语言,自然具有面向对象的语言特性,如封装、继承、多态。他具有静态语言的特性(如C++),又有动态语言的效率(动态绑定、动态加载等)。整体来说,确实是一门不错的编程语言。
现在,让我来想想OC的动态语言特性。OC的动态特性表现为了三个方面:动态类型、动态绑定、动态加载。之所以叫做动态,是因为必须到运行时(run time)才会做一些事情。

(1)动态类型
动态类型,说简单点就是id类型。动态类型是跟静态类型相对的。像内置的明确的基本类型都属于静态类型(int、NSString等)。静态类型在编译的时候就能被识别出来。所以,若程序发生了类型不对应,编译器就会发出警告。而动态类型就编译器编译的时候是不能被识别的,要等到运行时(run time),即程序运行的时候才会根据语境来识别。所以这里面就有两个概念要分清:编译时跟运行时。

(2)动态绑定
动态绑定(dynamic binding)貌似比较难记忆,但事实上很简单,只需记住关键词@selector/SEL即可。先来看看“函数”,对于其他一些静态语言,比如c++,一般在编译的时候就已经将将要调用的函数的函数签名都告诉编译器了。静态的,不能改变。而在OC中,其实是没有函数的概念的,我们叫“消息机制”,所谓的函数调用就是给对象发送一条消息。这时,动态绑定的特性就来了。OC可以先跳过编译,到运行的时候才动态地添加函数调用,在运行时才决定要调用什么方法,需要传什么参数进去。这就是动态绑定,要实现他就必须用SEL变量绑定一个方法。最终形成的这个SEL变量就代表一个方法的引用。这里要注意一点:SEL并不是C里面的函数指针,虽然很像,但真心不是函数指针。SEL变量只是一个整数,他是该方法的ID。以前的函数调用,是根据函数名,也就是字符串去查找函数体。但现在,我们是根据一个ID整数来查找方法,整数的查找字自然要比字符串的查找快得多!所以,动态绑定的特定不仅方便,而且效率更高。

(3)动态加载

就是根据需求动态地加载资源.

(补充)
Objective-C具有相当多的动态特性,表现为三方面:动态类型(Dynamic typing)、动态绑定(Dynamic binding)和动态加载(Dynamic loading)。动态——必须到运行时(run time)才会做的一些事情。
动态类型:即运行时再决定对象的类型,这种动态特性在日常的应用中非常常见,简单来说就是id类型。事实上,由于静态类型的固定性和可预知性,从而使用的更加广泛。静态类型是强类型,而动态类型属于弱类型,运行时决定接受者。
动态绑定:基于动态类型,在某个实例对象被确定后,其类型便被确定了,该对象对应的属性和响应消息也被完全确定。
动态加载:根据需求加载所需要的资源,最基本就是不同机型的适配,例如,在Retina设备上加载@2x的图片,而在老一些的普通苹设备上加载原图,让程序在运行时添加代码模块以及其他资源,用户可根据需要加载一些可执行代码和资源,而不是在启动时就加载所有组件,可执行代码可以含有和程序运行时整合的新类。

2. OC 中多态的体现

学过面向对象语言的小伙伴都知道,面向对象的语言(Java,C#,OC)都有三大特性:封装、继承、多态。
所谓封装,在OC中就是把变量和方法封装到一个类中;继承就是如果一个子类继承一个父类,那么子类就可以直接用父类的变量和方法,大大减少了代码的书写量并且提高了代码的可维护性(只要在父类中修改其中的变量和方法,子类继承过来的变量和方法也会随着改变,必须要一一修改);多态,从字面上理解就是多种形态,但仅仅知道这么多,怎么好意思说自己是学过面向对象的呢!

下面本人就板门弄斧的谈谈自己对多态的理解:

首先从多态的定义来说:

用一个父类的指针指向子类的对象,在函数(方法)调用的时候可以调用到正确版本的函数(方法)。
使用多态的条件:

1.子类必须重写父类的方法

2.父类指针指向子类对象

多态的应用场景:

用一个父类的指针指向子类的对象

#import <Foundation/Foundation.h>
//导入头文件
#import "Osier.h"
#import "PineTree.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //父类的指针指向子类的对象
        Tree *t1 = [[Osier alloc] init];
        [t1 grow];
        Tree *t2 = [[PineTree alloc] init];
        [t2 grow];
    }
    return 0;
}

输出结果为:
 柳树在春天发芽
 松树四季常青

父类(Tree)的文件

//.h声明文件
#import <Foundation/Foundation.h>

@interface Tree : NSObject

@property NSString *name;
@property int age;

-(void)grow;
@end

//.m实现文件
#import "Tree.h"

@implementation Tree

-(void)grow
{
    NSLog(@"树木生长");
}
@end

子类(Osier)文件

//.h声明文件
#import "Tree.h"

//柳树继承树这个类
@interface Osier : Tree

//重写父类grow方法
-(void)grow;
@end

//.m实现文件
#import "Osier.h"

@implementation Osier

-(void)grow
{
    NSLog(@"柳树在春天发芽");
}
@end

子类(PineTree)文件

//.h声明文件
#import "Tree.h"

//松树继承树类
@interface PineTree : Tree

//重写父类的grow方法
-(void)grow;
@end

//.m实现文件
#import "PineTree.h"

@implementation PineTree

-(void)grow
{
    NSLog(@"松树四季常青");
}
@end

用一个父类的指针当函数参数,用这个指针可以接受任何它的子类对象包括它自己。

其他文件不变,只在主函数中改变,代码如下

//其他文件不变,只在主函数中改变
#import <Foundation/Foundation.h>

#import "Osier.h"
#import "PineTree.h"

//定义一个函数,用父类指针作形参
void grow(Tree *t)
{
    [t grow];
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Tree *t0 = [[Tree alloc] init];
        grow(t0);

        Tree *t1 = [[Osier alloc] init];
        grow(t1);

        Tree *t2 = [[PineTree alloc] init];
        grow(t2);
    }
    return 0;
}
//输出结果为:
    树木生长
    柳树在春天发芽
    松树四季常青

总结:
多态的优点是提高程序的可扩展性

3. 一个 OC 对象的内存布局。

一个 OC 对象的属性(包括父类)都保存在对象本身的存储空间内;本类的实例方法保存在类对象中,本类的类方法保存在元类对象中;父类的实例方法保存在各级 super class 中,父类的类方法保存在各级 super meta class 中。

4. isa 指向哪。

isa 指的是个什么,对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。isa帮助一个对象找到它的方法。
isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。

5. self 和 super 分别是什么.区别是什么.

self调用自己方法,super调用父类方法
self是类,super是预编译指令
【self class】和【super class】输出是一样的

self和super底层实现原理:
当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找,然后调用父类的这个方法。
当使用 self 调用时,会使用 objc_msgSend 函数: id objc_msgSend(id theReceiver, SEL theSelector, …)。第 一个参数是消息接收者,第二个参数是调用的具体类方法的 selector,后面是 selector 方法的可变参数。以 [self setName:] 为例,编译器会替换成调用 objc_msgSend 的函数调用,其中 theReceiver 是 self,theSelector 是 @selector(setName:),这个 selector 是从当前 self 的 class 的方法列表开始找的 setName,当找到后把对应的 selector 传递过去。
当使用 super 调用时,会使用 objc_msgSendSuper 函数:id objc_msgSendSuper(struct objc_super *super, SEL op, …)第一个参数是个objc_super的结构体,第二个参数还是类似上面的类方法的selector,

struct objc_super {
      id receiver;
      Class superClass;
};

当编译器遇到 [super setName:] 时,开始做这几个事:
1)构 建 objc_super 的结构体,此时这个结构体的第一个成员变量 receiver 就是 子类,和 self 相同。而第二个成员变量 superClass 就是指父类
调用 objc_msgSendSuper 的方法,将这个结构体和 setName 的 sel 传递过去。
2)函数里面在做的事情类似这样:从 objc_super 结构体指向的 superClass 的方法列表开始找 setName 的 selector,找到后再以 objc_super->receiver 去调用这个 selector

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Army_Ma

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值