iOS章结 - 问答 \32

一、OC 中,对实例变量的修饰默认是 @protected,为什么不使用 @public

1 - 会暴露类的内部细节

2 - 破坏的类的封装

二、请说明设置器/访问器和实例变量的关系

1 - 设置器/访问器,它们内部操作的都是实例变量

2 - 每个实例变量都要有配置一对设置器/访问器

三、数组只能存储对象,那么需要存储整型或者结构体的时候,怎么做

1 - 存储:把整型或者结构体转换为对象,将对象存入数组

2 - 读取:取出对象,将对象还原为整型或者结构体

四、果官方文档推荐用 NSInteger,那么 NSInteger 和 int 有什么区别

1 - 在苹果的 API 实现中,NSInteger 是一个封装,它会识别当前操作系统的位数,自动返回最大的类型,其定义代码类似如下

1 #if __LP64__ || TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
2 typedef long NSInteger;
3 typedef unsigned long NSUInteger;
4 #else
5 typedef int NSInteger;
6 typedef unsigned int NSUInteger;
7 #endif

You usually want to use NSInteger when you don't know what kind of processor architecture your code might run on, so you may for some reason want the largest possible int type, which on 32 bit systems is just an int, while on a 64-bit system it's a long

2 - NSInteger 与 int 的区别就是 NSInteger 会根据系统的位数(32 or 64)自动选择 int 的最大数值(int or long)

五、简述对象和类的关系

1 - 类是对象的类型,对象是类的实例

2 - 类是模板,对象是具体表现(任何对象都要占内存空间)

六、简述在 ARC 内存管理实际应用中,容易出错的问题有哪些

1 - 有些方法,比如 dealloc、retain、release、aurorelease,禁止任何形式的调用和实现,包括使用 @selector(retain/release)等隐含调用

2 - 不能在 C语言的结构体中使用对象指针

3 - 不能使用 NSAutoReleasePool,作为代替使用 @autoReleasePool

4 - 开发者不需要使用 NSAllocateObject/NSDeallocateObject

5 - ARC 在方法和便利变量命名上也有一些新规定,禁止以 new 开头的属性变量命名

七、Category 和 Extension 的区别

1 - Category

① 用于给类及其子类添加新的方法

② 有自己单独的 .h 和 .m 文件

③ 不能添加新属性

2 - Extension(匿名 Category)

① 用于给类添加新方法,但只作用于原始类,不作用于子类

② 只能对有 implementation 源代码的类写 Extension,对于没有 implementation 源代码的类,比如 framework class,是不可以的

③ Extension 可以给原始类添加新方法、新属性(添加的方法需要实现)

八、如何对一个类进行扩展

三种方式:子类(继承)、类目/延展、协议

九、成员变量为什么使用下划线

1 - 和其他局部变量进行区分

2 - 设置器方法中的形参名不能和成员变量名一致

十、请简述 Category 与子类(继承)的区别

1 - 功能:分类只能添加方法;子类既能添加方法还能添加实例变量

2 - 使用:分类添加的方法是原始类的静态方法或实例方法,和原始类中的方法是平级关系;子类添加的方法只有子类以及继承该类的类才能使用

十一、请简述 Autoreleasepool 的工作原理

1 - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init]; 和[pool release],使用就像一对括号, [xxx autorelease] 必须写在两者之间

2 - [xxx autorelease] 一旦出现在自动释放池里,pool 就会把接收 autorelease 的对象给保存起来(以栈的方式把对象压入栈)

3 - 当 [pool release] 的时候,pool 会向之前保存的对象逐一发送 release 消息(对象出栈,越晚 autorelease 的对象,越早接收到 release 消息)

十二、请简述内存管理的常见错误

 1 - 未使用设置器

NSNumber *zero = [[NSNumber alloc] initWithInteger:1];
_count = zero;// count 是属性,直接赋值方式代替了点语法
[_count release]; // 错误

2 - 内存泄露:缺少了:[zero release]

NSNumber *zero = [[NSNumber alloc] initWithInteger:1];

3 - 释放了没有所有权的对象,如遍历构造器

NSNumber *zero = [NSNumber numberWithInteger:1];
[zero release];// 不是自己的事,少管

十三、请简述 id 和 instancetype 两者的区别

1 - ARC 模式

① id :编译时不会做类型检查,运行期才会进行检查。如果对象没有该方法,直接报错

② instancetype:编译时就会确定对象的类型。如果对象没有相应方法,直接报错

2 - MRC 模式

① 两者没有本质区别,如对象不管有没有方法,编译都可以过,但 instacetype 会报警告

② id 可以使用多态运用到方法的参数上;intancetype 只能用于创建对象的返回值上,比如初始化方法和便利构造器

十四、简述类工厂方法的作用

1 - 类方法将对象分配和初始化合在一个步骤中,返回被创建的对象,并进行自动释放的处理

2 - 类工厂可为初始化过程提供对象的分配信息,避免为可能没有用的对象盲目分配内存

3 - 提供单例 

十五、请说出 static 和 const 尽可能多的作用

1 - static

① 函数体内的 static 变量:作用范围为该函数体,它不同于 auto 变量,该变量在内存中只被分配一次,因此它的值在下次调用时仍维持上一次的值

② 在模块内的 static 全局变量:在模块内所有的函数均可访问,但不能被模块外的其他函数访问。在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内

③ 在类中的 static 成员变量:它属于整个类

2 - const

① 使变量不可改变

② 对于指针来讲,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或者两者同时指定为 const

③ 在函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值

④ 对于类的成员函数,若指定为 const 类型,则表明是一个常函数,不能修改类的成员变量

有时候必须指定其返回值为 const 类型,以使得其返回值不为左值

十六、当同时重写 setter/getter 方法时,属性是不会帮你自动生成实例变量且编译会不通过 ?请问怎么解决

1 - 可以自己定义实例变量

2 - 也可以使用 @synthesize 告诉属性内操作的哪个实例变量

十七、简述 OC 的优缺点

1 - 优点

① Categoies

② Posing

③ 动态识别

④ 指标计算

⑤ 弹性讯息传递

⑥ 不是一个过度复杂的 C 衍生语言

⑦ OC 和 C++ 可混编

2 - 缺点

① 不支持命名空间

② 不支持运算符重载    

③ 不支持多重继承

④ 使用动态运行时,所有方法都是函数调用,导致很多编译时的优化方法都用不到

十八、iOS 有没有垃圾回收机制 ?

Objective-C 2.0 也是有垃圾回收机制的。但是只能在 Mac OS X Leopard 10.5 以上的版本使用

十九、简述泛型指针 void * 和 id 的区别

1 - void *:C语言中的泛型指针,指代任意的指针类型(比如 int *、float *、int *……)。当返回值是一个地址或者指针的时候,返回值的类型都可以用 void * 表示,也可以用此类型来定义任意类型的指针变量

2 - id :OC 语言中的泛型指针,指代任意对象类型的指针。当返回值是一个对象指针的时候,返回值的类型都可以用 id 表示,也可以用此类型来定义任意类型的对象指针变量。但是 id 本身就是一个指针在使用 id 的时候不需要加星号,比如 id foo = nil 定义了一个指向 nil 的指针,这个指针指向 NSObject 的一个任意子类;而 id *foo = nil 则定义了一个指针,这个指针指向另一个指针,被指向的这个指针指向 NSObject 的一个子类

3 - 比如 id person 和 void* personV,[person print] 是可以的,但是 [personV print] 在编译的时候就会报错,因为 OC 编译器看到 id,会假定它可以接受任何 message,虽然在 runtime 时可能并不是这样的,但 personV 并不是 OC 类型,编译器就会报错。但值得注意的是,void * 是有可能可以接收 [print message] 

二十、代理使用 weak 还是 assign ?

1 - 建议使用 weak,它指明该对象并不负责保持 delegate 这个对象,就意味着 delegate 这个对象的销毁由外部控制

2 - 可以使用 assign,它也有 weak 的功效。对于使用 assign 修饰 delegate,在对象释放前,需要将 delegate 指针设置为 nil,不然会产生野指针 

二十一、什么情况使用 weak 关键字,它和 assign 有什么不同 ?

1 - 在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如 delegate 代理属性,自定义 IBOutlet 控件属性一般也使用 weak(当然也可以使用 strong,但是建议使用 weak)

2 - weak 和 assign 的不同点

① weak 策略在属性所指的对象遭到摧毁时,系统会将 weak 修饰的属性对象的指针指向 nil,这在 OC 给 nil 发消息是不会有什么问题的

② assign 策略在属性所指的对象遭到摧毁时,属性对象指针还指向原来的对象,由于对象已经被销毁,这时候就产生了野指针,如果这时候在给此对象发送消息,很容造成程序崩溃

③ assigin 可以用于修饰非 OC 对象,而 weak 必须用于 OC 对象

二十二、简述 id  |  nil  |  Nil  |  NULL  |  NSNULL  的区别

1 - id: 它以转换为任何数据类型,id 类型的变量可以存放任何数据类型的对象,声明的对象具有运行时特性

2 - nil 是一个实例对象值;如果我们要把一个对象设置为空的时候,就用 nil

3 - Nil 是一个类对象的值,如果我们要把一个 class 的对象设置为空的时候,就用 Nil

4 - NULL 指向基本数据类型的空指针(C语言的变量的指针为空)

5 - NSNull 是一个对象,它用在不能使用 nil 的场合

二十三、NSObject 和 id 的区别 

1 - NSObject 和 id 都可以指向任何对象

2 - NSObject 对象会在编译时进行检查,需要强制类型转换;id 类型不需要编译时检查,不需要强制类型转换

二十四、进程和线程的区别

1 - 调度:线程是调度和分配的基本单位,进程是作为拥有独立资源的基本单位

2 - 拥有资源:进程是拥有资源的独立单位,线程不用有资源,但是可以访问隶属于进程的资源

3 - 系统开销:进程创建、消亡都要系统为之分配和资源回收,导致进程系统开销远远大于进程的开销

二十五、使用宏定义,简单实现 swap(x,  y)

#define swap(x, y) {x = x +y; y = x - y; x = x-y;}

二十六、简述 KVO、NSNotification、Delegate、Block 的工作原理以及区别

1 - 功能原理

① KVO 是 Cocoa 框架实现的观察者模式(Key-Value Observing),一般同 KVC 搭配使用,通过 KVO 可以监测一个值的变化,是一对多的关系,一个值的变化会通知所有的观察者

② NSNotification 是通知,也是一对多的使用关系。在某些情况下,KVO 和 NSNotification 是一样的,都是状态变化之后告知对方。它的特点就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比 KVO 多了发送通知的一步。但是通知的优点是监听不再局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活

③ Delegate 是代理,是一对一关系。 Delegate 方法更加直接,最典型的特征是 Delegate 方法往往需要关注返回值

④ Block 本质上是 Delegate 的另一种形式,是函数式编程的一种形式。使用场景跟 Delegate 一样,相比 Delegate 更灵活,实现更直观

⑤ KVO、NSNotification、Delegate 这三种方法主要是用来监听事件发生的

2 - 区别

① NSNotification、KVO 两者都是观察者模式。KVO 是被观察者直接发送消息给观察者,是对象间的交互;通知是观察者和被观察者通过通知中心对象之间进行交互,即消息由被观察者发送到通知中心对象,再由中心对象发给观察者,两者之间并不进行直接的交互

② Delegate 只能一对一,一个对象只有一个代理;KVO、NSNotification 可以一对多,一个通知可以发给多个观察者

③ 使用场景

    KVO 一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用观察者模式

  Delegate 一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用 Delegate。Delegate 是强关联,就是委托和代理双方互相知道,你委托别人买股票你就需要知道经纪人,经纪人也不要知道自己的顾客

  Notification 一般是进行全局通知,比如股票好消息一出,通知大家去买入。Notification 是弱关联,好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息

二十七、进程和线程的区别

1 - 概念:进程和线程都是有操作系统所提供的程序运行的基本单元,系统利用该基本单元实现系统对应用程序的并发性

2 - 区别

① 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响

② 线程只是一个进程中的不同执行路径,它拥有自己的堆栈和局部变量,但线程之间没有独立的地址空间,一个线程死掉就意味着整个进程的死掉

③ 多进程的程序要比多线程的程序健壮,但是进程切换时,内耗大,效率差。对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,而不能使用进程

二十八、什么是沙箱模式 ?哪些操作是属于私有 API 范畴 ?

1 - 沙箱模式:某个 iphone 工程进行文件操作有此工程对应的指定位置,不能逾越

2 - iphone 常见私有 API 的应用

① 直接发送短信

② 访问沙箱之外的磁盘文件

二十九、为什么很多内置的类,比如 TableViewController 的 delegate 属性是 assign、weak 而不是 retain ?

1 - 为了防止循环引用(所有的引用计数系统,都存在循环应用的问题)

2 - 例如下面的引用关系:对象 a 创建并引用到了对象 b    且   对象 b 创建并引用到了对象 c   且   对象 c 创建并引用到了对象 b

  这时候 b 和 c 的引用计数分别是 2 和 1。当 a 不再使用 b,调用 release 释放对 b 的所有权,但是因为 c 还引用了 b,所以 b 的引用计数仍为 1,b 不会被释放。b 不释放,c 的引用计数就是 1,那么 c 也不会被释放。b 和 c 永远留在内存中

3 - 必须打断循环引用,通过其它规则来维护引用关系。比如 delegate 往往是采用 assign、weak 修饰,就是为了防止 delegation 两端产生不必要的循环引用

三十、讲一下 tableView 的重用机制

1 - 通过查看 UITableView 头文件,会找到 NSMutableArray *visiableCells、NSMutableDictnery *reusableTableCells 两个结构。visiableCells 内保存当前显示的 cells;reusableTableCells 保存可重用的 cells

2 - TableView 显示之初,reusableTableCells 为空,tableView dequeueReusableCellWithIdentifier:CellIdentifier 返回 nil

      开始时 cell 都是通过 [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] 来创建,且 cellForRowAtIndexPath 只是调用最大显示 cell 数的次数。比如有 100 条数据,iPhone 一屏最多显示 10 个 cell,程序最开始显示 TableView 的情况就是用 [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] 创建 10 次 cell,并给 cell 指定同样的重用标识(当然,可以为不同显示类型的 cell 指定不同的标识)并且 10 个 cell 全部都加入到 visiableCells 数组,reusableTableCells 为空

      向下拖动 tableView,当 cell1 完全移出屏幕,并且 cell11(它也是 alloc 出来的)完全显示出来的时候,cell11 加入到 visiableCells,cell1 则移出 visiableCells且被加入到 reusableTableCells

     接着向下拖动 tableView,因为 reusableTableCells 中已经有值,所以,当需要显示新的 cell,cellForRowAtIndexPath 再次被调用的时,tableView dequeueReusableCellWithIdentifier:CellIdentifier 返回 cell1,cell1 被加入到 visiableCells 且被移出 reusableTableCells。cell2 被移出 visiableCells且被加入到 reusableTableCells,依次循环

三十一、ViewController 的 didReceiveMemoryWarning 是在什么时候调用的?默认的操作是什么?

1 - 当程序接到内存警告时 controller 将会收到这个消息:didReceiveMemoryWarning。从 iOS3.0 开始,不需要重载这个函数,把释放内存的代码放到 viewDidUnload 中去

2 - 这个函数的默认实现是:检查 controller 是否可以安全地释放它的 view (指属性),比如 view 本身没有 superview 且可以被很容易地重建(从 nib 或者调用 loadView)。如果 view 可以被释放,那么这个函数释放 view 并调用 viewDidUnload

      你可以重载这个函数来释放 controller 中使用的其他内存。但要记得调用这个函数的 super 实现来允许父类(UIVIewController)释放 view。如果你的 ViewController 保存着 view 的子 view 的引用,在早期 iOS 版本中,你应该在这个函数中来释放这些引用。而在 iOS3.0 后的版本中,你应该在 viewDidUnload 中释放这些引用

三十二、ViewController 的 loadView、viewDidLoad、viewDidUnload 分别是什么时候调用的,在自定义 ViewCointroller 时在这几个函数中应该做什么工作 ?

1 - init 方法

① 在此方法中实例化必要的对象(遵从 LazyLoad 思想)

2 - loadView 方法

① 当 view 需要被展示而它却是 nil 时,viewController 会调用该方法

② 不要直接调用该方法,如果使用 IB 维护 views,不能重写该方法

3 - loadView 和 IB 构建 view

① 你在控制器中实现了 loadView 方法,那么你可能会在应用运行的某个时候被内存管理控制调用

② 如果设备内存不足的时候, view 控制器会收到 didReceiveMemoryWarning 的消息

③ 默认实现是检查当前控制器的 view 是否在使用。 如果它的 view 不在当前正在使用的 view hierarchy 里面,且你的控制器实现了 loadView 方法,那么这个 view 将被 release,loadView方法将被再次调用来创建一个新的 view

4 - viewDidLoad 方法

① viewDidLoad 此方法只有当 view 从 nib 文件初始化的时候才被调用(可以重写该方法以进一步定制 view;一般习惯在 viewDidLoad 里调用数据)

② 在 iPhone OS 3.0 及之后的版本中,还可以重写 viewDidUnload 来释放对 view 的任何索引

5 - viewDidUnload 方法

① 当系统内存吃紧时就会调用该方法(前提是 viewController 没有被 dealloc)。内存吃紧时,在 iPhone OS 3.0 之前 didReceiveMemoryWarning 是释放无用内存的唯一方式,但是 OS 3.0 及以后 viewDidUnload 方法是更好的方式

    在内存吃紧时在该方法中将所有 IBOutlet(无论是 property 还是实例变量)置为 nil 即可,因为系统 release view 时会将其 release 并释放其他与 view 有关的对象、其他在运行时创建(但非系统必须)的对象、在 viewDidLoad 中被创建的对象、缓存数据等。release 对象后,将对象置为 nil(IBOutlet 只需要将其置为 nil,系统 release view 时会将其 release)

② 一般认为 viewDidUnload 是 viewDidLoad 的镜像,因为当 view 被重新请求时,viewDidLoad 还会重新被执行

③ viewDidUnload 中被 release 的对象必须是很容易被重新创建的对象(比如在 viewDidLoad 或其它方法中创建的对象),不要 release 用户数据或其它很难被重新创建的对象

6 - dealloc 方法:和 viewDidUnload 没有关联,dealloc 还是继续做它该做的事情

7 - 它们的执行顺序如下:loadView/nib 文件来加载 view 到内存 --> viewDidLoad 进一步初始化这些 view --> 内存不足时调用 viewDidUnload 释放 views --> 当需要 view 时回到第一步(如此循环)

 

 

 

 

posted on   低头捡石頭  阅读(41)  评论(0编辑  收藏  举报

编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
< 2025年3月 >
23 24 25 26 27 28 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 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示