一.原生和html5的主要区别本质分析:
http://blog.csdn.net/u014326381/article/details/47787993
二.iOS底层原理开发
2-1、越狱环境:apple手机支持ARM64架构是从IPhone5s开始,iPad Air、iPad mini2开始支持ARM64架构;(越狱JailBreak:完美越狱9.0、9.0.1、9.0.2、9.1的5s、6、6plus、6s,参考http://jailbreak.25pp.com)
2-2、iOS开发源码地址---https://opensource.apple.com/tarballs/
将OC代码转化成C++、中间代码命令:
1、xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Eat.m;(报错clang: error: no such file or directory: 'Person+Eat.m' clang: error: no input files
);解决:cd是文件不是目录
,改成目录即可
2.指定代码转化成c++代码:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person.m -o personMain.cpp
3.支持arc:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp
4、将OC代码转化成中间代码:clang -emit-llvm -S Person.m
三、iOS面试题
3-1(面试题)、一个NSObject占用的内存?
解析:NSObject最终转化成c++、c语言内部,最终转化struct结构体存储(原因是存储内部存在很多不同类型的数据)NSObject转化的对象,转化成的struct的内部结构中存在isa,isa是一个指向class的指针,所以isa指针占用8个字节(64位,32位占4个字节);
class_getInstanceSize实例对象最少需要的内存空间、malloc_size实际分配的内存大小;
基本数据类型、oc对象占用字节:
int:4个字节
Bool :1个字节
char:1个字节
float:4字节
double:8字节
NSString:8字节
NSInteger:8字节
typedef struct objc_class *class;
@interface NSObject{
Class isa;
}
验证: class_getInstanceSize(效果为8字节:获取指向实例对象所指向成员变量占用的内存)和malloc_size(效果为16字节:获取指向的实例对象所指向内存的大小,因为源码n内部做了判断,size < 16,size = 16)方法可以验证结果;
class_getInstanceSize([NSObject class]) 依赖 #import <objc/runtime.h>方法可以
malloc_size 依赖 #import <malloc/malloc.h>
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%zd",class_getInstanceSize([NSObject class]));
NSLog(@"%zd",malloc_size((__bridge const void *)(obj)));
3-1-2、一个Student对象,有三个以下属性,占用多少内存
@property (nonatomic, assign) int a;
@property (nonatomic, assign) int b;
@property (nonatomic, assign) int c;
Student对象最终的内存结构
struct Student_IMPL{//指向对象成员变量占用的内存
struct Person_IMPL Person_IVARS;//指向父类--内存8字节
Int a; //内存4字节
Int b; //内存4字节
Int c; //内存4字节
}
struct Student_IMPL{//指向对象所占用的内存
struct Person_IMPL Person_IVARS;//父类--内存16字节
Int a; //内存4字节
Int b; //内存4字节
Int c; //内存4字节
}
结果分析:Student最终占用的内存为20,内存存在对齐:最少为最大成员的倍数,所以最终class_getInstanceSize结果为24个字节;malloc_size内存为16(最大成员变量为16所以倍数)的倍数,所以是32字节;
Student *stu = [[Student alloc] init];
stu.a = 10;
stu.b = 20;
stu.c = 30;
NSLog(@"%zd",class_getInstanceSize([Student class]));//24
NSLog(@"%zd",malloc_size((__bridge const void *)(stu)));//32
3-2(面试题)、isa指向哪里?OC对象(实例对象、类对象(内存独一份)、元类对象(内存独一份)存放的信息)?
解析:OC对象分为instance实例对象
(里面放着isa、对象的成员变量信息)、class类对象
(isa、superClass、成员变量、对象属性、协议方法、对象方法......)、meta_class元类对象
(isa、superClass、类对象方法......);实例对象的isa指向类对象,类对象的isa指向元类对象,元类对象的isa指向基类对象;
从64位开始,isa不是直接指向类对象、元类对象,需要&&上ISA_MASK值
3-2-1、获取类对象、原类对象的方法:
//1、获取类对象方法--注意([[NSObject class] class]无论调用多少次,返回都是类对象)
NSObject *obj = [[NSObject alloc] init];
Class objectClass1 = [obj class];
Class objectClass2 = object_getClass(obj);
Class objectClass3 = [NSObject class];
//2、获取原类对象方法
Class objectClass2 = object_getClass(objectClass1);
object_getClass和objc_getClass区别
object_getClass
(传instance返回class,传class返回meta-class);
objc_getClass
(传入字符串类名,返回对应的类对象)
isa的作用:比如在实例对象、类对象调用方法的时候---调用方法的过程实质是通过running time转化成消息转发,在此过程中(当实例对象调用方法的时候,因为方法是放在类对象里面,所以首先通过实例对象的isa指针找到类对象,并在类对象方法列表里面找到调用的方法);调用类方法的过程类似于实例对象调用实例方法过程;
superClass的作用:当实例对象调用一个方法,在当前的类对象方法列表里面并没有这个方法,此时就会通过superClass查找到实例对象的父类是查找;
3-3(面试题)、KVO的实现本质?怎么手动触发KVO?修改对象的成员变量会触发KVC?
解析:1)KVO本质
添加观察者之后,实例对象的isa指针发生了变化(实例对象在runtime的运行期间,自动生成了实例对象的子类NSKVONotifying_CWPerson);2)手动调用[self willChangeValueForKey:@"age"];[self didChangeValueForKey:@"age"];
即可手动触发KVO;3)修改对象的成员变量不会触发KVO
,因为成员变量内部没有set方法实现;
KVO的内部实际调用流程:
#import "NSKVONotifying_CWPerson.h"
@implementation NSKVONotifying_CWPerson
- (void)setAge:(int)age
{
//KVO调用监听的本质是:是因为生成的原CWPerson的子类NSKVONotifying_CWPerson,调用了setAge:方法
_NSSetLongLongValueAndNotify();
}
//模仿Foundation`_NSSetLongLongValueAndNotify 框架内部的实现
void _NSSetLongLongValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];//去父类改变属性值
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key
{
[observer observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
@end
验证新生成类调用Foundation _NSSetLongLongValueAndNotify
self.person1 = [[CWPerson alloc] init];
self.person1.name = @"cjw";
self.person2 = [[CWPerson alloc] init];
self.person2.name = @"cjw2";
NSLog(@"添加监听之前方法地址----%p %p",
[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
NSKeyValueObservingOptions opition = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"name" options:opition context:@"ceshi"];
NSLog(@"添加监听之后方法地址----%p %p",
[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
KVO方法调用验证.png
验证新生成NSKVONotifying_CWPerson里面的方法,多了class(隐藏KVO的实现本质)、dealloc(销毁对象时刻处理方法)、_isKVOA(是否KVO)这些方法
- (void)printMethodClassName:(Class)class
{
unsigned int count;
Method *methodList = class_copyMethodList(class, &count);
for (int i = 0; i < count; i ++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
NSLog(@"%@",methodName);
}
free(methodList);
}
3-4(面试题)、通过KVC赋值成员变量是否会触发KVO监听?KVC赋值和取值的过程?
验证是否会触发KVO监听、KVC赋值、取值过程:
监听对象 observe.h文件
#import <Foundation/Foundation.h>
@interface Observe : NSObject
@end
observe.m文件
#import "Observe.h"
@implementation Observe
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"observeValueForKeyPath -- %@",change);
}
@end
测试KVC赋值对象testSetValueModel.h文件
#import <Foundation/Foundation.h>
@interface testSetValueModel : NSObject{
//解析:如果没有setAge,_setAge这个方法的话,看accessInstanceVariablesDirectly返回值为yes的话,就setValue:设置一下属性(优先顺序:_age,_isAge,age,isAge),还是找不到抛除异常
@public
int _age;
int _isAge;
int age;
int isAge;
}
@end
测试KVC赋值对象testSetValueModel.m文件
#import "testSetValueModel.h"
@implementation testSetValueModel
//不用设置age的属性:(因为设置age的属性的话、系统内部会自动生成setAge:的方法),方法的优先调用setAge,_setAge
//-(void)setAge:(int)age{
// NSLog(@"setAge:");
//}
//-(void)_setAge:(int)age{
// NSLog(@"_setAge:");
//}
+(BOOL) accessInstanceVariablesDirectly{
return YES;
}
- (void)willChangeValueForKey:(NSString *)key{
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey:");
}
- (void)didChangeValueForKey:(NSString *)key{
NSLog(@"didChangeValueForKey--begin:");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey--end:");
}
@end
KVC的取值过程testValueForKeyModel.h文件
#import <Foundation/Foundation.h>
//取值model过程解析:如果没有getAge,age,isAge,_age这个方法的话,看accessInstanceVariablesDirectly返回值为yes的话,就valueForKey:取值成员变量(优先顺序:_age,_isAge,age,isAge),还是找不到抛除异常
@interface testValueForKeyModel : NSObject{
@public
// int _age;
// int _isAge;
// int age;
int isAge;
}
@end
KVC的取值过程testValueForKeyModel.m文件
#import "testValueForKeyModel.h"
@implementation testValueForKeyModel
//- (int)getAge{
// return 20;
//}
//- (int)age{
// return 21;
//}
//- (int)isAge{
// return 22;
//}
//- (int)_age{
// return 23;
//}
+(BOOL)accessInstanceVariablesDirectly{
return YES;
}
@end
调用
testSetValueModel *setV = [[testSetValueModel alloc] init];
Observe *obs2 = [[Observe alloc] init];
[setV addObserver:obs2 forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
[setV setValue:@100 forKey:@"age"];
[setV removeObserver:obs2 forKeyPath:@"age"];
执行结果如图所示:
KVC的调用流程.png
总结面试题:
1)通过KVC赋值成员变量,会触发KVO监听,是因为在KVC赋值的过程中调用了willChangeValueForKey:,didChangeValueForKey:(在此方法中触发了KVO观察者模式);
2)KVC的赋值过程:通过KVC给成员变量赋值,查找是否存在这个成员变量(优先顺序setValue、_setKey
,如果没有的话,接着accessInstanceVariablesDirectly
当前类中是否允许查看实例变量的值为yes的话,顺序_key,_isKey,key,isKey
),还是找不到抛除异常
);
3) KVC的取值过程:查看是否存在这个value的成员变量,优先顺序(getKey、key、isKey、_key
),如果没有的话,接着accessInstanceVariablesDirectly
当前类中是否允许查看实例变量的值为yes的话,顺序_key,_isKey,key,isKey
),还是找不到抛除异常
);
3-5(面试题)、
1)category和class extention的区别?(category运行时刻加载,extention编译时刻加载;extention可以为类添加属性和方,category可以为类添加方法,不通过特殊方法不能为类添加属性)
2)category的实现原理?
3)category的load方法调用时刻,能否继承?
4)load和initialize区别,调用顺序?
5)如何给category添加成员变量?
分类的OC代码:
Person+Eat.h文件
#import "Person.h"
@interface Person (Eat)<NSCopying>
- (void)eat;
- (void)eat2;
+ (void)classEat;
+ (void)classEat2;
@property(nonatomic,assign)NSInteger age;
@property(nonatomic,copy)NSString *name;
@end
Person+Eat.m文件
#import "Person+Eat.h"
@implementation Person (Eat)
- (void)eat
{
NSLog(@"instance - eat");
}
- (void)eat2
{
NSLog(@"instance - eat2");
}
+ (void)classEat
{
NSLog(@"class - eat");
}
+ (void)classEat2
{
NSLog(@"class - eat2");
}
@end
分类的代码C++底层实现:
分类的结构
struct _category_t {
const char *name;//类名
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
分类的赋值(源码赋值报错,贴上图片)
分类的赋值代码.png
分类的Runtime(objc4源码---分类---objc-runtime-new.mm) 实现核心:
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
//1、获取存放分类方法的二维数组
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
//2、获取存放分类属性方法的二维数组
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
//3、获取存放分类协议的二维数组
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
//4、获取最后参与编译分类的方法(i--)
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//5、取出分类重新存放到新的数组里面
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//属性
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
//协议
protolists[protocount++] = protolist;
}
}
//6、取出存放struct class_rw_t结构体方法列表
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
//7.重新将分类的方法和类方法组合
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
//属性
rw->properties.attachLists(proplists, propcount);
free(proplists);
//协议
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
objc4源码---分类---attachLists 内部实现
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
//8、array()存放着类的方法
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
//9、开辟新空间,类加分类的总数量
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//10、将类的方法向后移动---分类方法个数个空间(array()->lists + addedCount)
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//11、将存放分类的方法移到之前存放类方法的位置
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
//以下代码忽略
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
类load源码实现:
objc4源码调用流程:
1)(objc-os.mm):_objc_init(对象的初始化方法)、 load_images(加载对象的方法),
2)prepare_load_methods(准备加载方法): schedule_class_load、add_class_to_loadable_list、 add_category_to_loadable_list,
3)call_load_methods:call_class_loads、call_category_loads;
prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
//1、获取objc的不是lazy加载的方法
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
//2、预先计划加载class的load方法
schedule_class_load(remapClass(classlist[i]));
}
//加载分类分方法:根据编译的顺序加载
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
//4、加载分类分load的方法
add_category_to_loadable_list(cat);
}
}
schedule_class_load
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
//3、自己调用自己,知道把所有的父类load方法加载完毕
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
call_load_methods
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
//5、调用类的方法
call_class_loads();
}
// Call category +loads ONCE
//6、调用分类的方法
more_categories = call_category_loads();
// Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
load总结:
1)定义类的方法load、分类的load,注意load的方法是不会覆盖掉类的load方法
(原因call_load_methods中:call_class_loads(),call_category_loads()),
2)加载的顺序是先加载父类的load方法
(原因:schedule_class_load中--schedule_class_load(cls->superclass)),再类的load方法(不同类之间的顺序根据编译),再加载分类的load方法(各分类之间顺序是根据编译);
3)load是runtime运行时刻加载类、分类
load方法;
initialize源码实现class_getInstanceMethod:
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
#1、查找方法
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
return _class_getMethod(cls, sel);
}
lookUpImpOrNil:
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
#2、查找方法
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
lookUpImpOrForward核心代码实现:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
......
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
......
}
_class_initialize:()
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
#3、实现父类的initialized方法
_class_initialize(supercls);
}
......
#if __OBJC2__
@try
#endif
{
#4、实现当前类的initialized方法
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
......
}
initialize方法总结:
1)initialize方法在类第一次接受到消息
objc_msgSend()时候调用;
2)initialize方法从源码看:在一个有父类的方法第一次
发送消息时刻,会先调用父类的initialize方法
;
3)initialize方法:(Person继承NSObject、Student1继承Person、Student2继承Person,在Person的方法或者分类实现了initialize,Student1、Student2类 分类都没有实现initialize方法的时候)在此情况下Student1、Student2第一次接受到消息,会直接调用Person里面的initialize方法的实现;所以说类的initialize 方法并不一定只调用一次
;
category添加成员变量总结:
1)category的底层结构没有存放成员变量的数组,所以不能直接为分类添加成员变量;
2)在类中声明属性:系统会默认生成成员变量、set、get方法的声明和实现;category:在分类内部声明属性,只会生成属性的set、get方法的声明;
3)为分类添加成员变量:set方法objc_setAssociatedObject(self, @selector(nameRun), nameRun, OBJC_ASSOCIATION_COPY)
;
get方法objc_getAssociatedObject(self,_cmd)
;
网友评论