bigbro

秘密基地,记录成长

秘密基地


hello, 我是王岩,人称大师兄(bigbro),一名iOS客户端开发小白,正在努力成为大神的路上

面试经历总结

总结2018/07/16–2018/07/17社招面试(雪球、58金融、四达时代、洋钱罐)的经历总结,对知识进行查缺补漏

记录于此:任重道远,仍需努力,披荆斩棘,砥砺而行

开发语言(Objective-C、Swift)相关

  • Block的类型
    • 闭包的书写方式
    • block的数据结构
        struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);//对于上下文捕获变量的copy和释放
        void (*dispose)(void *);
        };
        struct Block_layout {
        void *isa;
        int flags;
        int reserved;
        void (*invoke)(void *, ...);//指向调用函数的指针
        struct Block_descriptor *descriptor;
        };
      
    • isa指向class对象;FuncPtr指向调用函数的指针;拥有isa指针,说明block也是一个对象
    • block就是一个里面存储了指向函数体中包含定义block时的代码块的函数指针,以及block外部上下文变量等信息的结构体
    • block类型分为:_NSConcreteGlobalBlock(全局)、_NSConcreteStackBlock(栈)、_NSConcreteMallocBlock(堆)
      void (^globalBlock)() = ^{
    
      };
    
      int main(int argc, const char * argv[]) {
          @autoreleasepool {
              void (^stackBlock1)() = ^{
    
              };
          }
          return 0;
      }   
    
    • globalBlock的isa指向了_NSConcreteGlobalBlock,即在全局区域创建,编译时具体的代码就已经确定在上图中的代码段中了,block变量存储在全局数据存储区;stackBlock的isa指向了_NSConcreteStackBlock,即在栈区创建。即全局的block存储在全局数据存储区域;栈block在栈区创建
    • 堆中的block无法直接创建,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。block的拷贝最终都会调用_Block_copy_internal函数
      static void *_Block_copy_internal(const void *arg, const int flags) {
          struct Block_layout *aBlock;
          ...
          aBlock = (struct Block_layout *)arg;
          ...
          // Its a stack block.  Make a copy.
          if (!isGC) {
              // 申请block的堆内存
              struct Block_layout *result = malloc(aBlock->descriptor->size);
              if (!result) return (void *)0;
              // 拷贝栈中block到刚申请的堆内存中
              memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
              // reset refcount
              result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
              result->flags |= BLOCK_NEEDS_FREE | 1;
              // 改变isa指向_NSConcreteMallocBlock,即堆block类型
              result->isa = _NSConcreteMallocBlock;
              if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
                  //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
                  (*aBlock->descriptor->copy)(result, aBlock); // do fixup
              }
              return result;
          }
          else {
              ...
          }
      }
    
    • 不管是用什么形式访问实例变量,最终都会转换成self+变量内存偏移的形式
    • ARC会自动帮strong类型且捕获外部变量的block进行copy,所以在定义block类型的属性时也可以使用strong,不一定使用copy;当block类型为strong,但是创建时没有捕获外部变量,block最终会变成__NSGlobalBlock__类型(这里可能因为block中的代码没有捕获外部变量,所以不需要在栈中开辟变量,也就是说,在编译时,这个block的所有内容已经在代码段中生成了,所以就把block的类型转换为全局类型)
    • block对于使用__block修饰的自动变量,会将其包装为对象,使其移动到堆上;对于单纯的局部自动变量只是值传递,只是保存了捕获时的取值,故不会受到外界变量的影响,也不能影响外界的变量
  • Runtime
    • OC语言在编译期都会被编译为C语言的Runtime代码,二进制执行过程中执行的都是C语言代码。而OC的类本质上都是结构体,在编译时都会以结构体的形式被编译到二进制中。Runtime是一套由C、C++、汇编实现的API,所有的方法调用都叫做发送消息
    • 对象模型
    • 每个对象都是一个objc_object的结构体,在结构体中有一个isa指针,该指针指向自己所属的类,由Runtime负责创建对象
    • IMP 在Runtime中IMP本质上就是一个函数指针,其定义如下。在IMP中有两个默认的参数id和SEL,id也就是方法中的self,这和objc_msgSend()函数传递的参数一样
    • Method 用来表示方法,其包含SEL和IMP;在Xcode进行编译的时候,只会将Xcode的Compile Sources中.m声明的方法编译到Method List,而.h文件中声明的方法对Method List没有影响
    • Property 在Runtime中定义了属性的结构体,用来表示对象中定义的属性;可以通过class_copyPropertyList或者class_copyIvarList获取对象的公共属性列表,甚至是私有属性列表。并且可以通过KVC或者Runtime(class_getInstanceVariable/object_setIvar/object_getIvar)访问和修改私有属性
    • OC中不存在真正的私有属性和私有方法
    • 内存布局
      • 创建实例对象时,会根据其对应的Class分配内存,内存构成是ivars+isa_t。并且实例变量不只包含当前Class的ivars,也会包含其继承链中的ivars。ivars的内存布局在编译时就已经决定,运行时需要根据ivars内存布局创建对象,**所以Runtime不能动态修改ivars,会破坏已有内存布局。** 内存布局
    • ivar的读写(访问)
      • 实例变量的isa_t指针会指向其所属的类,对象中并不会包含method、protocol、property、ivar等信息,这些信息在编译时都保存在只读结构体class_ro_t中。在class_ro_t中ivars是const只读的,在image load时copy到class_rw_t中时,是不会copy ivars的,并且class_rw_t中并没有定义ivars的字段。 在访问某个成员变量时,直接通过isa_t找到对应的objc_class,并通过其class_ro_t的ivar list做地址偏移,查找对应的对象内存。正是由于这种方式,所以对象的内存地址是固定不可改变的。
    • 方法调用
      • 实例调用方法:objc_msgSend()发起调用,传入self和SEL –> 通过isa在类的内部查找方法列表对应的IMP; 如果调用的方法时涉及到当前对象的成员变量的访问,这时候就是在objc_msgSend()内部,通过类的ivar list判断地址偏移,取出ivar并传入调用的IMP中的。
      • super调用父类方法:调用objc_msgSendSuper()函数实现。**需要注意的是,调用objc_msgSendSuper函数时传入的对象,也是当前实例变量,所以是在向自己发送父类的消息**
    • RunTime中的结构体
      • 常见结构体类型定义
        typedef struct objc_class *Class;
        typedef struct objc_object *id;
      
        typedef struct method_t *Method;
        typedef struct ivar_t *Ivar;
        typedef struct category_t *Category;
        typedef struct property_t *objc_property_t;
      
      • OC代码都是被首先编译问C语言文件然后生成二进制文件去执行。在OC中每个对象都是一个结构体(objc_object),结构体中都包含一个isa的成员变量,其位于成员变量的第一位。
        struct objc_object {
            private:
                isa_t isa;
        };
      

      OC的对象都会在编译过程中翻译为C语言的结构体:

        NSArray *array = [[NSArray alloc] init];
        =======================================
        // @class NSArray;
        #ifndef _REWRITER_typedef_NSArray
        #define _REWRITER_typedef_NSArray
        typedef struct objc_object NSArray;
        typedef struct {} _objc_exc_NSArray;
        #endif
        NSArray *array = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("alloc")), sel_registerName("init"));
      
      • OC中的类和元类也是一样,都是结构体构成的。由于类的结构体定义继承自objc_object,所以其也是一个对象,并且具有对象的isa特征; 对象模型
      • instaceOfType(objc_object) –(isa)–> isa_t –(class)–> 元类(objc_class)–superClass(isa)–> 根元类
        • isa_t 类,描述对象相关的数据,cls字段指向元类
        union isa_t 
        {
            isa_t() { }
            isa_t(uintptr_t value) : bits(value) { }
      
            Class cls;
            uintptr_t bits;
      
        # if __arm64__
        #   define ISA_MASK        0x0000000ffffffff8ULL
        #   define ISA_MAGIC_MASK  0x000003f000000001ULL
        #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
            struct {
                uintptr_t nonpointer        : 1; // 是32位还是64位
                uintptr_t has_assoc         : 1; // 对象是否含有或曾经含有关联引用,如果没有关联引用,可以更快的释放对象
                uintptr_t has_cxx_dtor      : 1; // 表示是否有C++析构函数或OC的析构函数
                uintptr_t shiftcls          : 33; // 对象指向类的内存地址,也就是isa指向的地址
                uintptr_t magic             : 6; // 对象是否初始化完成
                uintptr_t weakly_referenced : 1; // 对象是否被弱引用或曾经被弱引用
                uintptr_t deallocating      : 1; // 对象是否被释放中
                uintptr_t has_sidetable_rc  : 1; // 对象引用计数太大,是否超出存储区域
                uintptr_t extra_rc          : 19; // 对象引用计数
        #       define RC_ONE   (1ULL<<45)
        #       define RC_HALF  (1ULL<<18)
            };
        };
      
      • 元类的数据结构(objc_class)
        struct objc_class : objc_object {
            // Class ISA;
            Class superclass;
            cache_t cache;             
            class_data_bits_t bits;    
      
            class_rw_t *data() { 
                return bits.data();
            }
            void setData(class_rw_t *newData) {
                bits.setData(newData);
            }
            // .....
        }
      
      • bits是objc_class的主角,其内部只定义了一个uintptr_t类型的bits成员变量,存储了class_rw_t的地址。和class_data_bits_t相关的有两个很重要结构体,class_rw_t和class_ro_t,其中都定义着method list、protocol list、property list等关键信息。在编译后class_data_bits_t指向的是一个class_ro_t的地址,这个结构体是不可变的(只读)。在运行时,才会通过realizeClass函数将bits指向class_rw_t。
        struct class_rw_t {
            uint32_t flags;
            uint32_t version;
      
            const class_ro_t *ro;
      
            method_array_t methods;
            property_array_t properties;
            protocol_array_t protocols;
      
            Class firstSubclass;
            Class nextSiblingClass;
      
            char *demangledName;
        };
        struct class_ro_t {
            uint32_t flags;
            uint32_t instanceStart;
            uint32_t instanceSize;
            uint32_t reserved;
      
            const uint8_t * ivarLayout;
                  
            const char * name;
            method_list_t * baseMethodList;
            protocol_list_t * baseProtocols;
            const ivar_list_t * ivars;
      
            const uint8_t * weakIvarLayout;
            property_list_t *baseProperties;
        };
      
      • Tagged Pointer
        • 从iPhone5s开始,iOS设备开始引入了64位处理器,之前的处理器一直都是32位的。但是在64位处理器中,指针长度以及一些变量所占内存都发生了改变,32位一个指针占用4字节,但64位一个指针占用8字节;32位一个long占用4字节,64位一个long占用8字节等,所以在64位上内存占用会多出很多。苹果为了优化这个问题,推出了Tagged Pointer新特性。之前一个指针指向一个地址,而Tagged Pointer中一个指针就代表一个值。Tagged Pointer指针中就存储着对象的值;明显的提升了执行效率并节省了很多内存。在64位处理器下,内存占用减少了将近一半,执行效率也大大提升。由于通过指针来直接表示数值,所以没有了malloc和free的过程,对象的创建和销毁速度提升几十倍
    • 对象的初始化流程
      • alloc + init 和 new 两种调用方式在runtime;在runtime源码中,执行init操作本质上就是直接把self返回,其实如果没有自定义或者覆盖init,调不调用init都是一样的; 初始化对象:初始化isa指针,初始化元类,计算对象实例化时,所有变量所占的内存大小(对象内存大小固定(主要包括isa + ivar【class_ro_t】等)),在编译时就已经确定,在运行时无法改变
      • 在对象销毁时,运行时环境会调用NSObject的dealloc方法执行销毁代码,并不需要我们手动去调用。接着会调用到Runtime内部的objc_object::rootDealloc(C++命名空间)函数。
    • class_addMethod runTime过程中动态的添加方法
      • 在Runtime中动态的替换或者增加方法,本质上都是调用addMethod方法;该方法会返回一个布尔值,用来表明所要添加的方法是否存在
      • 在分类的load方法中进行方法swizzle的步骤
        • 声明原始方法和swizzle方法(在分类中实现了)的SEL和Method
        • 调用class_Method方法,添加原始方法为方法名,swizzle实现为Method的方法
        • 若class_Method调用返回True,说明原始方法不存在并且调价成功,那么调用class_replaceMethod替换方法名为swizzle方法的实现为原始Method的方法
        • 若class_Method调用返回false,说明原始方法已经存在添加失败,那么调用method_exchangeImplementations交换原始和swizzle的方法实现Method
        Class class = [self class];
        SEL originalSelector = @selector(dismissViewControllerAnimated:completion:);
        SEL swizzledSelector = @selector(dfup_dismissViewControllerAnimated:completion:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
      
    • SEL是由objc_selector结构体实现的,但是从现在的源码来看,SEL是一个const char*的常量字符串,只是代表一个名字而已
    • 程序加载过程
      • 在iOS程序中会用到很多系统的动态库,这些动态库都是动态加载的。所有iOS程序共用一套系统动态库,在程序开始运行时才会开始链接动态库。
      • 在应用程序启动后,由dyld(the dynamic link editor)进行程序的初始化操作。大概流程就像下面列出的步骤,其中第3、4、5步会执行多次,在ImageLoader加载新的image进内存后就会执行一次。
        1. 在应用程序启动后,由dyld将应用程序加载到二进制中,并完成一些文件的初始化操作。
        2. Runtime向dyld中注册回调函数。
        3. 通过ImageLoader将所有image加载到内存中。
        4. dyld在image发生改变时,主动调用回调函数。
        5. Runtime接收到dyld的函数回调,开始执行map_images、load_images等操作,并回调+load方法。
        6. 调用main()函数,开始执行业务代码。
      • load
        • load类方法的调用时机比main函数还要靠前。load方法是由系统来调用的,并且在整个程序运行期间,只会调用一次,所以可以在load方法中执行一些只执行一次的操作
        • load方法的调用顺序应该是“父类 -> 子类 -> 分类”的顺序。因为执行加载Class的时机是在Category之前的,而且load子类之前会先load父类,所以是这种顺序
        • 但是若子类没有实现load并不会去掉用父类的
      • initialize
        • 和load方法类似的也有initialize方法,initialize方法也是由Runtime进行调用的,自己不可以直接调用。与load方法不同的是,initialize方法是在第一次调用类所属的方法(实例方法、类方法)时,才会调用initialize方法,而load方法是在main函数之前就全部调用了。所以理论上来说initialize可能永远都不会执行,如果当前类的方法永远不被调用的话。
        • 分类的实现的initialize方法会覆盖本类的实现
        • 如果没有主动的显式的去掉用initialize,initialize只会被调用一次
    • 消息的发送机制
      • 在OC中方法调用是通过Runtime实现的,Runtime进行方法调用本质上是发送消息,通过objc_msgSend()函数进行消息发送
      • 不同类的相同SEL是同一个对象。
      • 当一个对象被创建时,系统会为其分配内存,并完成默认的初始化工作,例如对实例变量进行初始化。对象第一个变量是指向其类对象的指针-isa,isa指针可以访问其类对象,并且通过其类对象拥有访问其所有继承者链中的类
      • 消息发送的流程
        1. 判断当前调用的SEL是否需要忽略
        2. 判断接收消息的对象是否为nil
        3. 从方法的缓存列表中查找,通过cache_getImp函数进行查找,如果找到缓存则直接返回IMP。
        4. 查找当前类的method list,查找是否有对应的SEL,如果有则获取到Method对象,并从Method对象中获取IMP,并返回IMP(这步查找结果是Method对象)。
        5. 如果在当前类中没有找到SEL,则去父类中查找。首先查找cache list,如果缓存中没有则查找method list,并以此类推直到查找到NSObject为止。
        6. 如果在类的继承体系中,始终没有查找到对应的SEL,则进入动态方法解析中。可以在resolveInstanceMethod和resolveClassMethod两个方法中动态添加实现。
        7. 动态消息解析如果没有做出响应,则进入动态消息转发阶段。此时可以在动态消息转发阶段做一些处理,否则就会Crash。
    • Category
      • Category语法很相似的还有Extension,二者的区别在于,Extension在编译期就直接和原类编译在一起,而Category是在运行时动态添加到原类中的
      • 在有多个Category和原类的方法重复定义的时候,原类和所有Category的方法都会存在,并不会被后面的覆盖。假设有一个方法叫做method,Category和原类的方法都会被添加到方法列表中,只是存在的顺序不同;在进行方法调用的时候,会优先遍历Category的方法,并且后面被添加到项目里的Category,会被优先调用
        • 元类中的MethodList的最终顺序**同时与Building Phases的编译列表和是否实现了+load方法有关**
          • 没有实现+load方法的分类方法会排在实现了+load前面
          • 若同为实现或者同为未实现+load方法的分类,则按照Building Phases的编译列表中的顺序排列
      • 在有多个Category和原类方法重名的情况下,怎样在一个Category的方法被调用后,调用所有Category和原类的方法?

        可以在一个Category方法被调用后,遍历方法列表并调用其他同名方法。但是需要注意一点是,遍历过程中不能再调用自己的方法,否则会导致递归调用。为了避免这个问题,可以在调用前判断被调动的方法IMP是否当前方法的IMP。

          - (void)method {
            
              //获取当前方法的IMP
              IMP currentIMP = [[self class] instanceMethodForSelector:_cmd];
              unsigned int methodCount = 0;
              //获取类的所有方法列表(Methodlist)包括私有方法
              Method *methodList = class_copyMethodList([self class], &methodCount);
              NSMutableArray *methodsArray = [NSMutableArray arrayWithCapacity:methodCount];
                        
              //定义IMP函数指针类型
              void (*function) (id self, SEL _cmd, NSObject* object);
        
              for(int i = 0; i < methodCount; i++)
              {
                  Method temp = methodList[i];
                  IMP imp = method_getImplementation(temp);
                  SEL name_f = method_getName(temp);
                            
                  //判断与当前函数同名且不是当前函数的IMP
                  if ([[NSString stringWithUTF8String:sel_getName(name_f)] isEqualToString:[NSString stringWithUTF8String:sel_getName(_cmd)]] && imp != currentIMP) {
                    
                      function = method_getImplementation(temp);
                      function(self, name_f, [NSObject new]);
                  }
              }
          }
        
      • 那怎样在任何一个Category的方法被调用后,只调用原类方法呢?

        根据上面对方法调用的分析,Runtime在调用方法时会优先所有Category调用,所以可以倒叙遍历方法列表,只遍历第一个方法即可,这个方法就是原类的方法。

          - (void)method {
                        
              NSLog(@"printMySelf --- MainClass (C)");
                        
              //获取当前方法的IMP
              IMP currentIMP = [[self class] instanceMethodForSelector:_cmd];
              unsigned int methodCount = 0;
              //获取类的所有方法列表(Methodlist)包括私有方法
              Method *methodList = class_copyMethodList([self class], &methodCount);
              NSMutableArray *methodsArray = [NSMutableArray arrayWithCapacity:methodCount];
                        
              //定义IMP函数指针类型
              void (*function) (id self, SEL _cmd, NSObject* object);
              //将MethodList从后向前遍历,找到第一个同名Method,即为
              for(int i = methodCount - 1; i >=0 ; i--)
              {
                  Method temp = methodList[i];
                  IMP imp = method_getImplementation(temp);
                  SEL name_f = method_getName(temp);
                            
                  //判断不是当前函数的IMP
                  if ([[NSString stringWithUTF8String:sel_getName(name_f)] isEqualToString:[NSString stringWithUTF8String:sel_getName(_cmd)]] && imp != currentIMP) {
                                
                      function = method_getImplementation(temp);
                      function(self, name_f, [NSObject new]);
                      break;
                  }
              }
          }
        
    • 消息的转发
      • 当通过调用objc_msgSend()方法调用,也无法找到对应的Method的时候;会进入动态转发流程
        • +resolveInstanceMethod:动态解析类方法;可以在该方法中调用class_addMethod动态的添加方法,返回True
        • forwardingTargetForSelector:(SEL)aSelector:可以返回备援对象的实例
        • forwardInvocation:(NSInvocation *)anInvocation 作用与上一步等效,new其他备援对象的实例,作为NSInvocation调用的target
    • KVO
      • KVO和NSNotificationCenter都是iOS中观察者模式的一种实现。区别在于,相对于被观察者和观察者之间的关系,KVO是一对一的,而不一对多的。KVO对被监听对象无侵入性,不需要修改其内部代码即可实现监听
      • 通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keyPath属性的变化事件
      • 在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash
      • 观察者需要实现observeValueForKeyPath:ofObject:change:context:方法,当KVO事件到来时会调用这个方法,如果没有实现会导致Crash
    • UIView
      • frame实现相对父View坐标系中的位置描述;bounds是对于自身坐标系的表描述,bounds中position是对左上角的坐标点的位置的描述,即对其所有子View的Frame的参考坐标,所以父View的bounds的position的改变会影响所有子View的位置。UIScrollView滑动的时候实际改变的是bounds的position
      • layoutSubviews方法:init初始化不会触发layoutSubviews;addSubview会触发layoutSubviews;frame发生变化会触发layoutSubviews;滚动一个UIScrollView会触发layoutSubviews;改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件;创建一个customView后,必须找到着陆床,让它能够被addSubview到一个父层,否则永远不会进入layoutSubviews
      • how to use updateConstraints
        • 尽量将约束的添加写到类似于viewDidLoad的方法中(init方法)。
        • updateConstraints并不应该用来给视图添加约束,它更适合用于周期性地更新视图的约束,或者在添加约束过于消耗性能的情况下将约束写到该方法中。
        • 当我们在响应事件时(例如点击按钮时)对约束的修改如果写到updateConstraints中,会让代码的可读性非常差。
      • 刷新子对象布局
        • layoutSubviews方法:这个方法,默认没有做任何事情,需要子类进行重写
        • setNeedsLayout方法: 标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定会被调用
        • layoutIfNeeded方法:如果,有需要刷新的标记,立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)
      • 重绘
        • drawRect:(CGRect)rect方法:重写此方法,执行重绘任务
          • 该方法在调用sizeToFit后被调用
          • drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或者 setNeedsDisplayInRect,让系统自动调该方法
          • 要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕
          • setNeedsDisplay方法:标记为需要重绘,异步调用drawRect
          • setNeedsDisplayInRect:(CGRect)invalidRect方法:标记为需要局部重绘
        • sizeToFit
          • sizeToFit会自动调用sizeThatFits方法;
          • sizeToFit不应该在子类中被重写,应该重写sizeThatFits
          • sizeThatFits传入的参数是receiver当前的size,返回一个适合的size
      • UIView的绘制
    • 内存管理
最近的文章

HTTP

TCP/IP协议族主要协议:ARP、TCP、IP、DNS、HTTP HTTP是无状态协议;引入Cookie的机制用于状态管理 三次握手、四次挥手 GET和POST SSL:AES + RSA的通信过程 + CA证书认证身份 HTTPs = HTTP + SSL + TLS SSL独立协议,也可以与stmp等其他应用层协议写作保证安全…

程序员的自我修养继续阅读
更早的文章

SSM框架学习笔记

Spring,控制翻转,依赖注入;通过xml文件的配置实现对于对象生命周期的控制,以及依赖注入的实现;Spring将Java的所有类都看作JavaBean,而IoC的目标就是管理Beans MyBatis:sqSessionFactoryBuilder,sqlSessionFactory,sqlSession,Mapper;Pojo,Mapper(接口、XML) 控制反转就是通过描述(XML或者注解),并通过第三方去产生或获取特定对象的方式 Spring-Bean的装配方式【Io...…

后台学习继续阅读