[※]@property中有哪些属性关键字?
1.原子性nonatomic/automic
在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(automicity),如果具备nonatomic特质,则不使用同步锁.
2.读/写权限 readwrite/readonly
3.内存管理语义
assign "设置方法"只会针对"纯量类型" (cgfloat,nsinterger等)的简单赋值操作.
strong "拥有关系"为这种属性设置新值时,设置方法先保留新值,并释放旧址,然后再将新值设置上去. (保新去旧)
weak "非拥有关系"为这种属性赋新值时,设置方法既不保留新值,也不释放旧值.此特性和assign类似,然而属性所指的对象遭到摧毁时,属性也会被清空.
unsafe_unretained 此特质的语义和assign相同,但是他适合于"对象类型",该特质表达一种非拥有关系,当对象遭到摧毁时,属性值不会自动清空,这一点和weak有区别.
copy 此特质所表达的所属关系于strong类似,然而设置方法并不保存新值,而是将其"拷贝".拷贝
4.方法名
getter=<name>
@property(nonatomic,getter = isOn)BOOL on;自定义方法名
setter=<name> 不是特别常用.
[※]weak属性需要在dealloc中置nil么?
不需要,在ARC环境无论是强指针还是弱指针都无需在deallco设置为nil,ARC会自动帮我们处理强指针strong 和弱指针weak
1.有了ARC,我们的代码可以清晰很多,你不再需要考虑什么时候retain或release对象。唯一需要考虑的是对象之间的关联,也就是(哪个对象拥有哪个对象) 对象的拥有关系,根据拥有关系来处理对应的retainCount值.
2.ARC也有一些限制:
1> 首先ARC只能工作于Objective-C对象,如果应用使用了Core Foundation或malloc()/free(),此时还是需要你来手动管理内存
2> 此外ARC还有其它一些更为严格的语言规则,以确保ARC能够正常地工作
3.虽然ARC管理了retain和release,但并不表示你完全不需要关心内存管理的问题。因为strong指针会保持对象的生命,某些情况下你仍然需要手动设置这些指针为nil,否则可能导致应用内存不足。无论何时你创建一个新对象时,都需要考虑谁拥有该对象,以及这个对象需要存活多久
4.ARC还能很好地结合C++使用,这对游戏开发是非常有帮助的。对于iOS 4,ARC有一点点限制(不支持weak指针),但也没太大关系
ARC注意事项
1.不能直接调用dealloc方法,不能调用retain,release,autorelease,retainCount方法,包括@selector(retain)的方式也不行
2.可以用dealloc方法来管理一些资源,但不能用来释放实例变量,也不能在dealloc方法里面去掉[super dealloc]方法,在ARC下父类的dealloc同样由编译器来自动完成
3.Core Foundation类型的对象仍然可以用CFRetain,CFRelease这些方法
4.不能再使用NSAllocateObject和NSDeallocateObject对象
5.不能在C结构体中使用对象指针,如果有类似功能可以创建一个Objective-C类来管理这些对象
6.在id和void*之间没有简便的转换方法,同样在Objective-C和Core Foundation类型之间的转换都需要使用编译器指定的转换函数
7.不能再使用NSAutoreleasePool对象,ARC提供了@autoreleasepool块来代替它,这样更有效率
8.不能使用内存存储区(不能再使用NSZone)
9.不能以new为开头给一个属性命名
10.声明IBOutlet时一般应当使用weak,除了对StoryBoard这样nib中间的顶层对象要用strong
11.weak相当于老版本的assign,strong相当于retain
@property 后面可以有哪些修饰符?
线程安全的:
atomic,nonatomic
访问权限的
readonly,readwrite
内存管理(ARC)
assign,strong,weak,copy
内存管理(MRC)
assign,retain,copy
指定方法名称
setter=
getter=
什么情况使用 weak 关键字,相比 assign有什么不同?比如:
在ARC中,出现循环引用的时候,必须要有一端使用weak,比如:自定义View的代理属性
已经自身已经对它进行一次强应用,没有必要在强引用一次,此时也会使用weak,自定义View的子控件属性一般也使用weak;但b是也可以使用strong
weak当对象销毁的时候,指针会被自动设置为nil,而assign不会* assigin 可以用非OC对象,而weak必须用于OC对象
怎么用 copy 关键字?
对于字符串和block的属性一般使用copy
字符串使用copy是为了外部把字符串内容改了,影响该属性达到赋值的目的
block使用copy是在MRC遗留下来的,在MRC中,方法内部的block是在在栈区的,使用copy可以把它放到堆区.在ACR中对于block使用copy还是strong效果是一样的
这个写法会出什么问题: @property (copy) NSMutableArray *array;
添加,删除,修改数组内的元素的时候,程序会因为找不到对于的方法而崩溃.因为copy就是复制一个不可变NSArray的对象 copy对象出来的是不可变的数组
如何让自己的类用 copy 修饰符?
你是说让我的类也支持copy的功能吗?
如果面试官说是:
遵守NSCopying协议
实现 - (id)copyWithZone:(NSZone *)zone;方法
如果面试官说否,是属性中如何使用copy
在使用字符串和block的时候一般都使用copy
字符串使用copy是为了外部把字符串内容改了,影响该属性达到赋值的目的
block使用copy是在MRC遗留下来的,在MRC中,方法内部的block是在在栈区的,使用copy可以把它放到堆区.在ACR中对于block使用copy还是strong效果是一样的(C函数是在栈区,OC对象是在堆区)
如何重写带 copy 关键字的 setter?
重写copy的setter方法时候,一定要调用一下传入的对象的copy方法,然后在赋值给该setter的方法对应的成员变量
@property 的本质是什么?ivar、getter、setter是如何生成并添加到这个类中的
(生成5个记录属性来记录具体的实现过程分别是对象偏移量OBJC_IVAR_$ ,setter和getter方法,iVar_list成员变量列表,method_list列表,prop_list列表)
在普通的OC对象中,@property就是编译其自动帮我们生成一个私有的成员变量和setter与getter方法的声明和实现
我为了搞清属性是怎么实现的,曾经反编译过相关的代码,他大致生成了五个个东西
OBJC_IVAR_$类名$属性名称该属性的偏移量
setter与getter方法对应的实现函数
ivar_list 就是成员变量列表
method_list 方法列表
prop_list 属性列表
也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的属性的描述,然后计算该属性在对象中的偏移量,然后伸出setter与getter方法对应的实现,在setter方法方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.
@protocol 和 category中如何使用 @property
在protocol 使用@property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守指定协议的对象的实现该属性对应的方法
在category 使用@property也是只会生成setter和getter方法的声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数
objc_setAssociatedObject 动态赋值加载
objc_getAssociatedObject 动态取值加载
runtime 如何实现 weak属性
runtime 对注册的类,会进行布局,对于weak对象会放入一个hash表中。用weak指向的对象地址作为key,当此对象的引用计数为0的时候会dealloc,进而在这个 weak表中找到此对象地址为键的所有weak对象,从而设置为nil;
[※※]@synthesize和@dynamic分别有什么作用?
@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;
@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
@dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可(不可以修改,只可以读取))。假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var =someVar,由于缺setter方法会导致程序崩溃;或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。常用于动态绑定
[※※※]ARC下,不显示指定任何属性关键字时,默认的关键字都有哪些?
对应基本数据类型默认关键字是
atomic,readwrite,assign
对于普通的OC对象
atomic,readwrite,strong
原子属性(作用:通过锁定机制来保持原子性)读写属性(readwrite 可读可写属性) assign赋值属性非拥有关系 strong拥有关系属性
[※※※]用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
因为父类指针可以指向子类对象,使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本. (关键字:副本不可变 保持对象不受外界影响)
如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性. (关键字:可能指向可变的对象,外界改变会影响属性);
[※※※]@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
如果没有指定成员变量的名称与自动生成一个属性同名的成员变量(注意:无指明生成同名),如果指定的成员变量的名称,会生成一个指定的名称的成员变量,如果这个成员已经存在了就不再生成了.
如果是 @synthesize foo;还会生成一个名称为foo的成员变量
如果是 @synthesize foo = _foo;就不会生成成员变量了. 其实默认的生成是: _变量名
[※※※※※]在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
@synthesize主要就是用来生成setter,getter方法的实现,在@property被增强之后,其实已经很少使用@synthesize了.
[※※]objc中向一个nil对象发送消息将会发生什么?
在Objective-C中向nil发送消息是完全有效的 ——只是在运行时不会有任何作用.
如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:Person *motherInlaw = [aPerson spouse] mother];如果spouse对象为nil,那么发送给nil的消息mother也将返回nil。
如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者long long的整型标量,发送给nil的消息将返回0。
如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0。
如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。
[※※※]objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?
该方法编译之后就是objc_msgSend()函数调用.如果我没有记错的大概是这样的.
((void ()(id, SEL))(void )objc_msgSend)((id)obj, sel_registerName("foo"));
[※※※]什么时候会报unrecognized selector的异常?
当使用该对象上某个方法,而该对象上没有实现这个方法的时候此时会闪退
[※※※※]一个objc对象如何进行内存布局?(考虑有父类的情况)
所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中.
每一个对象内部都一个isA指针,指向他的类对象,类对象中存放着本对象的对象方法列表和成员变量的列表,属性列表,它内部也有一个isA指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向他的父类对象.
根对象就是NSobject
[※※※※]一个objc对象的isa的指针指向什么?有什么作用?
指向他的类对象,从而可以找到对象上的方法每一个对象内部都一个isA指针,指向他的类对象,类对象中存放着本对象的对象方法列表和成员变量的列表,属性列表,它内部也有一个isA指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向他的父类对象.
[※※※※]下面的代码输出什么?
@implementation Son : Father
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
这里面的super是一个实力对象
}
return self;
}
@end
输出的结果都是:Son,
原因:super和self都是指向的本实例对象的,
不同的是,super调用的跳过本类方法,调用父类的方法
父类方法的class方法本来都是在基类中实现的,所以无论使用self和super调用都是一样的.
具体分析参照刨根问底Objective-C Runtime(1)- Self & Super
[※※※※]使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
在ARC下不需要编译器能够帮组我们来完成
在MRC中,对于使用retain或copy策略的需要
[※※※※※]objc中的类方法和实例方法有什么本质区别和联系?
类方法
类方法是属于类对象的
类方法只能通过类对象调用
类方法中的self是类对象
类方法可以调用其他的类方法
类方法中不能访问成员变量
类方法中不定直接调用对象方法
实例方法
实例方法是属于实例对象的
实例方法只能通过实例对象调用
实例方法中的self是实例对象
实例方法中可以访问成员变量
实例方法中直接调用实例方法
实例方法中也可以调用类方法(通过类名)
[※※※※]runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.(关键字:方法列表 @selector本质是方法名称)
函数指针, 用于表示对象方法的实现 typedef id ( *IMP ) ( id , SEL , . . . ) ;
id 指代objc中的对象,每个对象的在内存的结构并不是确定的,但其首地址指向的肯定是isa。通过isa指针,运行时就能获取到objc_class。
IMP 是一个函数指针。objc中的方法最终会被转换成纯C的函数,IMP就是为了表示这些函数的地址。
[※※※※※]_objc_msgForward函数是做什么的,直接调用它将会发生什么?
首先了解objc_msgSend
obj_msgSend的实际动作就是:找到这个函数指针,然后调用它。
objc_msgSend的动作比较清晰:首先在Class中的缓存查找imp函数指针(没缓存则初始化缓存),如果没找到,则向父类的Class查找。如果一直查找到NSObject根类仍旧没有实现,则用_objc_msgForward函数指针代替imp。最后,执行这个imp函数指针。
_objc_msgForward是用于消息转发的。这个函数的实现并没有在objc-runtime的开源代码里面,而是在Foundation框架里面实现的。加上断点启动程序后,会发现__CFInitialize这个方法会调用objc_setForwardHandler函数来注册一个实现。当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。为了展示消息转发的具体动作,这里尝试向一个对象发送一条错误的消息,并查看一下_objc_msgForward是如何进行转发的。结合NSObject文档可以知道,_objc_msgForward消息转发做了如下几件事:
1.调用resolveInstanceMethod:方法,允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回。如果仍没实现,继续下面的动作。
2.调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接转发给它。如果返回了nil,继续下面的动作。
3.调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。
4.调用forwardInvocation:方法,将地3步获取到的方法签名包装成Invocation传入,如何处理就在这里面了。
上面这4个方法均是模板方法,开发者可以override,由runtime来调用。最常见的实现消息转发,就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的。
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //调用这个函数,伪代码...
}
//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) return _objc_msgForward; //这个是用于消息转发的
return imp;
}
IMP lookUpImpOrNil(Class cls, SEL sel) {
if (!cls->initialize()) {
_class_initialize(cls);
}
Class curClass = cls;
IMP imp = nil;
do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询
if (!curClass) break;
if (!curClass->cache) fill_cache(cls, curClass);
imp = cache_getImp(curClass, sel);
if (imp) break;
} while (curClass = curClass->superclass);
return imp;
}
[※※※※※]runtime如何实现weak变量的自动置nil?
2. 我猜系统会维护一个弱指针列表,当某个对象销毁时候,它会把所有指向该对象的弱指针设置为nil
ARC 的实现
苹果的官方说明中称,ARC是“由编译器进行内存管理”的,但实际上只有编译器是无法完全胜任的,ARC还依赖OC运行时库,也就是说ARC是通过以下工具、库来实现的:
● clang (LLVM 编译器)3.0以上
● objc4 OC运行时库 493.9以上
如果按照苹果的说明,仅仅是编译器管理内存的,那么__weak修饰符也可以在iOS 4中使用
__weak 修饰符
就像我们知道的那样__weak修饰符提供了如同魔法般的公能。
● 若使用__weak修饰符的变量引用对象被废弃时,则将nil赋值给该变量
● 使用附有__weak修饰符的变量,就是使用注册到autoreleasepool的对象。
我们来看看它的实现:
{
id __weak obj_weak = obj;//obj已被赋值,并且是strong类型的
}
/*编译器的模拟代码*/
id obj_weak;
objc_initWeak(&obj_weak,obj);//初始化附有__weak修饰符的变量
objc_destroyWeak(&obj_weak);//释放该变量
其中objc_initWeak objc_destroyWeak都是调用了objc_storeWeak函数,所以,上面的代码可以转化为下面的代码
id obj_weak;
obj_weak = 0;
objc_storeWeak(&obj_weak,obj);
objc_storeWeak(&obj,0);
objc_storeWeak函数以把obj的地址作为键值,obj_weak的地址作为值存放到weak表(weak是一个hash表)中。
释放对象时,废弃对象的同时,程序的动作是怎样的呢?对象通过objc_release释放。
1. objc_release
2. 因为引用计数为0所以执行dealloc
3. _objc_rootDealloc
4. object_dispose
5. objc_destructInstance
6. objc_clear_deallocating
而,调用objc_clear_deallocating的动作如下:
1. 从weak表中获取废弃对象的地址为键值的记录。
2. 将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil
3. 从weak表中删除记录
4. 从引用计数表中删除废弃对象的地址作为键值的记录
根据以上步骤,前面说的如果附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给这个变量,这个功能即被实现。
__weak的第二个功能,使用__weak修饰符的变量,即是使用注册到autoreleasepool中的对象。
{
id __weak obj_weak = obj;//obj已被赋值,并且是strong类型的
NSLog(@"%@",obj_weak);
}
/*编译器的模拟代码*/
id obj_weak;
objc_initweak(&obj_weak,obj);
id tmp = objc_loadWeakRetained(&obj_weak);
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destroyWeak(&obj_weak);
与被赋值时相比,在使用附有__weak修饰符变量的情形下,增加了对objc_loadWeakRetained函数和objc_autorelease函数的调用。这些函数的动作如下:
1. objc_loadWeakRetained函数取出附有__weak修饰符变量所引用的对象并retain
2. objc_autorelease函数将对象注册到autorelease中。
由此可知,因为附有__weak修饰符变量所引用的对象这样被注册到autorelease中,所以在@autoreleasepool块结束之前都可以放心使用。
注:OC中有一些类,并不支持ARC,例如NSMachPort类。可以通过allowsWeakReference/retainWeakReference方法来判断是否支持ARC
[※※※※※]能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
因为编译后的类已经注册在 runtime中,类结构体中的 objc_ivar_list实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量,
运行时创建的类是可以添加实例变量,调用 class_addIvar函数。但是得在调用 objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。
[※※※]runloop和线程有什么关系?
1. 每一个线程中都一个runloop,只有主线的的runloop默认是开启的,其他线程的runloop是默认没有开启的
2. 可以通过CFRunLoopRun()函数来开启一个事件循环
3. 看SDWebImage源码的时候见到有这么用过.
[※※※]runloop的mode作用是什么?
model 主要是用来指定时间在运行循环中的优先级的
苹果公开提供的 Mode 有两个:
kCFRunLoopDefaultMode
kCFRunLoopCommonModes
如果我们把一个NSTimer对象以kCFRunLoopDefaultMode添加到主运行循环中的时候,当一直有用户事件处理的时候,NSTimer将不再被调度
如果我们把一个NSTimer对象以kCFRunLoopCommonModes添加到主运行循环中的时候,当一直有用户事件处理的时候,NSTimer还能正常的调度,互不影响.
[※※※※]以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
用sche创建的是在主线程是默认的模式下运行的,当滚动列表的时候runloop是运行在tracking的一个模式下,所以默认模式下的timer是不会执行,
解决,把timer加到common的一个模式下,这个是一个模式集合,包括默认的模式,和tracking的模式
如果我们把一个NSTimer对象以kCFRunLoopDefaultMode添加到主运行循环中的时候,当一直有用户事件处理的时候,NSTimer将不再被调度
如果我们把一个NSTimer对象以kCFRunLoopCommonModes添加到主运行循环中的时候,当一直有用户事件处理的时候,NSTimer还能正常的调度,互不影响.
[※※※※※]猜想runloop内部是如何实现的?
1. 他是一个死循环
2.如果事件队列中存放在事件,那就取出事件,执行相关代码
3.如果没有事件,就挂起,等有事件了,立即唤醒事件循环,开始执行.
简单来说。。。
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
[※]objc使用什么机制管理对象内存?
* MRC 手动引用计数 retainCount
* ARC 自动引用计数,现在通常使用自动引用计数 retainCount
[※※※※]ARC通过什么方式帮助开发者管理内存?
通过编译器在编译的时候,插入如内管理的代码动态的绑定
[※※※※]不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)
在每次事件循环开始创建自动释放池,在每次事件结束销毁自动释放池
以viewDidLoad方法为例,可以理解为在viewDidLoad方法开始执行之前创建自动释放池,
在viewDidLoad方法执行之后销毁自动释放池
[※※※※]BAD_ACCESS在什么情况下出现?
1. 死循环了
2. 访问一个僵尸对象
[※※※※※]苹果是如何实现autoreleasepool的?
1. 我猜想autoreleasepool本质就是一个队列(数组),
2. 当调用autorelease的时候会把该对象添加到autoreleasepool中,并且把引用计数+1
3. 当autoreleasepool即将销毁的时候,把其中的所有对象进行一次release操作
[※※]使用block时什么情况会发生引用循环,如何解决?
只要是一个对象对该block进行了强引用,在block内部有直接使用到该对象解决方案:在block外面使用__strong来修饰替换对应的强引用
[※※]在block内如何修改block外部变量?
通过 __bock修改的外部变量,可以在block内部修改
当Block从栈复制到堆时,会使用_Block_object_assign函数持有该变量(相当于retain)。当堆上的Block被废弃时,会使用_Block_object_dispose函数释放该变量(相当于release)。
由上文描述可知,我们可以使用下述代码解除Block循环引用的问题:
__block id tmp = self;
void(^block)(void) = ^{
tmp = nil;
};
block();
通过执行block方法,nil被赋值到_block变量tmp中。这个时候_block变量对 self 的强引用失效,从而避免循环引用的问题。使用__block变量的优点是:
通过__block变量可以控制对象的生命周期
在不能使用__weak修饰符的环境中,我们可以避免使用__unsafe_unretained修饰符
在执行Block时可动态地决定是否将nil或者其它对象赋值给__block变量
[※※※]使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
一般不用考虑,因为官方文档中没有告诉我们要注意发生强引用,所以推测系统控件一般没有对这些block进行强引用,所以我们可以不用考虑循环强引用的问题
[※※]GCD的队列(dispatch_queue_t)分哪两种类型?
并行队列同时会开很多线程,(测试用了11个任务,结果显示11个任务同时执行了),可以使用信号量来控制线程数量,函数concurrentQueueTest中,最多同时运行三个任务;
串行队列,执行完一个任务才会执行下一个任务,如果有两个串行队列,则两个串行队列可以并发执行,见serialQueueTest函数以及其输出;
如果某些任务需要更另一些任务完成后才执行,可以使用dispatch_group_t,见groupQueueTest以及其输出;
[※※※※]如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
总体上说: 使用 dispatch group,然后 wait forever等待完成, 或者采取 group notify 来通知回调。
细节:
1. 创建异步队列
2. 创建dispatch_group dispatch_group_t = dispatch_group_create()
3. 通过组来执行异步下载任务
dispatch_group_async(queueGroup, aQueue, ^{
NSLog(@"下载图片.");
});
4.等到所有任务完成 dispatch_group_wait(queueGroup, DISPATCH_TIME_FOREVER);
5.合成图片
[※※※※]dispatch_barrier_async的作用是什么?
barrier:是障碍物的意思,在多个并行任务中间,他就像是一个隔离带,把前后的并行任务分开.
dispatch_barrier_async 作用是在并行队列中,等待前面操作并行任务完成再执行dispatch_barrier_async中的任务,如果后面还有并行任务,会开始执行后续的并行任务
[※※※※※]苹果为什么要废弃dispatch_get_current_queue?
容易误用造成死锁
[※※※※※]以下代码运行结果如何?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 线程锁死
});
NSLog(@"3");
}
只能输出1,然后线程主线程死锁
[※※]addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?
// 添加键值观察
1. 调用对象:要监听的对象
2. 参数
1> 观察者,负责处理监听事件的对象
2> 观察的属性
3> 观察的选项
4> 上下文
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
// NSObject 分类方法,意味着所有的 NSObject都可以实现这个方法!
// 跟协议的方法很像,分类方法又可以称为“隐式代理”!不提倡用,但是要知道概念!
// 所有的 kvo监听到事件,都会调用此方法
1. 观察的属性
2. 观察的对象
3. change 属性变化字典(新/旧)
4. 上下文,与监听的时候传递的一致
可以利用上下文区分不同的监听!
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"睡会 %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@ %@ %@ %@", keyPath, object, change, context);
}
[※※※]如何手动触发一个value的KVO
1.通过setValue:forKey:给属性赋值
2.通过setValue:forKeyPath:给属性赋值
3.直接调用setter方法方法给属性赋值
4.直接通过指针给属性赋值
给这个value设置一个值,就可以触发了
[※※※]若一个类有实例变量NSString *_foo,调用setValue:forKey:时,可以以foo还是_foo作为key?
都可以
[※※※※]KVC的keyPath中的集合运算符如何使用?
1. 必须用在集合对象上或普通对象的集合属性上
2. 简单集合运算符有@avg, @count, @max , @min ,@sum,
3. 格式 @"@sum.age"或 @"集合属性.@max.age"
[※※※※]KVC和KVO的keyPath一定是属性么?
1.一个可以是成员变量
[※※※※※]如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?
当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa指针 ( isa 指针告诉 Runtime系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。
原来,这个中间类,继承自原本的那个类。不仅如此,Apple还重写了 -class 方法,企图欺骗我们这个类没有变,就是原本那个类。更具体的信息,去跑一下 Mike Ash的那篇文章里的代码就能明白,这里就不再重复。
[※※※※※]apple用什么方式实现对一个对象的KVO?
同上
[※※]IBOutlet连出来的视图属性为什么可以被设置成weak?
因为视图已经对它有一个强引用了
[※※※※※]IB中User Defined Runtime Attributes如何使用?
User Defined Runtime Attributes 是一个不被看重但功能非常强大的的特性,
它能够通过KVC的方式配置一些你在interface builder中不能配置的属性。当你希望在IB中作尽可能多得事情,
这个特性能够帮助你编写更加轻量级的viewcontroller
[※※※]如何调试BAD_ACCESS错误
1.设置全局断点快速定位问题代码所在行
[※※※]lldb(gdb)常用的调试命令?
最常用就是 : po 对象
[※]浅拷贝深拷贝
浅拷贝是拷贝指针,深拷贝是新开辟空间,把里面的类容拷贝出来
[※]#include,#import,@class的区别
#include
#include <> :用于对系统文件的引用,编译器会在系统文件目录下去查找该文件。
#include "xx.h":用于对用户自定义的文件的引用,编译器首先会去用户目录下查找,然后去安装目录,最后去系统目录查找。
注:使用include要注意重复引用的问题:
class A,class B都引用了class C,class D若引用class A与class B,就会报重复引用的错误。
#import
功能与include基本相同,不过它避免了重复引用的问题。所以在OC中我们基本用的都是import。
@class
@class就是告诉编译器有这个类存在,但是类是如何实现的不用告诉编译器.若.m文件用到了这个类,还是要在.m文件汇总import这个类的。
既然这样,为什么不直接在头文件中import呢,举个例子:
class A引用了class B,class B引用了class C.... , class A,B,C...的头文件又import了很多文件,那么 import了A的话,编译器就需要编译大量的文件,编译时间就会增加。
难道头文件中都是用@class吗?当然不是,有时也是需要#import的,那么什么时候该用什么呢?
(1)一般如果有继承关系的用#import,如B是A的子类那么在B中声明A时用#import;
(2) 另外就是如果有循环依赖关系,如:A->B,B->A这样相互依赖时,如果在两个文件的头文件中用#import分别声明对方,那么就会出现头文件循环利用的错误,这时在头文件中用@class声明就不会出错;
(3)还有就是自定义代理的时候,如果在头文件中想声明代理的话如@interface SecondViewController:UIViewController时应用#import不然的话会出错误,注意XXXXDelegate是自定义的。
关键字const什么含义
const意味着”只读”,下面的声明都是什么意思?
const int a; a是常整型数
int const a; a是常整型数
const int *a; a是指向整型数的指针整型数不可以修改,指针可以修改
int * const a; 指针不可修改整型数可以修改
int const * a const; 整型数不可以修改 同时指针也不可以修改
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。
结论:
• 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果
你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清
理的。)
• 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
• 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需要对它进行初
始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者同时指
定为 const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为 const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为 const类型,以使得其返回值不为“左值”。
static作用?
(1) 函数体内 static变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的 static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的 static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的 static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的 static成员函数属于整个类所拥有,这个函数不接收 this指针,因而只能访问类的static 成员变量。
在类中声明私有方法 ?
两种方式:
1.不声明方法只实现就可以了
2.声明在头文件中,但是函数使用(private)修饰
堆和栈的区别?
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
申请大小:
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的。
ViewController的didReceiveMemoryWarning是在什么时候调用的?默认的操作是什么?
当程序接到内存警告时View Controller将会收到这个消息:didReceiveMemoryWarning
从iOS3.0开始,不需要重载这个函数,把释放内存的代码放到viewDidUnload中去。
这个函数的默认实现是:检查controller是否可以安全地释放它的view(这里加粗的view指的是controller的view属性),比如view本身没有superview并且可以被很容易地重建(从nib或者loadView函数)。
如果view可以被释放,那么这个函数释放view并调用viewDidUnload。
你可以重载这个函数来释放controller中使用的其他内存。但要记得调用这个函数的super实现来允许父类(一般是UIVIewController)释放view。
如果你的ViewController保存着view的子view的引用,那么,在早期的iOS版本中,你应该在这个函数中来释放这些引用。而在iOS3.0或更高版本中,你应该在viewDidUnload中释放这些引用。
怎么理解MVC,在Cocoa中MVC是怎么实现的?
MVC设计模式考虑三种对象:模型对象、视图对象、和控制器对象。模型对象代表特别的知识和专业技能,它们负责保有应用程序的数据和定义操作数据的逻辑。视图对象知道如何显示应用程序的模型数据,而且可能允许用户对其进行编辑。控制器对象是应用程序的视图对象和模型对象之间的协调者。
ViewCotroller
Xib
delegate和notification区别,分别在什么情况下使用?
KVC(Key-Value-Coding)
KVO(Key-Value-Observing)
理解KVC与KVO(键-值-编码与键-值-监看)
当通过KVC调用对象时,比如:[self valueForKey:@”someKey”]时,程序会自动试图通过几种不同的方式解析这个调用。首先查找对象是否带有 someKey这个方法,如果没找到,会继续查找对象是否带有someKey这个实例变量(iVar),如果还没有找到,程序会继续试图调用 -(id) valueForUndefinedKey:这个方法。如果这个方法还是没有被实现的话,程序会抛出一个NSUndefinedKeyException异常错误。
(Key-Value Coding查找方法的时候,不仅仅会查找someKey这个方法,还会查找getsomeKey这个方法,前面加一个get,或者_someKey以及_getsomeKey这几种形式。同时,查找实例变量的时候也会不仅仅查找someKey这个变量,也会查找_someKey这个变量是否存在。)
设计valueForUndefinedKey:方法的主要目的是当你使用-(id)valueForKey方法从对象中请求值时,对象能够在错误发生前,有最后的机会响应这个请求。
类变量的@protected ,@private,@public,@package,声明各有什么含义?
上面的几个声明表明的是类成员的作用域,
@private作用范围只能在自身类(外界既不可访问,又不能继承);
@protected作用范围在自身类和子类,如果什么都不加修饰,默认是@protected(外界不可访问,但是可以继承);
@public作用范围最大,可以在任何地方被访问(外界即可访问,又可以继承);
@package作用范围在某个框架内
线程是什么?进程是什么?二者有什么区别和联系?
(记住这个例子:进程可以理解为工厂的生产车间线程可以理解为工厂车间的生产线 一个工厂至少有一个生产车间 一个生产车间至少有一条生产线)
线程是CPU独立运行和独立调度的基本单位(可以理解为一个进程中执行的代码片段),
进程是资源分配的基本单位(进程是一块包含了某些资源的内存区域)。
进程是线程的容器,真正完成代码执行的是线程,而进程则作为线程的执行环境。一个程序至少包含一个进程,一个进程至少包含一个线程,一个进程中的多个线程共享当前进程所拥有的资源。
谈谈你对多线程开发的理解?ios中有几种实现多线程的方法?
好处:
1、使用线程可以把程序中占据时间长的任务放到后台去处理,如图片、视频的下载(后台执行IO流,增强体验)
2、发挥多核处理器的优势,并发执行让系统运行的更快、更流畅,用户体验更好(发挥优势,并发执行)
缺点:
1、大量的线程降低代码的可读性,
2、更多的线程需要更多的内存空间
3、当多个线程对同一个资源出现争夺的时候要注意线程安全的问题。(这时候一般会使用线程锁)
iOS有三种多线程编程的技术:
1、NSThread(两种创建方式)
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:nil];
[myThread start];
2、NSOperationQueue
NSOperationQueue *oprationQueue = [[NSOperationQueue alloc] init];
oprationQueue addOperationWithBlock:^{
//这个block语句块在子线程中执行
}
3、Grand Central Dispatch (GCD)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗时的操作比如下载应用程序 下载图片上传图片
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面UI元素
}); });
http://blog.csdn.net/totogo2010/article/details/8016129
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"];
NSData * data = [[NSData alloc]initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc]initWithData:data];
if (data != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
}
});
PS:不显示的创建线程的方法:
用NSObject的类方法 performSelectorInBackground:withObject:创建一个线程:
[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
内存中的几个区?
http://blog.csdn.net/hairetz/article/details/4141043
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap)一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。(这就是为什么OC中的对象都是在堆区)
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。-程序结束后由系统释放。
4、文字常量区 —常量字符串就是放在这里的。程序结束后由系统释放
5、程序代码区 —存放函数体的二进制代码。
例子:
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main() {
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456/0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456/0放在常量区,编译器可能会将它与p3所指向的"123456"
优化成一个地方。
}
二、堆和栈的理论知识
2.1申请方式
stack(栈):由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间
heap(堆):需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = new char[10];
但是注意p1、p2本身是在栈中的。
2.2申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表
中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。
另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有
的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
2.5堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈
的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
2.6存取效率的比较
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到
edx中,再根据edx读取字符,显然慢了。
2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就像我们去饭馆里吃饭,只管点菜(发出申请),付钱,和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。(经典!)
iOS的动态性
iOS的动态性来自三个方面:动态类型、动态绑定、动态载入、SEL类型
1、动态类型<弱类型>(id):在代码的运行阶段判断代码的类型,使用id类型可以让应用在“运行时”使用任何类型来替换。动态类型让程序更加灵活,但是会使数据的统一性降低和代码的可读性。我们常用静态类型<强类型>(如NSString),使用静态类型编译器可以完全分析你的代码,这让代码的性能和可预知性更高。
2、动态绑定:让代码在运行时判断需要调用什么方法,而不是在编译时。动态类型和动态绑定使得选择哪个接收者已经调用什么方法都放到运行时去完成。
3、动态载入:应用程序可以根据需要加载可执行代码以及资源,而不是在启动时就加载所有资源。
4、SEL类型 iOS在编译的时候会根据方法的名字(包括参数序列),生成一个用来区分这个方法的唯一的ID,这个ID是SEL类型的,SEL的本质就是类方法的编号[函数地址]。(类似C语言里面的函数指针,但是OC的类不能直接使用函数指针,这样只能做一个@selector语法来取。注意:@selector是查找当前类(含子类)的方法。)
怎样实现一个singleton的类。
static LOSingleton * shareInstance;
+(LOSingleton *)sharedInstance
{
@synchronized(self){
//这个东西其实就是一个加锁。如果self其他线程访问,则会阻塞。这样做一般是用来对单例进行一个死锁的保护
if (shareInstance == nil) {
shareInstance = [[super allocWithZone:NULL] init];
}
}
return shareInstance;
}
//第二种方式
+ (LOSingleton *) sharedInstance
{
static LOSingleton *sharedInstance = nil ;
static dispatch_once_t onceToken; // 锁
dispatch_once (& onceToken, ^ { // 最多调用一次
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
什么是安全释放?
在对象release之后把指针置为nil这样保证了数据的清空和指针的清空;
什么是序列化和反序列化,可以用来做什么?如何在OC中实现复杂对象的存储?
如果你需要存储一个复杂的对象的话,经常要以二进制的方法序列化这个对象,这个过程叫Archiving。如果一个对象需要进行序列化,那么需要遵循NScoding协议,主要有两个方法:
-(id)initWithCoder:(NSCoder*)coder;//从coder中读取数据,保存到相应变量中,即反序列化数据。
-(void)encodeWithCoder:(NSCoder*)coder;//读取实例变量,并把这些数据写到coder中去,即序列化数据。
写一个标准宏MIN,这个宏输入两个参数并返回较小的一个?
#define kMIN(X,Y) ((X) > (Y)) ? (Y) :(X)
#define kMIN(x,y) ((x) > (y)) ? (y) : (x)
描述应用程序的启动顺序。
1、程序入口main函数创建UIApplication实例和UIApplication代理实例.
2、在UIApplication代理实例中重写启动方法,设置第一ViewController.
3、在第一ViewController中添加控件,实现对应的程序界面.
读取图片
1.从资源(resource)读取
UIImage* image=[UIImage imageNamed:@"1.jpg"];
2.从网络读取
NSURL *url=[NSURL URLWithString:@"http://www.sinaimg.cn/qc/photo_auto/chezhan/2012/50/00/15/80046_950.jpg"];
UIImage *imgFromUrl =[[UIImage alloc]initWithData:[NSData dataWithContentsOfURL:url]];
3.从手机本地读取
//读取本地图片非resource
NSString *aPath3=[NSString stringWithFormat:@"%@/Documents/%@.jpg",NSHomeDirectory(),@"test"];
UIImage *imgFromUrl3=[[UIImage alloc]initWithContentsOfFile:aPath3];
UIImageView* imageView3=[[UIImageView alloc]initWithImage:imgFromUrl3];
4.从现有的context中获得图像
//add ImageIO.framework and #import <ImageIO/ImageIO.h>
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
CGImageRef img= CGImageSourceCreateImageAtIndex(source,0,NULL);
CGContextRef ctx=UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
//transformCTM的2种方式
//CGContextConcatCTM(ctx, CGAffineTransformMakeScale(.2, -0.2));
//CGContextScaleCTM(ctx,1,-1);
//注意坐标要反下,用ctx来作为图片源
CGImageRef capture=CGBitmapContextCreateImage(ctx);
CGContextDrawImage(ctx, CGRectMake(160, 0, 160, 230), [image CGImage]);
CGContextDrawImage(ctx, CGRectMake(160, 230, 160, 230), img);
CGImageRef capture2=CGBitmapContextCreateImage(ctx);
5.用Quartz的CGImageSourceRef来读取图片
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
CGImageRef img= CGImageSourceCreateImageAtIndex(source,0,NULL);
二.保存图片
1.转换成NSData来保存图片(imgFromUrl是UIImage)
//保存图片 2种获取路径都可以
//NSArray*paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//NSString*documentsDirectory=[paths objectAtIndex:0];
//NSString*aPath=[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.jpg",@"test"]];
NSString *aPath=[NSString stringWithFormat:@"%@/Documents/%@.jpg",NSHomeDirectory(),@"test"];
NSData *imgData = UIImageJPEGRepresentation(imgFromUrl,0);
[imgData writeToFile:aPath atomically:YES];
2.用Quartz的CGImageDestinationRef来输出图片,这个方式不常见,所以不做介绍,详细可以看apple文档Quartz 2D Programming Guide
三.绘制图(draw|painting)
1.UIImageView方式加入到UIView层
UIImageView* imageView=[[UIImageView alloc]initWithImage:image];
imageView.frame=CGRectMake(0, 0, 320, 480);
[self addSubview:imageView];
[imageView release];
2.[img drawAtPoint]系列方法
[image4 drawAtPoint:CGPointMake(100, 0)];
3.CGContextDrawImage
CGContextDrawImage(ctx, CGRectMake(160, 0, 160, 230), [image CGImage]);
4.CGLayer
这个是apple推荐的一种offscreen的绘制方法,相比bitmapContext更好,因为它似乎会利用iphone硬件(drawing-card)加速
CGLayerRef cg=CGLayerCreateWithContext(ctx, CGSizeMake(320, 480), NULL);
//需要将CGLayerContext来作为缓存context,这个是必须的
CGContextRef layerContext=CGLayerGetContext(cg);
CGContextDrawImage(layerContext, CGRectMake(160, 230, 160, 230), img);
CGContextDrawLayerAtPoint(ctx, CGPointMake(0, 0), cg);
5.CALayer的contents
UIImage* image=[UIImage imageNamed:@"1.jpg"];
CALayer *ly=[CALayer layer];
ly.frame=CGRectMake(0, 0, 320, 460);
ly.contents=[image CGImage];
[self.layer addSublayer:ly];
四.其它
1.CGImage和UIImage互换
这样就可以随时切换UIKit和Quartz之间类型,并且选择您熟悉的方式来处理图片.
CGImage cgImage=[uiImage CGImage];
UIImage* uiImage=[UIImage imageWithCGImage:cgImage];
2.UIImage resizableImageWithCapInsets的问题
假设一张44x29的图片,同样的Insets=UIEdgeInsetsMake(10,10,10,10)在@2x情况和非@2x情况下,表现会有不同,非@2x是OK正常的,但是如果同样尺寸的图片变成@2x,则导致在切换过渡的时候会很卡,应该是在不同的重绘导致的,表面原因是因为Insets设置的是点,在@2x情况下拉伸,其实拉升的像素是上面20,下面也是20,但是图片其实只有29,所以导致不正确,只要将insets设置成=UIEdgeInsetsMake(5,10,5,10)就正常了,所以以后要注意了。
3.动画图片使用注意
animationImage 设置完毕以后要startAnimation.不会自动启动动画图片。
此外在读取大量动画图片的时候不太适合用这个方法,因为一下子那么多图片容易爆掉。可以用这个方法替代,具体我也没试,方法就是手动切换图片,并非直接使用系统方法而已。
imgV=[[UIImageView alloc]initWithFrame:CGRectMake(40, 40, 128, 128)];
[self.window addSubview:imgV];
[self performSelectorInBackground:@selector(playAnim)withObject:nil];
[imgV release];
-(void)playAnim{
for (int i=0;i<101;){
usleep(100000);
UIImage *image=[[UIImage alloc]initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%d",i+1 ] ofType:@"tiff"]];
[self performSelectorOnMainThread:@selector(changeImage:) withObject:image waitUntilDone:YES];
i++;
}
}
-(void)changeImage:(UIImage*)image{
imgV.image=image;
}
相关帖子:http://www.cocoachina.com/bbs/read.php?tid=110154
4.UIControl设置UIImage
问题描述主要是有一个很小的叉按钮,需要响应很大的点击区域,这个其实很简单,代码如下:
UIImage *bg=[UIImage imageNamed:@"heizi1.jpg"];
//图片大于点及区域,缩小下就行
bg=[self scaleImage:bg ToSize:(CGSize){100,100}];
UIButton* button = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
//图片大于button,则会被拉伸,如果小于button则居中显示
[button setImage:bg forState:UIControlStateNormal];
此外多说一句,这个icon图片如果要准备2套图,缩放毕竟消耗效率
缩放图片代码
-(UIImage *)scaleImage:(UIImage *)img ToSize:(CGSize)itemSize{
UIImage *i;
CGSize itemSize=CGSizeMake(30, 30);
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect=CGRectMake(0, 0, itemSize.width, itemSize.height);
[img drawInRect:imageRect];
i=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return i;
从view截图出来
#import <QuartzCore/QuartzCore.h>
-(UIImage *)getImageFromView:(UIView *)orgView{
UIGraphicsBeginImageContext(orgView.bounds.size);
[orgView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
http://blog.csdn.net/jerryvon/article/details/7526147#
用obj-c写一个冒泡排序
NSMutableArray *arr = [NSMutableArray arrayWithArray:@[@"10",@"1",@"3",@"6",@"12",@"0",@"4",@"8"]];
NSString *temp;
for (int i = 0; i < arr.count; i++) {
for (int j = 0; j < arr.count - 1 - i; j++) {
if ([arr[j] integerValue] > [arr[j+1] integerValue]) {
temp = arr[j];
[arr replaceObjectAtIndex:j withObject:arr[j+1]];
[arr replaceObjectAtIndex:j+1 withObject:temp];
}
}
}
c语言写一个冒泡排序
void bubblesort(int r[100],int n) {
for (int i = 0;i < n-1;i++){
for (int j = 0;j< n-1-j;j++){
if (r[j] > r[j+1]){
int temp = r[j];
r[j] = r[j+1];
r[j+1] = temp;
}
}
}
}
简述你对UIView、UIWindow和CALayer的理解
UIView继承于UIResponder, UIResponder继承于NSObject,UIView可以响应用户事件。CALayer继承于NSObject,所以CALayer不能响应事件。
UIView构建界面,UIView侧重于对内容的管理,CALayer侧重于对内容的绘制。
UIView是用来显示内容的,可以处理用户事件;CALayer是用来绘制内容的,对内容进行动画处理,依赖与UIView来进行显示,不能处理用户事件。
分析json、xml的区别?json、xml解析方式的底层是如何处理的?
json底层原理遍历字符串中的字符,最终根据规定的特助字符,比如 {}, [], :号等进行区分,{}是字典,[]表示的时数组,:号是字典的键和值的分水岭,最总是将json数据转化为字典。Xml两种解析方式,DOM和SAX,DOM需要读入整个XML文档(文档驱动),SAX是事件驱动的,并不需要读入整个文档,文档的读入过程也就是SAX的解析过程。
面向对象的三大特征,并作简单的介绍
封装、继承、多态。
封装:是把客观事物封装成抽象的类,隐藏内部的实现,对外部提供接口。
继承:可以使用现有类的所有功能,并且在无需重新编写原来的类的情况下对这些功能进行扩展。比如:我们一般继承UIViewController来扩展我们自己的控制器类
多态:不同的对象以自己的方式响应相同的的消息的能力叫做多态,或者说父类指针指向子类对象<如UITableView的,cellForRow方法,返回值类型是UITbaleViewCell,但是你返回的cell可以是你自定义的cell,在比如多个类里面都有同一个方法>
简述NotificationCenter、KVC、KVO、Delegate?并说明它们之间的区别?
Notification:观察者模式,controller向defaultNotificationCenter添加自己的 notification,其他类注册这个notification就可以收到通知,这些类可以在收到通知时做自己的操作(多观察者默认随机顺序发通知给观察者们,而且每个观察者都要等当前的某个观察者的操作做完才能轮到他来操作,可以用NotificationQueue的方式安排观察者的反应顺序,也可以在添加观察者中设定反映时间,取消观察需要在viewDidUnload跟dealloc中都要注销)。
KVC:键值编码,可以直接通过字符串的名字(key)来间接访问属性的机制,而不是通过调用getter和setter方法访问。
KVO:观测指定对象的属性,当指定对象的属性更改之后会通知相应的观察者。
delegate:一对一,delegate遵循某个协议并实现协议声明的方法。
分别描述类别(categories)和延展(extensions)是什么?以及两者的区别?继承和类别在实现中有何区别?为什么Category只能为对象添加方法,却不能添加成员变量?
category类目:在不知道源码的情况下为一个类扩展方法,extension:为一个类声明私有方法和变量。
继承是创建了一个新的类,而类别只是对类的一个扩展,还是之前的类。
categories类目的作用就是为已知的类添加方法。
说说响应链
当事件发生的时候,响应链首先被发送给第一个响应者(往往是事件发生的视图,也就是用户触摸屏幕的地方)。事件将沿着响应者链一直向下传递,直到被接受并作出处理。一般来说,第一响应这是个视图对象或者其子类,当其被触摸后事件就交由它处理,如果他不处理,事件就会被传递给视图控制器对象UIViewController(如果存在),然后是它的父视图对象(superview),以此类推直到顶层视图。接下来会沿着顶层视图(top view)到窗口(UIwindow 对象) 再到程序的(UIApplication对象),如果整个过程都没有响应这个事件,则该事件被丢弃,一般情况下,在响应链中只要有对象处理事件,事件就会被传递.典型的响应路线图如:First Responser --> The Window -->The Applicationn --> App Delegate
你做iphone开发时候,有哪些传值方式,view和Controller之间是如何传值的?
属性、delegate、blocks单例 通知 本地化userDefault splite数据库 nskeyarcher
介绍一下XMPP?有什么优缺点吗?
XMPP:基于XML的点对点的即时通讯协议。
XMPP 协议是公开的,XMPP协议具有良好的扩展性,安全性
缺点是只能传输文本
http://blog.csdn.net/kaitiren/article/details/29586565
描述上拉加载、下拉刷新的实现机制?
根据下拉或者上拉的距离来判断是否进行网络请求,网络请求的下拉刷新的时候要把对应的page值置为0也就是第一页 下拉刷新的时候要把对应的page++请求新一页面的数据,然后针对现有的数组中添加数据这时候一般使用懒加载的形式添加对应可变数组
谈谈对性能优化的看法,如何做?
从用户体验出发:
1、程序logging不要太长、
2、相同数据不做重复获取
3、昂贵资源要重用(cell、sqlite、date),
4、良好的编程习惯和程序设计:选择正确的集合对象和算法来进行编程、选择适合的数据存储格式(plist、SQLite)、优化SQLite查询语句
5、数据资源方面的优化(缓存和异步加载)
解决方案:
改进代码和设计
能够发现问题
利用log或工具分析问题原因
假设问题原因
block的声明和实现
声明:
#import <Foundation/Foundation.h>
typedef void(^TestBlock)(NSString *string);
@interface LO_Person : NSObject
+ (void)showStringFromBlcok:(TestBlock)justBlock;
@end
实现:
#import "LO_Person.h"
@implementation LO_Person
+ (void)showStringFromBlcok:(TestBlock)justBlock
{
NSString *str = @"测试blcok";
justBlock(str);
}
@end
调用:
[LO_Person showStringFromBlcok:^(NSString *string) {
NSLog(@"-- %@",string);
}];
当前有一个数组,里面有若干重复的数据,如何去除重复的数据?(会几个写几个)
最简单的方式,把数组里面的元素放到集合里面。
也可以对数组进行排序,排序之后把数组里相同的元素删除掉
还可以使用数组中的同一个数据即作为key和value之后使用allValues来处理得到不重复的数组
以.mm为拓展名的文件里,可以包含的代码有哪些?c和obj-c如何混用
obj-c的编译器处理后缀为m的文件时,可以识别为obj-c和c的代码,处理mm文件可以识别为obj-c,c,c++代码,但cpp文件必须只能用c/c++代码,而且cpp文件include的头文件中,也不能出现obj- c的代码,因为cpp只是cpp
2) 在mm文件中混用cpp直接使用即可,所以obj-c混cpp不是问题
3)在cpp中混用obj- c其实就是使用obj-c编写的模块是我们想要的。
如果模块以类实现,那么要按照cpp class的标准写类的定义,头文件中不能出现obj-c的东西,包括#import cocoa的。实现文件中,即类的实现代码中可以使用obj-c的东西,可以import,只是后缀是mm。
如果模块以函数实现,那么头文件要按 c的格式声明函数,实现文件中,c++函数内部可以用obj-c,但后缀还是mm或m
在百度地图的部分实现类中使用了C++的东西.
sizeof和strlen的区别和联系
sizeof是运算符,strlen是函数
char str[20] = "0123456789";
int a = strlen(str); //a=10; >>>> strlen 计算字符串的长度,以结束符 0x00 为字符串结束。
int b = sizeof(str); //而b=20; >>>> sizeof计算的则是分配的数组 str[20]所占的内存空间的大小,不受里面存储的内容改变。
sprintf,strcpy,memcpy的功能?使用上要有哪些要注意的地方?
char *strcpy(char *dest, const char *src);
其对字符串进行操作,完成从源字符串到目的字符串的拷贝,当源字符串的大小大于目的字符串的最大存储空间后,执行该操作会出现段错误。
int sprintf(char*str, const char *format, ...)
函数操作的源对象不限于字符串:源对象可以是字符串、也可以是任意基本类型的数据。主要是实现将其他数据类型转换为字符串
void *memcpy(void*dest, const void *src, size_t n)
实现内存的拷贝,实现将一块内存拷贝到另一块内存。该函数对源对象与目的对象没有类型现在,只是对内存的拷贝实现的内存的拷贝
什么叫数据结构?
数据结构是计算机存储、组织数据的方式。是指相互之间存在一种或多种特定关系的数据元素的集合。
通常,精心选择的数据结构可以带来更高的运行或者存储效率。
如何引用一个已经定义过的全局变量?
extern 可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件的方式来引用某个在头文件中的全局变量,假定你那个变量写错了,那么编译期间会报错,如果用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。
TCP/IP 建立连接的过程?
客户端:来我家吃饭吧?
服务器:好的你家做的饭够不够呀?
客户端:放心吧够吃! 完成确认连接
在TCP/IP 协议中,TCP协议提供可靠的连接服务,采用三次握手建立连接;
第一次握手:建立连接时,客户端发送连接请求到服务器,并进入SYN_SEND状态,等待服务器确认; (客户端发送请求等待服务器确认 客户端进入SYN_SEND状态类似于问:来我家吃饭吧?)
第二次握手:服务器收到客户端连接请求,向客户端发送允许连接应答,此时服务器进入SYN_RECV状态;(服务器接收到之后发送允许的应答此时服务器进入等待接收SYN_RECV 类似于:好的你家做的饭够不够呀? )
第三次握手:客户端收到服务器的允许连接应答,向服务器发送确认,客户端和服务器进入通信状态,完成三次握手。(客户端向服务器发送允许确认完成:类似于:放心吧够吃!)
(所谓的三次握手,就是要有三次连接信息的发送、接收过程。TCP连的建立需要进行三次连接信息的发送、接收。)
什么是UDP和TCP的区别是什么?
TCP 的全称是传输控制协议,这种协议可以提供面向连接的、可靠的、点到点的通信,具有顺序性!
UDP 的全称是用户数据包协议。他可以提供非连接的不可靠的点懂啊多点的通信,是osi参考模型中一种无连接的传输层协议,提供面向事务的简单的不可靠信息传输,_IETF RFC 768 是UDP 的正式规范;
选择何种协议,看程序注重那个方面,可靠抑或快速。
关于block的内部探究?
http://blog.csdn.net/jasonblog/article/details/7756763
http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
通过该图,我们可以知道,一个 block 实例实际上由 6部分构成:
isa 指针,所有对象都有该指针,用于实现对象相关的功能。
flags,用于按 bit位表示一些 block 的附加信息,本文后面介绍 block copy的实现代码可以看到对该变量的使用。
reserved,保留变量。
invoke,函数指针,指向具体的 block实现的函数调用地址。
descriptor, 表示该 block 的附加描述信息,主要是 size大小,以及 copy 和 dispose 函数的指针。
variables,capture过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
广告页面的逻辑:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface SXAdManager : NSObject
+ (BOOL)isShouldDisplayAd;
+ (UIImage *)getAdImage;
+ (void)loadLatestAdImage;
@end
/**
* 思路:首先在启动的时候判断是否本地有URL如果没有,那么就直接跳过同时启动请求广告页面的请求,下载后保存成nsdata数据格式写在本地,下次启动加载本次缓存的图片,同时在下载图片成功的时候删除本次缓存的图片将新的图片数据移动到当前的位置.
*/
#import "SXAdManager.h"
#import "SXNetworkTools.h"
// 当前图片路径
#define kCachedCurrentImage ([[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingString:@"/adcurrent.png"])
// 新的图片路径
#define kCachedNewImage ([[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingString:@"/adnew.png"])
@interface SXAdManager ()
+ (void)downloadImage:(NSString *)imageUrl;
@end
@implementation SXAdManager
+ (BOOL)isShouldDisplayAd
{
return ([[NSFileManager defaultManager]fileExistsAtPath:kCachedCurrentImage isDirectory:NULL] || [[NSFileManager defaultManager]fileExistsAtPath:kCachedNewImage isDirectory:NULL]);
}
+ (UIImage *)getAdImage
{
if ([[NSFileManager defaultManager]fileExistsAtPath:kCachedNewImage isDirectory:NULL]) {
[[NSFileManager defaultManager]removeItemAtPath:kCachedCurrentImage error:nil];
[[NSFileManager defaultManager]moveItemAtPath:kCachedNewImage toPath:kCachedCurrentImage error:nil];}
return [UIImage imageWithData:[NSData dataWithContentsOfFile:kCachedCurrentImage]];
}
+ (void)downloadImage:(NSString *)imageUrl
{
//异步下载图片
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:imageUrl]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (data) {
[data writeToFile:kCachedNewImage atomically:YES];
}
}];
[task resume];
}
+ (void)loadLatestAdImage
{
NSInteger now = [[[NSDate alloc] init] timeIntervalSince1970]; // 获取当前的时间
NSString *path = [NSString stringWithFormat:@"http://g1.163.com/madr?app=7A16FBB6&platform=ios&category=startup&location=1×tamp=%ld",(long)now];//请求参数
[[[SXNetworkTools sharedNetworkToolsWithoutBaseUrl]GET:path parameters:nil success:^(NSURLSessionDataTask *task, NSDictionary* responseObject) {
NSLog(@"responseObject == %@",responseObject);
NSArray *adArray = [responseObject valueForKey:@"ads"];
NSString *imgUrl = adArray[0][@"res_url"][0];
NSString *imgUrl2 = nil;
if (adArray.count >1) {
imgUrl2= adArray[1][@"res_url"][0];
}
BOOL one = [[NSUserDefaults standardUserDefaults]boolForKey:@"one"];
if (imgUrl2.length > 0) {
if (one) {
[self downloadImage:imgUrl];
[[NSUserDefaults standardUserDefaults]setBool:!one forKey:@"one"];
}else{
[self downloadImage:imgUrl2];
[[NSUserDefaults standardUserDefaults]setBool:!one forKey:@"one"];
}
}else{
[self downloadImage:imgUrl];
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"%@",error);
}] resume];
}
@end
webView的默认缓存大小?
<ul>
<li>Memory capacity: 4 megabytes (4 * 1024 * 1024 bytes) 内存中默认是4M
<li>Disk capacity: 20 megabytes (20 * 1024 * 1024 bytes) 磁盘中20M
<li>Disk path: <nobr>(user home directory)/Library/Caches/(application bundle id)</nobr>
</ul>
#pragma mark - ******************** 拼接html语言
- (void)showInWebView
{
NSMutableString *html = [NSMutableString string];
[html appendString:@"<html>"];
[html appendString:@"<head>"];
[html appendFormat:@"<link rel="stylesheet" href="%@">",[[NSBundle mainBundle] URLForResource:@"SXDetails.css" withExtension:nil]];
[html appendString:@"</head>"];
[html appendString:@"<body>"];
[html appendString:[self touchBody]];
[html appendString:@"</body>"];
[html appendString:@"</html>"];
[self.webView loadHTMLString:html baseURL:nil];
}
- (NSString *)touchBody
{
NSMutableString *body = [NSMutableString string];
[body appendFormat:@"<div class="title">%@</div>",self.detailModel.title];
[body appendFormat:@"<div class="time">%@</div>",self.detailModel.ptime];
if (self.detailModel.body != nil) {
[body appendString:self.detailModel.body];
}
// 遍历img
for (SXDetailImgModel *detailImgModel in self.detailModel.img) {
NSMutableString *imgHtml = [NSMutableString string];
// 设置img的div
[imgHtml appendString:@"<div class="img-parent">"];
// 数组存放被切割的像素
NSArray *pixel = [detailImgModel.pixel componentsSeparatedByString:@"*"];
CGFloat width = [[pixel firstObject]floatValue];
CGFloat height = [[pixel lastObject]floatValue];
// 判断是否超过最大宽度
CGFloat maxWidth = [UIScreen mainScreen].bounds.size.width * 0.96;
if (width > maxWidth) {
height = maxWidth / width * height;
width = maxWidth;
}
// 调用js
NSString *onload = @"this.onclick = function() {"
"window.location.href = 'sx:src=' +this.src;"
"};";
[imgHtml appendFormat:@"<img onload="%@" width="%f" height="%f" src="%@">",onload,width,height,detailImgModel.src];
// 结束标记
[imgHtml appendString:@"</div>"];
// 替换标记
[body replaceOccurrencesOfString:detailImgModel.ref withString:imgHtml options:NSCaseInsensitiveSearch range:NSMakeRange(0, body.length)];
}
return body;
}
#pragma mark -- UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString *url = request.URL.absoluteString;
NSRange range = [url rangeOfString:@"sx:src="];
if (range.location != NSNotFound) {
NSInteger begin = range.location + range.length;
NSString *src = [url substringFromIndex:begin];
[self savePictureToAlbum:src];
return NO;
}
return YES;
}
KVO 的奥秘
KVO(key-value-observing)是一种十分有趣的回调机制,在某个对象注册监听者后,在被监听的对象发生改变时,对象会发送一个通知给监听者,以便监听者执行回调操作。
KVO的使用非常简单,使用KVO的要求是对象必须能支持kvc机制——所有NSObject的子类都支持这个机制。拿上面的渐变导航栏做??,我们为tableView添加了一个监听者controller,在我们滑动列表的时候,会计算当前列表的滚动偏移量,然后改变导航栏的背景色透明度。
Key-Value Observing (简写为KVO):当指定的对象的属性被修改了,允许对象接受到通知的机制。每次指定的被观察对象的属性被修改的时候,KVO都会自动的去通知相应的观察者。
//添加监听者
[self.tableView addObserver: self forKeyPath: @"contentOffset" options: NSKeyValueObservingOptionNew context: nil];
/**
* 监听属性值发生改变时回调
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
CGFloat offset = self.tableView.contentOffset.y;
CGFloat delta = offset / 64.f + 1.f;
delta = MAX(0, delta);
[self alphaNavController].barAlpha = MIN(1, delta);
}
KVO的优点:
当有属性改变,KVO会提供自动的消息通知。这样的架构有很多好处。
首先,开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知。这是KVO机制提供的最大的优点。因为这个方案已经被明确定义,获得框架级支持,可以方便地采用。开发人员不需要添加任何代码,不需要设计自己的观察者模型,直接可以在工程里使用。
其次,KVO的架构非常的强大,可以很容易的支持多个观察者观察同一个属性,以及相关的值。
KVO如何工作:
需要三个步骤来建立一个属性的观察员。理解这三个步骤就可以知道KVO如何设计工作的。
(1)首先,构思一下如下实现KVO是否有必要。比如,一个对象,当另一个对象的特定属性改变的时候,需要被通知到。例如,PersonObject希望能够觉察到BankObject对象的accountBalance属性的任何变化。
(2)那么 PersonObject必须发送一个“addObserver:forKeyPath:options:context:”消息,注册成为 BankObject的accountBalance属性的观察者。(说明:“addObserver:forKeyPath:options:context:”方法在指定对象实例之间建立了一个连接。注意,这个连接不是两个类之间建立的,而是两个对象实例之间建立的。)
(3)为了能够响应消息,观察者必须实现 “observeValueForKeyPath:ofObject:change:context:”方法。这个方法实现如何响应变化的消息。在这个方法里面我们可以跟自己的情况,去实现应对被观察对象属性变动的相应逻辑。
(4)假如遵循KVO规则的话,当被观察的属性改变的话,方法 “observeValueForKeyPath:ofObject:change:context:”会自动被调用。
1. 你使用过Objective-C的运行时编程(Runtime Programming)么?如果使用过,你用它做了什么?你还能记得你所使用的相关的头文件或者某些方法的名称吗?
Objecitve-C的重要特性是Runtime(运行时),在#import <objc/runtime.h>下能看到相关的方法,用过objc_getClass()和class_copyMethodList()获取过私有API;
objective-c
Method method1 = class_getInstanceMethod(cls, sel1);
Method method2 = class_getInstanceMethod(cls, sel2);
method_exchangeImplementations(method1, method2);
代码交换两个方法,在写unit test时使用到。
3. Core开头的系列的内容。是否使用过CoreAnimation和CoreGraphics。UI框架和CA,CG框架的联系是什么?分别用CA和CG做过些什么动画或者图像上的内容。(有需要的话还可以涉及Quartz的一些内容)
UI框架的底层有CoreAnimation,CoreAnimation的底层有CoreGraphics。
UIKit |
------------ |
Core Animation |
Core Graphics |
Graphics Hardware|
使用CA做过menu菜单的展开收起
4. 是否使用过CoreText或者CoreImage等?如果使用过,请谈谈你使用CoreText或者CoreImage的体验。
CoreText可以解决复杂文字内容排版问题。CoreImage可以处理图片,为其添加各种效果。体验是很强大,挺复杂的。
5. NSNotification和KVO的区别和用法是什么?什么时候应该使用通知,什么时候应该使用KVO,它们的实现上有什么区别吗?如果用protocol和delegate(或者delegate的Array)来实现类似的功能可能吗?如果可能,会有什么潜在的问题?如果不能,为什么?
NSNotification是通知模式在iOS的实现,KVO的全称是键值观察(Key-value observing),其是基于KVC(key-value coding)的,KVC是一个通过属性名访问属性变量的机制。例如将Module层的变化,通知到多个Controller对象时,可以使用NSNotification;如果是只需要观察某个对象的某个属性,可以使用KVO。
delegate对于委托模式,在设计模式中是对象适配器模式,其是delegate是指向某个对象的,这是一对一的关系,而在通知模式中,往往是一对多的关系。委托模式,从技术上可以现在改变delegate指向的对象,但不建议这样做,会让人迷惑,如果一个delegate对象不断改变,指向不同的对象。
6. 你用过NSOperationQueue么?如果用过或者了解的话,你为什么要使用NSOperationQueue,实现了什么?请描述它和GCD的区别和类似的地方(提示:可以从两者的实现机制和适用范围来描述)。
使用NSOperationQueue用来管理子类化的NSOperation对象,控制其线程并发数目。GCD和NSOperation都可以实现对线程的管理,区别是 NSOperation和NSOperationQueue是多线程的面向对象抽象。项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。
项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。
7. 既然提到GCD,那么问一下在使用GCD以及block时要注意些什么?它们两是一回事儿么?block在ARC中和传统的MRC中的行为和用法有没有什么区别,需要注意些什么?如何避免循环引用?
使用block是要注意,若将block做函数参数时,需要把它放到最后,GCD是Grand Central Dispatch,是一个对线程开源类库,而Block是闭包,是能够读取其他函数内部变量的函数。
未完待续...