京东面试题

1、请写出以下代码输出

1
2
3
4
5
 
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d, %d", *(a + 1), *(ptr + 1));
 

参考答案: 2, 随机值

这种类型题好像挺常见的。考的就是C语言上的指针的理解和数组的理解。

分析:

a代表有5个元素的数组的首地址,a[5]的元素分别是1,2,3,4,5。接下来,a + 1表示数据首地址加1,那么就是a[1],也就是对应于值为2.但是,这里是&a + 1,因为a代表的是整个数组,它的空间大小为5 * sizeof(int),因此&a + 1就是a+5。a是个常量指针,指向当前数组的首地址,指针+1就是移动sizeof(int)个字节。

因此,ptr是指向int *类型的指针,而ptr指向的就是a + 5,那么ptr + 1也相当于a + 6,所以最后的*(ptr + 1)就是一个随机值了。而*(ptr – 1)就相当于a + 4,对应的值就是5。

2、写一个标准宏Max,并给出以下代码的输出

1
2
3
4
5
6
 
int array[5] = {1, 2, 3, 4, 5};
int *p = &array[0];
int max = Max(*p++, 1);
printf("%d %d", max, *p);
 

参考答案: 1,2

1
2
3
 
#define Max(X, Y) ((X) > (Y) ? (X) : (Y))
 

当看到宏时,就会想到宏定义所带来的副作用。对于++、–,在宏当中使用是最容易产生副作用的,因此要慎用。

分析:

p指针指向了数组array的首地址,也就是第一个元素对应的地址,其值为1.
宏定义时一定要注意每个地方要加上圆括号
*p++相当于*p, p++,所以Max(*p++, 1)相当于:

1
2
3
4
5
6
7
8
9
10
11
 
(*p++) > (1) ? (*p++) : (1)
 
=>
 
(1) > (1) ? (*p++) : (1)
 
=>
 
第一个*p++的结果是,p所指向的值变成了2,但是1 > 1为値,所以最终max的值就是1。而后面的(*p++)也就不会执行,因此p所指向的地址对应的值就是2,而不是3.
 

扩展:如果上面的*p++改成*(++p)如何?

分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
(*++p) > (1) ? (*++p) : (1)
 
=>
 
(2) > (1) ? (*++p) : (1)
 
=>
 
max = *++p;
 
=>
 
*p = 3,max = 3;
 

3、在一个对象的方法里:self.name=@object;和name=@object有什么不同

参考答案:

这是老生常谈的话题了,实质上就是问setter方法赋值与成员变量赋值有什么不同。通过点语法self.name实质上就是[self setName:@object];。而name这里是成员变量,直接赋值。

一般来说,在对象的方法里成员变量和方法都是可以访问的,我们通常会重写Setter方法来执行某些额外的工作。比如说,外部传一个模型过来,那么我会直接重写Setter方法,当模型传过来时,也就是意味着数据发生了变化,那么视图也需要更新显示,则在赋值新模型的同时也去刷新UI。这样也不用再额外提供其他方法了。

4、怎样使用performSelector传入3个以上参数,其中一个为结构体

参考答案:

1
2
3
4
5
 
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
 

因为系统提供的performSelector的api中,并没有提供三个参数。因此,我们只能传数组或者字典,但是数组或者字典只有存入对象类型,而结构体并不是对象类型,那么怎么办呢?

没有办法,我们只能通过对象放入结构作为属性来传过去了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
typedef struct HYBStruct {
  int a;
  int b;
} *my_struct;
 
@interface HYBObject : NSObject
 
@property (nonatomic, assign) my_struct arg3;
@property (nonatomic, copy)  NSString *arg1;
@property (nonatomic, copy) NSString *arg2;
 
@end
 
@implementation HYBObject
 
// 在堆上分配的内存,我们要手动释放掉
- (void)dealloc {
  free(self.arg3);
}
 
@end

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
my_struct str = (my_struct)(malloc(sizeof(my_struct)));
str->a = 1;
str->b = 2;
HYBObject *obj = [[HYBObject alloc] init];
obj.arg1 = @"arg1";
obj.arg2 = @"arg2";
obj.arg3 = str;
 
[self performSelector:@selector(call:) withObject:obj];
  
// 在回调时得到正确的数据的
- (void)call:(HYBObject *)obj {
    NSLog(@"%d %d", obj.arg3->a, obj.arg3->b);
}
 

5、UITableViewCell上有个UILabel,显示NSTimer实现的秒表时间,手指滚动cell过程中,label是否刷新,为什么?

参考答案:

这是否刷新取决于timer加入到Run Loop中的Mode是什么。Mode主要是用来指定事件在运行循环中的优先级的,分为:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
  • UITrackingRunLoopMode:ScrollView滑动时会切换到该Mode
  • UIInitializationRunLoopMode:run loop启动时,会切换到该mode
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合

苹果公开提供的Mode有两个:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
  • NSRunLoopCommonModes(kCFRunLoopCommonModes)

如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。当我们滚动的时候,也希望不调度,那就应该使用默认模式。但是,如果希望在滚动时,定时器也要回调,那就应该使用common mode。

对于这道题,如果要cell滚动过程中定时器正常回调,UI正常刷新,那么要将timer放入到CommonModes下,因为是NSDefaultRunLoopMode,只有在空闲状态下才会回调。

6、有a、b、c、d 4个异步请求,如何判断a、b、c、d都完成执行?如果需要a、b、c、d顺序执行,该如何实现?

参考答案:

  1. 对于这四个异步请求,要判断都执行完成最简单的方式就是通过GCD的group来实现:
1
2
3
4
5
6
7
8
9
10
11
12
 
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*任务a */ });
dispatch_group_async(group, queue, ^{ /*任务b */ });
dispatch_group_async(group, queue, ^{ /*任务c */ });
dispatch_group_async(group, queue, ^{ /*任务d */ });
 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 在a、b、c、d异步执行完成后,会回调这里
});
 

当然,我们还可以使用非常老套的方法来处理,通过四个变量来标识a、b、c、d四个任务是否完成,然后在runloop中让其等待,当完成时才退出run loop。但是这样做会让后面的代码得不到执行,直到Run loop执行完毕。

  1. 要求顺序执行,那么可以将任务放到串行队列中,自然就是按顺序来异步执行了。

7、使用block有什么好处?使用NSTimer写出一个使用block显示(在UILabel上)秒表的代码。

参考答案:

说到block的好处,最直接的就是代码紧凑,传值、回调都很方便,省去了写代理的很多代码。

对于这里根本没有必要使用block来刷新UILabel显示,因为都是直接赋值。当然,笔者觉得这是在考验应聘者如何将NSTimer写成一个通用用的Block版本。

 

使用起来像这样:

1
2
3
4
5
6
7
8
 
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                    repeats:YES
                                   callback:^() {
  weakSelf.secondsLabel.text = ...
}
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
 

8、一个view已经初始化完毕,view上面添加了n个button(可能使用循环创建),除用view的tag之外,还可以采用什么办法来找到自己想要的button来修改Button的值

参考答案:

这个问题有很多种方式,而且不同的使用场景也不一样的。比如说:

  • 第一种:如果是点击某个按钮后,才会刷新它的值,其它不用修改,那么不用引用任何按钮,直接在回调时,就已经将接收响应的按钮给传过来了,直接通过它修改即可。
  • 第二种:点击某个按钮后,所有与之同类型的按钮都要修改值,那么可以通过在创建按钮时将按钮存入到数组中,在需要的时候遍历查找。

9、tableview在滑动时,有时候会大量加载本地图片,这时候会很卡,如何解决加载耗时过长导致不流畅的问题

参考答案:

这是优化tableview的相关专题,如果只是处理图片加载问题,那可以通过异步读取图片然后刷新UI。当然,我们也可以在取数据时,在模型中提前准备好需要显示的图片资源,这样在cell只就不需要操作图片读取,而是直接显示。

 
  • 10、给定一个如下的字符串(1,(2,3),(4,(5,6)7))括号内的元素可以是数字,也可以是括号,请实现一个算法清除嵌套的括号,比如把上面的表达式的变成:(1,2,3,4,5,6,7),表达式有误时请报错。

参考答案:
最终结果中只有左右括号和逗号,而且左右括号在固定位置,这个比较好处理 , 在result的开始和结束位置加上()即可 。
现在就可以把原字符串的符号看成数字的分隔符,都统一的改成逗号,追加进result中 , 在追加前 在处理下result,防止出现重复的符号,比如(1,(2,3,)4) ->  3,,4
至于错误 , 我们只需要加两个变量分别来记录左圆括号和右圆括号的个数,如果左右括号数量不一致 就认为出错了。代码如下:

#import <Foundation/Foundation.h>

void remove_inline_brackets(char *str) {
    
    int i = 0;
    // 记录左括号,右括号的数量
    int left = 0;
    int right = 0;
    // 最后的结果数组
    char result[100] = "(";
    int j = 1; // 记录result的下表
    while (str[i] != '\0') {
        
        // 把左括号,右括号都变成逗号分隔,然后在去除重复的逗号
        if (str[i] == '(') {
            left ++;
            str[i] = ',';
        }
        if (str[i] == ')') {
            right ++;
            str[i] = ',';
        }
        // 是逗号,看看是否需要去除重复
        if (str[i] == ',') {
            // 如果result的前一个元素是标点了,那就是符号重复了,不追加
            if (result[j-1] == ',' || result[j-1] == '('  || result[j-1] == ')' ) {
                i++;
                continue;
            }
            
        }
        // 到这里说明是数字了,追加到result中
        result[j] = str[i];
        j++;
        i++;
    }
    result[j] = ')';
    result[j+1] = '\0';
    
    // 左括号和右括号数量不一致
    if (left != right) {
        printf("error,左括号和右括号数量不一致\n");
        return;
    }
    printf("结果:%s \n",result);
    
    
}

int main(){
    
//    char str[] = "(1,(2,3),(4,(5,6)7))";
//    char str[] = "((1,(2,3),(4,(5,6)7))";
//    char str[] = "(1,(2,3)),(4,(5,6)7))";
//    char str[] = "(1,(21,33)3,(4,(5,6)7))";
    char str[] = "(1,(2,3),(43,(15,63)17))";
    
    remove_inline_brackets(str);

}
 

 

1、如何声明私有变量和私有方法?      

  • 声明私有变量可以通过@private关键字来声明。例如,这样就是私有的成员变量了:

1
2
3
4
5
6
 
@interface HYBTestModel : NSObject {
  @private NSString *_userName;
}
@end
 
  • 没有关键字声明为私有方法,因为ObjC中也没有真正意义上的私有方法。我们要让方法成员私有,只能通过放在.m文件中定义而不暴露在外部。但是,如果有人知道内部此这么一个方法,那么也是可以访问的。

先说明:ObjC中没有绝对的私有变量和私有方法。

如何修改私有成员变量的值?

1
2
3
4
5
6
7
 
HYBTestModel *model = [[HYBTestModel alloc] init];
 
// 通过KVC可以轻松修改私有成员变量
// 自己加一个打印就可以看到有值了!
[model setValue:@"修改私有变量的值" forKey:@"_userName"];
 

那又如何访问私有成员变量?

1
2
3
4
 
Ivar userNameIvar = class_getInstanceVariable([model class], "_userName");
NSString *userName = object_getIvar(model, userNameIvar);
 

我们可以通过runtime来获取对象的成员变量Ivar,然后再通过object_getIvar来获取某个对象的成员变量的值。

看到这里,还相信ObjC中所谓私有变量吗?

2、assign、retain、copy分别起什么作用?重写下面的属性的getter/setter方法

1
2
3
 
@property (nonatomic, retain) NSNumber *num;
 

参考答案:

从题目可知这问的是MRC下的问题。在MRC下:

  • assign用于非对象类型,对于对象类型的只用于弱引用。
  • retain用于对象类型,强引用对象
  • copy用于对象类型,强引用对象。

重写setter/getter(如何重写getter和setter,是不会自动登录_num成员变量的,需要自己手动声明):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
- (NSNumber *)num {
   return _num;
}
 
- (void)setNum:(NSNumber *)aNum {
   if (_num != aNum) {
     [_num release];
     _num = nil;
    
     _num = [aNum retain];
   }
}
 

3、如何声明一个delegate属性,为什么?

参考答案:

声明属性时要,在ARC下使用weak,在MRC下使用assign。比如:

1
2
3
 
@property (nonatomic, weak) id<HYBTestDelegate> delegate;
 

在MRC下,使用assign是因为没有weak关键字,只能使用assign来防止循环引用。在ARC下,使用weak来防止循环引用。

4、autorelease的对象何时被释放

参考答案:

如果了解一点点Run Loop的知道,应该了解到:Run Loop在每个事件循环结束后会去自动释放池将所有自动释放对象的引用计数减一,若引用计数变成了0,则会将对象真正销毁掉,回收内存。

所以,autorelease的对象是在每个事件循环结束后,自动释放池才会对所有自动释放的对象的引用计数减一,若引用计数变成了0,则释放对象,回收内存。因此,若想要早一点释放掉auto release对象,那么我们可以在对象外加一个自动释放池。比如,在循环处理数据时,临时变量要快速释放,就应该采用这种方式:

1
2
3
4
5
6
7
8
9
 
for (int i = 0; i < 10000000; ++i) {
   @autoreleasepool {
      HYBTestModel *tempModel = [[HYBTestModel alloc] init];
      // 临时处理
      // ...
   } // 出了这里,就会去遍历该自动释放池了
}
 

5、这段代码有问题吗?如何修改?

1
2
3
4
5
6
7
8
9
 
for (int i = 0; i < 10000; ++i) {
  NSString *str = @"Abc";
  str = [str lowercaseString];
  str = [str stringByAppendingString:@"xyz"];
  
  NSLog(@"%@", str);
}
 

参考答案:

这道题从语法上看没有任何问题的,当然,既然面试官出了这一道题,那肯定是有问题的。

问题出在哪里呢?语法没有错啊?内存最后也可以得到释放啊!为什么会有问题呢?是的,问题是挺大的。这对于不了解iOS的自动释放池的原理的人或者说内存管理的人来说,这根本看不出来这有什么问题。

问题就出在内存得不到及时地释放。为什么得不到及时地释放?因为Run Loop是在每个事件循环结束后才会自动释放池去使对象的引用计数减一,对于引用计数为0的对象才会真正被销毁、回收内存。

因此,对于这里的问题,一个for循环执行10000次,会产生10000个临时自动番话对象,一直放到自动释放池中管理,内存得不到回收。

然后,现象是内存暴涨。正确的写法:

1
2
3
4
5
6
7
8
9
10
11
 
for (int i = 0; i < 10000; ++i) {
  @autoreleasepool {
    NSString *str = @"Abc";
    str = [str lowercaseString];
    str = [str stringByAppendingString:@"xyz"];
      
    NSLog(@"%@", str);
  }
}
 

6、UIViewController的viewDidUnload、viewDidLoad和loadView分别什么时候调用?UIView的drawRect和layoutSubviews分别起什么作用?

参考答案:

第一个问题:

  • 在控制器被销毁前会调用viewDidUnload(MRC下才会调用)
  • 在控制器没有任何view时,会调用loadView
  • 在view加载完成时,会调用viewDidLoad

第二个问题:

  • 在调用setNeedsDisplay后,会调用drawRect方法,我们通过在此方法中可以获取到context(设置上下文),就可以实现绘图
  • 在调用setNeedsLayout后,会调用layoutSubviews方法,我们可以通过在此方法去调整UI。当然能引起layoutSubviews调用的方式有很多种的,比如添加子视图、滚动scrollview、修改视图的frame等。

11、UITableView是如何重用cell的?

参考答案:

UITableView提供了一个属性:visibleCells,它是记录当前在屏幕可见的cell,要想重用cell,我们需要明确指定重用标识(identifier)。

当cell滚动出tableview可视范围之外时,就会被放到可重用数组中。当有一个cell滚动出tableview可视范围之外时,同样也会有新的cell要显示到tableview可视区,因此这个新显示出来的cell就会先从可重用数组中通过所指定的identifier来获取,如果能够获取到,则直接使用之,否则创建一个新的cell。

12、如果更高效地显示列表

参考答案:

要更高效地显示列表(不考虑种种优化),可以通过以下方法处理(只是部分):

  • 提前根据数据计算好高度并缓存起来
  • 提前将数据处理、I/O计算异步处理好,并保存结果,在需要时直接拿来使用

14、描述KVC、KVO机制

参考答案:

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

KVO即Key-Value Observing,是建立在KVC之上,它能够观察一个对象的KVC key path值的变化。 当keypath对应的值发生变化时,会回调observeValueForKeyPath:ofObject:change:context:方法,我们可以在这里处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值