-
一、@synthesize与@dynamic
-
@property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@synthesize var = _var;
-
1.@dynamic关键字。
这个关键字有两个作用:
1、让编译器不要创建实现属性所用的实例变量;
2、让编译器不要创建该属性的getter和setter方法。
@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定.
2.@ synthesize关键字。
这个关键字有两个作用:
1、让编译器创建实现属性所用的实例变量;
2、让编译器创建该属性的getter和setter方法。
当你同时重写了 setter 和 getter 时,系统就不会生成 ivar(实例变量/成员变量)。这时候有两种选择:
1、要么如第21行:手动创建 ivar
2、要么如第29行:使用@synthesize name = _name; ,关@property 与 ivar。
总结下 @synthesize 合成实例变量的规则,有以下几点:
如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
如果这个成员已经存在了就不再生成了.
如果是 @synthesize foo; 还会生成一个名称为foo的成员变量,也就是说:
如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,
如果是 @synthesize foo = _foo; 就不会生成成员变量了.
假如 property 名为 foo,存在一个名为 _foo 的实例变量,那么还会自动合成新变量么? 不会。如下图:
@dynamic与@synthesize的区别:
- 区别在于:使用@synthesize,编译器会确实的产生getter和setter方法,而@dynamic仅仅是告诉编译器这两个方法在运行期会有的,无需产生警告(让编译器不要创建该属性的getter和setter方法)。
使用场景:
1、同时重写了 setter 和 getter 时
2、重写了只读属性的 getter 时
3、使用了 @dynamic 时
4、在 @protocol 中定义的所有属性
5、在 category 中定义的所有属性
6、重载的属性延伸问题1:@protocol 和 category 中如何使用 @property?
在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的, 是希望遵守我协议的对象能实现该属性 category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要 给 category 增加属性的实现,需要借助于运行时的两个函数: objc_setAssociatedObject 、objc_getAssociatedObject
延伸问题2: weak和 assign 修饰对象,assign为什么会导致野指针错误,而weak不会?
weak 用于指针变量,比assign多了一个功能,当对象消失后自动把指针变成nil,由于消息发送给空对象表示无操作,这样有效的避免了崩溃(野指针),为了解决原类型与循环引用问题
或者这么说下weak和 assign的区别:weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性 设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所 指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对 “纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。 assigin 用于非 OC 对象,而 weak 必须用于 OC 对象
二、深拷贝和浅拷贝
这里分享一篇写的非常不错的博客:https://blog.csdn.net/Mazy_ma/article/details/51899397
自我总结:
延伸问题:在声明NSString属性时,到底是选择strong还是copy?
答案:
https://blog.csdn.net/qq_18425655/article/details/51373611
但是文中的一句话:“所有系统容器类的 copy 和 mutableCopy 都是浅拷贝;其次,拷贝是针对容器类而言的,字符串不是容器类,何谈拷贝?”是错误的,不要相信这句话即可
自我总结: 一般不可变的用copy修饰,可变的用strong修饰。一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题,保证安全性。NSArray也是同理。
三、OC中load,initialize的区别
1.对于load方法,只要文件被引用就会被调用。load方法调用顺序是父类的load方法优先调用于子类的load方法,而本类的load方法优先于category调用。
调用顺序:类-->分类-->子类-->分类
2.文件被引用并不代表initialize就会被调用,只有类或者子类中第一次有函数调用时,都会调用initialize。initialize是线程安全的,我们不能在initialize方法中加锁,这有可能导致死锁。我们也不应该在函数中实现复杂的代码。initialize只会被调用一次。
调用顺序:类-->子类 (注释:分类会覆盖自己类)
3.+load和+initialize共同点:在不考虑开发者主动使用的情况下,系统最多会调用一次如果父类和子类都被调用,父类的调用一定在子类之前这两个方法不适合做复杂的操作,应该是足够简单在使用时都不要过重地依赖于这两个方法,除非真正必要。在使用时一定要注意防止死锁!都不需要调用[super load]、[super initialize]
+load和+initialize不同点:load方法没有自动释放池,如果做数据处理,需要释放内存,则开发者得自己添加autoreleasepool来管理内存的释放; initialize与load不同,即使子类不实现initialize方法,也会把父类的实现继承过来调用一遍。注意的是在此之前,父类的方法已经被执行过一次了,同样不需要super调用。扩展延伸:iOS中的runtime的交换方法为什么一般都写在load方法,而不是在initialize方法?
1、父类实现了load方法,里面用了runtime交换方法的话 但是子类如果不实现load方法也不会调用父类的;
2、父类实现了initialize方法,里面用了runtime交换方法的话 但是子类如果不实现initialize方法就会调用父类的,而且分类的initialize会覆盖本类的initialize方法,导致本类的initialize的函数不调用
你真正的了解OC对象的本质和OC的分类吗?
思考:我们知道我们重写OC的分类的方法,如果该有同样的方法就不会再调用或者说覆盖了该类的实现方法,但是重写OC的分类load的方法,也会调用该类的load方法。(为什么会这样呢)
想了解这些,就必须知道OC对象的本质。 我们平时编写的Objective-C代码,底层实现其实都是C\C++代码,通过我们编写的代码转换成C++,我们可以查看到OC的底层实现。具体如下:
Objective-C的面向对象都是基于C\C++的数据结构实现的,Objective-C的对象、类主要是基于C\C++的什么数据结构实现的?答案是:结构体
将Objective-C代码转换为C\C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
如果需要链接其他框架,使用-framework参数。比如-framework UIKit
知道答案之前,我们先来看一下对象和对象分类的底层实现。
答案:类其实是一个结构体,它拥有2个属性数组保存着这个类所有的方法(分别是类方法和对象方法),而在分类中写的的方法会插入到类这个属性数组最前面,而调用方法是通过消息机制,也就是msg_send,运行时动态从2个数组(数组有顺序的)中从开头到结尾遍历寻找方法,所以会优先响应分类的方法,找到方法就不在寻找下面的方法了。而load方法的调用是通过地址去调用的(Method_load),直接通过地址去调用方法(效率会比较高),所以分类和该类的方法都会调用load。以上结论是通过查看OC的底层实现得出来的结果。(有兴趣的可以自己去验证下,查看RunTime源码和将自己写的代码转化成C++去看)扩展:isa指针知识
需要了解更多内容,OC对象的本质和OC方法的调用问题,请私信作者
四、KVO&KVC
什么事KVO和KVC?
答:KVC(Key-Value-Coding):它是一种可以直接通过字符串的名字(key)来访问类属性(实例 变量)的机制。而不是通过调用 setter、getter 方法访问。 KVO: (Key-Value-Observing):键值观察机制,当指定的对象的属性被修改后,则对象 就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO 就会自动通知 相应的观察者了。
底层实现:
- KVC:
KVC 运用了一个 isa-swizzling 技术。isa-swizzling 就是类型混合指针机制。KVC 主要通 过 isa-swizzling,来实现其内部查找定位的。isa 指针,如其名称所指,(就是 is a kind of 的意思),指向维护分发表的对象的类。该分发表实际上包含了指向实现类中的方法的指针,和其它数据。
- KVO:
当某个类的对象第一次被观察时,系统就会在运行时动态地创建该类的一个派生类,在这个派生类中重写原类中被观察属性的setter方法,派生类在被重写的setter方法实现真正的通知机制(Person->NSKVONotifying_Person). 派生类重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter的调用就会调用重写的setter,从而激活键值通知机制。此外,派生类还重写了dealloc 方法来释放资源。
KVC调用流程:
当通过KVC调用对象时,比如:[self valueForKey:@”someKey”]时,程序会自动试图通过几种不同的方式解析这个调用.
KVC的查找顺序的查找顺序:
1、检查是否存在-
、-is (只针对布尔值有效)或者-get 的访问器方法,如果有 可能,就是用这些方法返回值; 检查是否存在名为-set :的方法,并使用它做设置值。对于 -get 和 -set :方法, 将大写Key字符串的第一个字母,并与Cocoa的方法命名保持一致; 2、如果上述方法不可用,则检查名为-_
、-_is (只针对布尔值有效)、-_get 和-_set :方法; 3、如果没有找到访问器方法,可以尝试直接访问实例变量。实例变量可以是名为:
或 _ ; 4、如果仍未找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。这些方 法的默认实现都是抛出异常,我们可以根据需要重写它们。
KVC使用场景
- 1、调用私有变量
- 2、修改文件的只读属性
- 3、数组求和求平均值
kVO使用场景
- 1、实现上下拉刷新控件 contentoffset
- 2、webview混合排版 contentsize
- 3、监听模型属性实时更新UI
五、autorelease池的使用
- 1、工厂方法为什么不释放对象
很多类方法为了在代码块结束时引用的对象不会因无强引用而被释放内存采用自动释放的方式, 当其最近的自动释放池释放时该对象才会释放.- 2、RC下autorelease的使用场景
ARC中手动添加autoreleasepool可用于前释放使用自动释放策略的对象,防止大量自动释放 的对象堆积造成内存峰值过高.- 3、自动释放池如何工作
自动释放池时栈结构,每个线程的runloop运行时都会自动创建自动释放池,程序员可以代码手 动创建自动释放池,自动释放的对象会被添加到最近的(栈顶)自动释放池中,系统自动创建的自 动释放池在每个运行循环结束时销毁释放池并给池中所有对象发release消息,手动创建释放池 在所在代码块结束时销毁释放池并发消息统一release
六、简要说明一下 App 的启动过程,main 文件说起,main 函数中有什么函数? 作用是什么?
打开程序——->执行 main 函数———>UIAPPlicationMain 函数——->初始化 UIAPPlicationMain 函数(设置代理,开启事件循环)———>监听系统事件—->程序结束
先执行 main 函数,main 内部会调用 UIApplicationMain 函数UIApplicationMain 函数作用:
- 1、根据传入的第三个参数创建 UIApplication 对象或它的子类对象。如果该参数为 nil,直接 使用该 UIApplication 来创建。(该参数只能传入UIApplication 或者是它的子类)
- 2、根据传入的第四个参数创建 AppDelegate 对象,并将该对象赋值给第 1 步创建的 UIApplication 对象的 delegate 属性。
- 3、开启一个事件循环,循环监控应用程序发生的事件。每监听到对应的系统事件时,就会通知 AppDelegate。
3分2种情况:
- 3.1、有stroyboard-->应用程创建一个UIWindow对象(继承自UIView),并设置为AppDelegate的window属性。-->加载Info.plist文件,读取最主要storyboard文件的名称。-->加载最主要的storyboard文件,创建白色箭头所指的控制器对象。并且设置控制器为UIWindow的rootViewController属性(根控制器)。-->展示UIWindow,展示之前会将添加rootViewController的view到UIWindow上面(在这一步才会创建控制器的view),其内部会执行该行代码:[window addSubview: window.rootViewControler.view];
- 3.2没有stroyboard-->首先会调用delegate对象的application:didFinishLaunchingWithOptions:方法。-->在application:didFinishLaunchingWithOptions:方法中需要主动创建UIWindow对象。并设置为AppDelegate的window属性。-->主动创建一个UIViewController对象,并赋值给window的rootViewController属性。-->调用window的makeKeyAndVisible方法显示主窗口。
main 函数作用:
- 1、创建 UIApplication 对象
- 2、创建(AppDelegate)应用程序代理
- 3、开启事件循环,包括应用程序的循环运行,并开始处理用户事件。
七、消息转发机制
请参照图片品味以下步骤:
- 第一步:通过resolveInstanceMethod:方法决定是否动态添加方法。如果返回Yes则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回No,则进入下一步;
- 第二步:这步会进入forwardingTargetForSelector:方法,用于指定备选对象响应这个selector,不能指定为self。如果返回某个对象则会调用对象的方法,结束。如果返回nil,则进入第三步;
- 第三步:这步我们要通过methodSignatureForSelector:方法签名,如果返回nil,则消息无法处理。如果返回methodSignature,则进入下一步;
- 第四步:这步调用forwardInvocation:方法,我们可以通过NSInvocation对象做很多处理,比如修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果失败,则进入doesNotRecognizeSelector方法,若我们没有实现这个方法,那么就会crash。
示例程序:
Person.h
#import
@interface Person : NSObject @property (nonatomic, copy) NSString *name; - (void)sing; @end Person.m
#import "People.h" #if TARGET_IPHONE_SIMULATOR #import
#else #import #import #endif @implementation Person + (BOOL)resolveInstanceMethod:(SEL)sel { // 我们没有给People类声明sing方法,我们这里动态添加方法 if ([NSStringFromSelector(sel) isEqualToString:@"sing"]) { class_addMethod(self, sel, (IMP)otherSing, "v@:"); return YES; } return [super resolveInstanceMethod:sel]; } void otherSing(id self, SEL cmd) { NSLog(@"%@ 唱歌啦!",((Person *)self).name); } 在main.m中运行以下代码:
#import
#import "AppDelegate.h" #import "People.h" int main(int argc, char * argv[]) { @autoreleasepool { Person *mgPerson = [[Person alloc] init]; mgPerson.name = @"MG明明就是你"; [mgPerson sing]; return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
我们没有提供MG明明就是你sing的方法实现,因此在调用此方法的时候,会调用resolveInstanceMethod方法,我们动态添加了方法。我们也可以返回No,继续向下传递.
我们可以参考一下YYWeakProxy源码
@interface YYWeakProxy : NSProxy
/**
The proxy target.
*/
@property (nullable, nonatomic, weak, readonly) id target;
/**
Creates a new weak proxy for target.
@param target Target object.
@return A new proxy object.
*/
- (instancetype)initWithTarget:(id)target;
/**
Creates a new weak proxy for target.
@param target Target object.
@return A new proxy object.
*/
+ (instancetype)proxyWithTarget:(id)target;
@end
#import "YYWeakProxy.h"
@implementation YYWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[YYWeakProxy alloc] initWithTarget:target];
}
// 与forwardInvocation的区别,只能返回一个对象去处理
- (id)forwardingTargetForSelector:(SEL)selector {
return _target; // 返回去响应方法的对象
}
// 与forwardingTargetForSelector的区别,可以响应多个对象,消息可转发给多个对象
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
// _target被释放时,会调用这个方法,如果不实现这个方法,就会崩溃
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end
扫一扫,关注我
-
轻轻点击,关注我
-
轻轻点击,在慕课网关注我
-
关注我
-
轻轻点击,关注我微博
-
浏览我的GitHub
-
扫一扫,关注我的公众号