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。
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
;
|
object;和name=@
object有什么不同
参考答案:
这是老生常谈的话题了,实质上就是问setter方法赋值与成员变量赋值有什么不同。通过点语法self.name实质上就是[self setName:@object
];。而name这里是成员变量,直接赋值。
一般来说,在对象的方法里成员变量和方法都是可以访问的,我们通常会重写Setter方法来执行某些额外的工作。比如说,外部传一个模型过来,那么我会直接重写Setter方法,当模型传过来时,也就是意味着数据发生了变化,那么视图也需要更新显示,则在赋值新模型的同时也去刷新UI。这样也不用再额外提供其他方法了。
参考答案:
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
)
;
}
|
参考答案:
这是否刷新取决于timer加入到Run Loop中的Mode是什么。Mode主要是用来指定事件在运行循环中的优先级的,分为:
苹果公开提供的Mode有两个:
如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。当我们滚动的时候,也希望不调度,那就应该使用默认模式。但是,如果希望在滚动时,定时器也要回调,那就应该使用common mode。
对于这道题,如果要cell滚动过程中定时器正常回调,UI正常刷新,那么要将timer放入到CommonModes下,因为是NSDefaultRunLoopMode,只有在空闲状态下才会回调。
参考答案:
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执行完毕。
参考答案:
说到block的好处,最直接的就是代码紧凑,传值、回调都很方便,省去了写代理的很多代码。
对于这里根本没有必要使用block来刷新UILabel显示,因为都是直接赋值。当然,笔者觉得这是在考验应聘者如何将NSTimer写成一个通用用的Block版本。
代码放到了这里: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
]
;
|
参考答案:
这个问题有很多种方式,而且不同的使用场景也不一样的。比如说:
参考答案:
这是优化tableview的相关专题,如果只是处理图片加载问题,那可以通过异步读取图片然后刷新UI。当然,我们也可以在取数据时,在模型中提前准备好需要显示的图片资源,这样在cell只就不需要操作图片读取,而是直接显示。
如果想要更深入地优化,学习以下知识点:
参考答案:
如果只是判断整个表达式是否有错误,然后去掉里面的圆括号,那么一个循环就可以了。不过我们只需要加两个变量分别来记录左圆括号和右圆括号的个数。这里假设逗号总是正确的情况下,伪代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
left
=
0
;
rigt
=
0
;
for
i
=
0
;
i
<
str
.
length
;
++
i
{
if
是左括号
{
left
++
;
continue
;
}
if
是右括号
{
right
++
;
// 处理(1,)这样的结构
if
前一个是逗号
{
error
;
}
continue
;
}
[
newStr
append
:str
[
i
]
]
;
}
if
left
!=
right
{
error
;
}
|
感谢标哥的整理
参考答案:
1
2
3
4
5
6
|
@interface
HYBTestModel
: NSObject
{
@private
NSString
*
_userName
;
}
@end
|
先说明: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中所谓私有变量吗?
1
2
3
|
@property
(
nonatomic
,
retain
)
NSNumber
*num
;
|
参考答案:
从题目可知这问的是MRC下的问题。在MRC下:
重写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
]
;
}
}
|
参考答案:
声明属性时要,在ARC下使用weak,在MRC下使用assign。比如:
1
2
3
|
@property
(
nonatomic
,
weak
)
id
<HYBTestDelegate>
delegate
;
|
在MRC下,使用assign是因为没有weak关键字,只能使用assign来防止循环引用。在ARC下,使用weak来防止循环引用。
参考答案:
如果了解一点点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
]
;
// 临时处理
// ...
}
// 出了这里,就会去遍历该自动释放池了
}
|
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
)
;
}
}
|
参考答案:
第一个问题:
第二个问题:
参考答案:
更多内容看这里:iOS NSOperation
参考答案:
不太清楚题目的语义,好像是说扩展类方法?通过category很容易做到,这里就不说了!
参考答案:
随手写一个吧:
1
2
3
4
5
6
7
8
9
10
11
12
|
+
(
instancetype
)
sharedInstance
{
static
id
s_manager
=
nil
;
static
dispatch_once_t
onceToken
;
dispatch_once
(
&
onceToken
,
^
{
s_manager
=
[
[
HYBTestSingleton
alloc
]
init
]
;
}
)
;
return
s_manager
;
}
|
参考答案:
冒泡算法的核心算法思想是每趟两两比较,将小的往上浮,大的往下沉,就像气泡一样从水底往水面浮。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
void
bubbleSort
(
int
a
[
]
,
int
len
)
{
for
(
int
i
=
0
;
i
<
len
-
1
;
++
i
)
{
// 从水底往水面浮,所以从最后一个开始
for
(
int
j
=
len
-
1
;
j
>
i
;
j
--
)
{
// 后者比前者还小,将需要交换
if
(
a
[
j
]
<
a
[
j
-
1
]
)
{
int
temp
=
a
[
j
]
;
a
[
j
]
=
a
[
j
-
1
]
;
a
[
j
-
1
]
=
temp
;
}
}
}
}
|
更详细地可阅读:冒泡排序
参考答案:
UITableView提供了一个属性:visibleCells,它是记录当前在屏幕可见的cell,要想重用cell,我们需要明确指定重用标识(identifier)。
当cell滚动出tableview可视范围之外时,就会被放到可重用数组中。当有一个cell滚动出tableview可视范围之外时,同样也会有新的cell要显示到tableview可视区,因此这个新显示出来的cell就会先从可重用数组中通过所指定的identifier来获取,如果能够获取到,则直接使用之,否则创建一个新的cell。
参考答案:
要更高效地显示列表(不考虑种种优化),可以通过以下方法处理(只是部分):
参考答案:
这个问题三言两语讲不明白。简单来说,M对应于Model(数据层)、V对应于View(视图层)、C对应于Controller(控制器层)。
如下图:
用户在V上操作,需要通过C更新M,然后将新的M交到C,C让M更新。
更详细可以阅读:iOS中的MVC设计模式
参考答案:
KVC即是指NSKeyValueCoding,是一个非正式的Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于KVC实现的关键技术之一。
KVO即Key-Value Observing,是建立在KVC之上,它能够观察一个对象的KVC key path值的变化。 当keypath对应的值发生变化时,会回调observeValueForKeyPath:ofObject:change:context:方法,我们可以在这里处理。
更详细的内容,请自行百度吧,现在笔者没有写相关文章!
参考答案:
笔者只说说我们在开发中真正常用到的设计模式(包括架构设计模式):
更详细可以看这里:23种设计模式目录
真心感谢标哥啊~~