美文网首页
iOS开发(解惑-01)

iOS开发(解惑-01)

作者: Xcode8 | 来源:发表于2018-10-19 18:08 被阅读71次
一.原生和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值

ISA_MASK.png

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查找到实例对象的父类是查找;

isa、superClass调用过程.png

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);

相关文章

  • iOS开发(解惑-01)

    一.原生和html5的主要区别本质分析: http://blog.csdn.net/u014326381/arti...

  • iOS开发 (解惑-02)

    一、block 1)iOS开发的内存分配;2)block的变量捕获机制:为了保证在block的内部可以正常访问外部...

  • C语言第一部分

    本篇博客的主要知识点是: 01.ios开发概述 02.什么是 ios 03.什么是 ios 开发 04.为什么选择...

  • iOS 中的静态库与动态库

    如果你经常困惑 iOS 开发中的静态库和动态库的作用与区别, 那么这篇文章可以为你解惑 静态库 (Static L...

  • iOS开发优秀博客和软件推荐

    iOSBlogAndTools iOS开发优秀博客和软件推荐 iOS开发中文博客 iOS开发工具 iOS开发网站 ...

  • iOS 开发小记-01

    最近又开始写不少业务代码了,有些小知识点小坑,用这个系列记录一下。iOS 开发小记-01iOS 开发小记-02 1...

  • iOS 开发小记-02

    最近又开始写不少业务代码了,有些小知识点小坑,用这个系列记录一下。iOS 开发小记-01iOS 开发小记-02 1...

  • iOS开发之UIButton的图片显示解惑

    前言: 写这篇文章来给大家分享一下我了解的关于UIButton的图片显示知识点和我遇到的坑及解决方案,帮助遇到同样...

  • iOS开发日常01

    CGAffineTransformMakeRotation 实现以初始位置(center坐标为圆心)为基准,将坐标...

  • 收录 : iOS支付开发

    iOS 银联支付开发流程iOS 微信支付开发流程iOS 支付宝支付开发流程iOS Apple Pay开发流程App...

网友评论

      本文标题:iOS开发(解惑-01)

      本文链接:https://www.haomeiwen.com/subject/fwchqftx.html