美文网首页
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)

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