阅读视图

发现新文章,点击刷新页面。

Objective-C之Class底层结构探索

isa 走位图

在讲 OC->Class 底层类结构之前,先看下下面这张图:

isa走位

通过isa走位图 得出的结论是: 1,类,父类,元类都包含了 isa, superclass
2,对象isa指向类对象,类对象的isa指向了元类,元类的 isa 指向了根元类,根元类 isa 指向自己
3,类的 superclass 指向父类,父类的 superclass 指向的根类,根类的superclass 指向的nil
4,元类的 superclass 指向父元类,父元类 superclass 指向的根元类,根元类 superclass 指向根类,根类 superclass 指向nil

这下又复习了 isasuperclass 走位;那么问题这些类,类对象,元类对象当中的在底层展现的数据结构是怎样呢,这是我需要探索的,于是把源码贴出来展开分析下:

struct objc_class

struct objc_class : objc_object {
    // Class ISA;
    Class superclass; 
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;  
    class_rw_t *data() const {
        return bits.data();
    }
    const class_ro_t *safe_ro() const {
        return bits.safe_ro();
    }
}

从源码没见 isa 属性,其实它继承了objc_object ,而 objc_object 中有个isa ,在运行时类图生成中会产生一个isa 指向objc_object 这个类图,而 superclass 指向它的父类;根据上面 isa , superclass 走位图就知道它的指向关系。

cache_t & class_data_bits_t

cache 方法缓存,这个作用将常调用的方法缓存下来;便于下次直接查找调用,提高查找效率。 它的结构:

struct cache_t {
struct bucket_t *buckets() const;//存储方法的散列表
mask_t mask() const;//散列表缓存长度
mask_t occupied() const;//已缓存方法个数
}
struct class_data_bits_t {
    class_rw_t* data() const;//类信息
}

bits 存储具体类信息,它需要&FAST_DATA_MASK来计算得到类心所有信息,源码如下:

FAST_DATA_MASK 掩码值

imageng

bool has_rw_pointer() const {
#if FAST_IS_RW_POINTER
        return (bool)(bits & FAST_IS_RW_POINTER);
#else
        class_rw_t *maybe_rw = (class_rw_t *)(bits & FAST_DATA_MASK);
        return maybe_rw && (bool)(maybe_rw->flags & RW_REALIZED);
#endif
}

通过源码确实需要这种方式计算能得到类的存储信息;那为什么要用这种方式去处理呢。 比如说我要得到存储在 class_rw_t 类信息信息我只要通过 FAST_DATA_MASK 掩码值就能得到它的地址信息,通过地址信息就能从内存中拿到所有类的存储信息。

那这样我的FAST_DATA_MASK掩码值不一样,我通过&计算,得到的数据信息也就不一样,不得不说苹果工程师想的周到,而且这种方式不仅isa也是这样,很多地方都用这种方式取值,大大提高访问速度,数据提取效率。

class_rw_t ,class_ro_t,class_rw_ext_t

struct class_rw_t {
     const class_ro_t *ro() const ;
     const method_array_t methods() const ;//如果是类对象:放对象方法,元类:元类对象方法
     
     const property_array_t properties() const;
     const protocol_array_t protocols() const;
     class_rw_ext_t *ext() const;
}
struct class_rw_ext_t {
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    uint32_t version;
}

可以看出类的信息具体就存储在class_rw_tclass_ro_tclass_rw_ext_t 中,

剖析下class_rw_t 先看看method_array_tproperty_array_tprotocol_array_t源码结构

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};


class protocol_array_t : 
    public list_array_tt<protocol_ref_t, protocol_list_t, RawPtr>
{
    typedef list_array_tt<protocol_ref_t, protocol_list_t, RawPtr> Super;

 public:
    protocol_array_t() : Super() { }
    protocol_array_t(protocol_list_t *l) : Super(l) { }
};

看完之后,他们都继承list_array_tt,那么 list_array_tt 是什么鬼,它数据结构是怎样的,这下在取找下它。源码如下:

template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
 protected:
    template <bool authenticated>
    class iteratorImpl {
        const Ptr<List> *lists;
        const Ptr<List> *listsEnd;
    }
        
    using iterator = iteratorImpl<false>;
    using signedIterator = iteratorImpl<true>;

 public:
    list_array_tt() : list(nullptr) { }
    list_array_tt(List *l) : list(l) { }
    list_array_tt(const list_array_tt &other) {
        *this = other;
    }

    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray =(array_t*)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<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;
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
        }
    }
    
}

我把主要地方拿去出来,可以看到 attachLists 它的目的是将一个或多个列表(List 类型)附加到某个 list_array_tt对象中。这个对象可以包含零个、一个或多个列表,这些列表可以是单个指针,也可以是指针数组。函数的输入参数是一个指向 List 指针数组的指针 addedLists 和一个无符号整数 addedCount,表示要添加的列表数量。

由此我推断它是一个数组,而且是一个二维数组存储的,所有由此得出 class_rw_tmethodspropertiesprotocols这几个属性利用二维数组取存储类的方法,协议等信息,而且是可读可写的属性。

那它设计这种二维数组有什么好处呢?当然有好处,它可以动态的给数组里面增加删除方法,很方便我们分类方法的编写完进行存储。

那搞清楚了 class_rw_t 几个重要数据存储信息,那 class_rw_t 它的作用是干什么的呢;

class_rw_t 结构体定义来看;它是在应用运行时,将OC类,分类的信息直接写入到class_rw_t结构的数据结构中,在类的方法,协议进行调用时,从里面去读取,然后常调用的方法,又存储在cache_t这个结构体中,可想而知,苹果对OC类的处理,煞费苦心。

struct class_ro_t

class_rw_t结构体中有个 class_ro_t 结构体,在探索下这个东西做什么的,它的源码如下:

struct class_ro_t {
    WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    property_list_t *baseProperties;
}

先说说 ivars 这个属性修饰的结构体源码如下:

struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
    bool containsIvar(Ivar ivar) const {
        return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
    }
};

这个貌似只有一个继承 entsize_list_tt,那在探索下源码:

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
     struct iteratorImpl {
     uint32_t entsize;
        uint32_t index;  // keeping track of this saves a divide in operator-

        using ElementPtr = std::conditional_t<authenticated, Element * __ptrauth(ptrauth_key_process_dependent_data, 1, 0xdead), Element *>;

        ElementPtr element;

        typedef std::random_access_iterator_tag iterator_category;
        typedef Element value_type;
        typedef ptrdiff_t difference_type;
        typedef Element* pointer;
        typedef Element& reference;

        iteratorImpl() { }

        iteratorImpl(const List& list, uint32_t start = 0)
            : entsize(list.entsize())
            , index(start)
            , element(&list.getOrEnd(start))
        { }
     }
}

可以看出这段代码定义了一个结构体 entsize_list_tt,它内部包含一个嵌套的结构体 iteratorImpl,用于实现一个迭代器。遍历容器(如列表、数组等)的对象。

到此可以得出ivars 是一个 ivar_list_t 数组,它存储了类的属性变量信息,那protocol_list_t结构体内部也是数组形式构建的。

baseProtocolsbaseProperties 这两个属性对类的存储信息只能读取,不能写入。

所以总结的是:从 class_ro_t 结构体定义来看,它存储类的变量,方法,协议信息,而且这个结构体属于类的只读信息,它包含了类的初始信息。

class_rw_ext_t

这个结构体不在过多叙述,简单来说它是基于 class_rw_t 之后为了更好管理oc类的高级特性,比如关联属性等,衍生出来的一个结构体,包括:method_array_t ,property_arrat_t ,protocol_array_t 等定义属性类型

到这里类结构及存储所关联的信息都在这里了;来一张他们关联的结构思维图:

imageng

总结:一开始编译时,程序将类的初始信息放在 class_ro_t中,当程序运行时,将类的信息合并在一起的时候,它会将 class_ro_t 类的信息合并到 class_rw_t 结构体中去。

struct method_t

为什么要说method_t,因为它不仅在 class_ro_t 有使用,在OC底层其他地方也有使用;比如如下源码:

void method_exchangeImplementations(Method m1Signed, Method m2Signed)
{
    if (!m1Signed  ||  !m2Signed) return;

    method_t *m1 = _method_auth(m1Signed);
    method_t *m2 = _method_auth(m2Signed);

    mutex_locker_t lock(runtimeLock);

    IMP imp1 = m1->imp(false);
    IMP imp2 = m2->imp(false);
    SEL sel1 = m1->name();
    SEL sel2 = m2->name();

    m1->setImp(imp2);
    m2->setImp(imp1);


    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?

    flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
        return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
    });

    adjustCustomFlagsForMethodChange(nil, m1);
    adjustCustomFlagsForMethodChange(nil, m2);
}

static IMP
_method_setImplementation(Class cls, method_t *m, IMP imp)
{
    lockdebug::assert_locked(&runtimeLock);

    if (!m) return nil;
    if (!imp) return nil;

    IMP old = m->imp(false);
    SEL sel = m->name();

    m->setImp(imp);

    // Cache updates are slow if cls is nil (i.e. unknown)
    // RR/AWZ updates are slow if cls is nil (i.e. unknown)
    // fixme build list of classes whose Methods are known externally?

    flushCaches(cls, __func__, [sel, old](Class c){
        return c->cache.shouldFlush(sel, old);
    });

    adjustCustomFlagsForMethodChange(cls, m);

    return old;
}


方法交换,实现中底层都有用到,我们探索下,先看看 method_t 源码:

struct method_t {

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

    // A "big" method, but name is signed. Used for method lists created at runtime.
    struct bigSigned {
        SEL __ptrauth_objc_sel name;
        const char * ptrauth_method_list_types types;
        MethodListIMP imp;
    };

    // ***HACK: This is a TEMPORARY HACK FOR EXCLAVEKIT. It MUST go away.
    // rdar://96885136 (Disallow insecure un-signed big method lists for ExclaveKit)
#if TARGET_OS_EXCLAVEKIT
    struct bigStripped {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
#endif

}

可以看到这结构体中掐套了多个结构体;在把它简化下:

struct method_t {
    SEL name;//方法名
    const char *types;//包含函数具有参数编码的字符串类型的返回值
    MethodListIMP imp;//函数指针(指向函数地址的指针)
}

SEL :函数名,没特别的意义;

特点: 1,使用@selector()sel_registerName()获得 2,使用sel_getName()NSStringFromSelector()转成字符串 3,不同类中相同名字方法,对应的方法选择器是相同或相等的

底层代码结构:

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

types:包含了函数返回值、参数编码的字符串

imagengimageng

可以看到types在值:v16@0:8 ,可以看出name,types,IMP其实都在class_ro_t结构体中,这样确实证明了之前说的;class_ro_t结构体在运行时存储着类的初始状态数据。

v16@0:8说明下:
v:方法返回类型,这里说void,
16:第一个参数,
@:id类型第二个参数,
0:第三个参数
: :selector类型
8:第四个参数

那这种types参数又是什么鬼东西,查下了资料这叫:Type Encoding(类型编码) 怎么证明了,使用如下代码: imagepng

苹果官网types encoding表格: imageng

IMP 其实就是指向函数的指针,感觉这个就没有必要讲了。

struct cache_t

cache_t 用于 class的方法缓存,对class常调用的方法缓存下来,提高查询效率,这个上之前都已经说过;接下来看看 bucket_t

struct bucket_t

struct bucket_t {
cache_key_t _key;//函数名
IMP _imp;//函数内存地址
}

这种散列表的模型,其实在底层用一个数组展现:

imagng

其实它的内部就是一个一维数组,那可能问了,数组难道它是循环查找吗,其实不然;在它元素超找时,它是拿到你的 函数名 & mask,而这个 mask 就是 cache_t 结构体中的 mask值;计算得到函数在 散列表 存储的索引值,在通过索引拿到函数地址,进行执行。

接下来看个事例:

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        Student *stu=[Student new];

        [stu test];

        [stu test];

        [stu test];

        [stu test];

    }

    return 0;

}

如上方法:当首次调用它会去类对象中查找,在方法执行时,他会放入cache_t 缓存中,当第二次,第三次,第四次时,它就去缓存中查找。

imagpng

当方法执行后;我们看到 _mask 是:3,这个3代表了我类中定义了三个函数;而——_occupied 是一个随意的值;它其实代表了换存方法的个数。

那如何知道方法有缓存了,再继续往下执行:

imageng

这时候执行完 test02, _mask的值从 3 变成了 7 ,说明散列表 bucket_t 做了扩容操作。在这里bucket_t 元素需要 _mask 个元素,所以最终 bucket_t 从原有的3个元素进行了 2倍 扩容。

在看下方法是否进行缓存:

imageng

可以看见当执行完 [stu test02] 时,数据做了扩容,并且扩容的数据使用(null) 进行填充。

在看个事例:

imageng

在执行 [stu test] 之前;其实bucket_t 就3个元素,并且存入了 init 方法;

imageng

当执行完 [stu test] 之后;就存入 test 方法。

但是注意的地方:它在扩容时对之前的缓存进行清除。

image.png

通过查看源码,我们知道了它如何进行清除操作,

imageng

当执行完 [stu test02];[stu test03]; 之后,它先将缓存清空;这时候 init , test 方法被清空,bucket_t扩容完在存储:test02test03 方法。

那问题又来了,它是如何快速定位到方法的,然后执行的?接下来看看代码:

imagepng

可以清楚看见,当我使用 @selector(test03)&stu_cache._mask 就可以得到下标,然后再从 bucket_t 拿到方法。

到这里 class结构,类的方法缓存到此结束了,从上面也可以思考下:如果自己去实现散列表数组,是不是思路就跟清晰了。

谢谢大家!青山不改,绿水长流。后会有期!

SwiftUI从入门到精髓

SwiftUI

序言

开年的第一篇文章,今天分享的是SwiftUI,SwiftUI出来好几年,之前一直没学习,所以现在才开始;如果大家还留在 iOS 开发,这门语言也是一个趋势; 目前待业中.... 不得不说已逝的2023,大家开始都抱着一解封,经济都会向上转好,可是现实不是我们想象那样;目前我也在学习 SwiftUI,并且努力找工作中....。至于 2024 年经济如何,咱们作为老百姓在大环境和全球经济影响下;坦然面对,提升自己。 这里不得不说国人坚韧不拔的精神。“卷” -- 努力吧Coding人

SwiftUI体验

Xcode创建项目之后出现工程默认创建的UI界面;如下

swiftUI

一开始心里对自己说:"SwiftUI作为iOS开发新的UI体系,为啥初创的项目这么多代码,给初学者看到,一种压迫感,心想这语法好复杂,不想学了";不管你是不是这样心里,我刚开始看见,这么一坨代码,没什么心思,于是索性删掉;按自己能理解学习的方式来操作;于是做了简化:

import SwiftUI
import SwiftData

struct ContentView: View {
   
    var body: some View {
        Text("hello,word")
    }
}

#Preview {
    ContentView()
        .modelContainer(for: Item.self, inMemory: true)
}

关键字 some

关键字some啥玩意儿,完全陌生;先看看View;点击进入源码结构查看:

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required ``View/body-swift.property`` property.
    associatedtype Body : View

    @ViewBuilder @MainActor var body: Self.Body { get }
}

一堆英文注解估计大家不喜欢看,我就没贴出来了;简单来说: View 是一个泛型协议,它定义了所有视图类型需要遵循的接口,通过some修饰;表示 "我返回一个满足View 协议的某种类型"。some关键字告诉 Swift,虽然我们知道body必须返回一个View,但我们不确定具体是哪种 View(例如,TextImageVStack 等)。

协议里有一个associatedtypebody,其实这种协议就是当作约束形式使用;只要遵守这种协议编译器每次闭包中返回的一定是一个确定,遵守View协议的类型。

那么苹果工程师利用Swift5.1 Opaque return types 特性,为开发者提供了一个灵活的开发模式,抹掉了具体的类型,不需要修改公共API来确定每次闭包的返回类型,也降低了代码书写难度。(学学苹果那些大神思想,不错)

在来看看Preview

struct ContentView_Previews:PreviewProvider{
    static var previews: some View{
        ContentView()
        
    }
}

PreviewProvider就一个协议类,它的作用提供swiftUI不用运行,就能直接看到UI渲染变化,我觉得这个挺好,减少开发人员对UI运行测试次数和时间,而previews就是一个静态属性,返回一个 View 对象,用于在预览面板中展示。

@State属性包装器

@State属性包装器解决UI界面上,数据同步以及及时刷新的功能。一般来说数据更新完,界面 UI 同时更新。在 SwiftUI里面,视图中声明的任何状态、内容和布局,源头一旦发生改变,会自动更新视图,因此,只需要一次布局,这个时候出现了@State,它来解决与UI之间数据状态问题。

它的概念就是:@State 是一个属性包装器 (property wrapper) ,用于声明状态属性 (state property) 当状态属性发生变化时,SwiftUI 会自动更新视图以反映最新的状态。

属性的值被存储在特殊的内存区域中,这个区域与 View struct 是隔离的 至于被它修饰的属性内存存储与分布现在无从得知,还没学习到那么深入,这事儿慢慢来,不是一天两天的,先上个代码看看它怎么使用的:

import SwiftUI

struct StateBootcamp: View {
    
    @State var bgkColor:Color = Color.blue
    @State var cut:Int = 0
    
    var body: some View {
        
        ZStack{
            
            bgkColor
                .ignoresSafeArea(.all)
            
            VStack(spacing: 20){
                
                Text("Hello, World!")
                    .font(.title)
                
                Text("count:\(cut)")
                    .font(.largeTitle)
                
                HStack(spacing: 20){
                    Button("Button01") {
                        cut+=1
                        bgkColor = Color.red
                    }
                    .font(.title)
                    .foregroundColor(.white)
                    
                    Button("Button02") {
                        cut-=1
                        bgkColor = .purple
                    }
                    .font(.title)
                    .foregroundColor(.white)
                }
                Button("默认"){
                    cut=0
                    bgkColor = .blue
                }
                .font(.title)
                .foregroundColor(.white)
            }
        }
    }
}

#Preview {
    StateBootcamp()
}

其实一看代码,就一幕了然,知道它的使用与作用;如果你写过swift代码,这些东西很好理解,但是只会OC,那么我建议你学习下swift;在来看 swiftUI 语法糖才更好理解。

在看看源码:

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen @propertyWrapper public struct State<Value> : DynamicProperty {
    public init(wrappedValue value: Value)
    public init(initialValue value: Value)
    public var wrappedValue: Value { get nonmutating set }
    public var projectedValue: Binding<Value> { get }
}


@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension State where Value : ExpressibleByNilLiteral {

    /// Creates a state property without an initial value.
    ///
    /// This initializer behaves like the ``init(wrappedValue:)`` initializer
    /// with an input of `nil`. See that initializer for more information.
    @inlinable public init()
}


可以看到State是一个结构体,由@propertyWrapper包装的。@propertyWrapper是属性包装器。property wrapper 做的事情大体如下:

-   为底层的存储变量`State<Int>`自动提供一组 **getter****setter** 方法,结构体内保存了`Int`的具体数值;

-   在 body 首次求值前,将`State<Int>`关联到当前`View`上,为它在堆中对应当前`View`分配一个存储位置。

-`@State`修饰的变量设置观察,当值改变时,触发新一次的`body`求值,并刷新 UI。

SwiftUI 基础组件

Spacer垫片 :先贴贴代码

import SwiftUI

struct SpacerBootcampDemo: View {
    var body: some View {
        Text("Spacer UP")
            .font(.largeTitle)
        
        Spacer()
            .frame(width: 37)
            .background(.blue)
        
        Text("Spacer Down")
            .font(.largeTitle)
        
    }
}

#Preview {
    SpacerBootcampDemo()
}

在看看效果图:

Spacer

总结:Spacer 是一个灵活的空间视图,它的主要作用是在布局中自动调整自身的高度和宽度,以填满特定的空间;简单来说,它就是一个垫片,调整自身视图的高度,如果它周围有其他视图,也会受到 Spacer 影响。

ScrollView 如果你之前使用 UIkit 框架开发,在用 SwiftUI ,一下有点不适应,代码和之前的 UIkit 开发模式不太一样,但是大大缩短UI编写时间;先上代码:


import SwiftUI

struct ScollViewBootcamp: View {
    
    var body: some View {
        
        ScrollView{
            LazyVStack{
                ForEach(0..<20){
                    (idx) in
                    
                    VStack {
                        
                        Text("Hello, World!")
                            .font(.title)
                            .foregroundStyle(.white)
                            .frame(width: UIScreen.main.bounds.width-20,height: 350)
                            .background(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0..<215)/255.0, green: CGFloat.random(in: 0..<235)/255.0, blue: CGFloat.random(in: 0...247)/255.0, alpha: 0.9)))
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        
                        Rectangle()
                            .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0...187)/255.0, green: CGFloat.random(in: 0..<210)/255.0, blue: CGFloat.random(in: 0...237)/255.0, alpha: 0.9)))
                            .frame(width: UIScreen.main.bounds.width-20,height: 530)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        
                        
                        ScrollView(.horizontal,showsIndicators: false,content: {
                            LazyHStack{
                                ForEach(0..<10){
                                    idx in
                                    Rectangle()
                                        .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0...167)/255.0, green: CGFloat.random(in: 0...131)/255.0, blue: CGFloat.random(in: 0...89)/255.0, alpha: 0.9)))
                                        .frame(width: 200, height: 300)
                                        .clipShape(RoundedRectangle(cornerRadius: 10))
                                    
                                }
                            }
                        })
                        .padding(.leading,10)
                        .padding(.trailing,10)
                        
                        
                    }
                }
            }
            .frame(width:UIScreen.main.bounds.width)
            
        }
    }
}


#Preview {
    ScollViewBootcamp()
}

上图看看效果:

ScrollView

简单几句就能实现 ScrollView 的滑动效果;非常方便。

LazyVGrid 网格布局,先上代码:

import SwiftUI

struct GridViewBootcamp: View {
    
    let columns=[        GridItem(.flexible(),spacing: 6   ,alignment: .center),        GridItem(.flexible(),spacing: 6    ,alignment: .center),        GridItem(.flexible(),spacing: 6  ,alignment: .center),    ]
    
    var body: some View {
        
        ScrollView{
            LazyVGrid(columns: columns,
                      alignment: .center,
                      spacing: 6,
                      pinnedViews: [.sectionHeaders],content:
                        {
                Section(content: {}, header: {
                    Text("section header 一")
                        .font(.largeTitle)
                        .foregroundStyle(.blue)
                        .frame(width: UIScreen.main.bounds.width,height: 100,alignment: .leading)
                })
                
                ForEach(0..<41){
                    index in
                    Rectangle()
                        .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0..<255)/255.0, green: CGFloat.random(in: 0..<255)/255.0, blue: CGFloat.random(in: 0...255)/255.0, alpha: 0.9)))
                        .frame(height: 50)
                }
                
                //-------
                Section {
                    
                } header: {
                    Text("section header 二")
                        .font(.largeTitle)
                        .foregroundStyle(.blue)
                        .frame(width: UIScreen.main.bounds.width,alignment: .leading)
                    
                }
                
                ForEach(0..<41){
                    index in
                    Rectangle()
                        .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0..<255)/255.0, green: CGFloat.random(in: 0..<255)/255.0, blue: CGFloat.random(in: 0...255)/255.0, alpha: 0.9)))
                        .frame(height: 50)
                }
                
            })
            .padding(.leading,6)
            .padding(.trailing,6)
            .background(.gray)
        }.background(.blue)
    }
}

#Preview {
    GridViewBootcamp()
}

效果图:

LazyVGrid

总结:LazyVGrid 大家看到这个单词有个 Lazy 懒加载的意思,它的内部加载item简单来说,就是当视图需要时,才会执行item内容渲染功能,展示UI上。也就这点注意。

SafeArea 安全区域:

import SwiftUI

struct SafeAreaBootcamp: View {
    var body: some View {
        GeometryReader{
            src in
            Rectangle()
                .fill(.blue)
                .frame(maxWidth: .infinity,
                       maxHeight: .infinity)
        }
    }
}

#Preview {
    SafeAreaBootcamp()
}

效果图:

safeArea

可以看到上下边距存在安全区域的,如果禁用安全区域,使用 ignoresSafeArea(.all) 可以去掉;这个就太简单了。

代码如下:

safeArea

最后说说SwiftUI函数表达

上上代码:


import SwiftUI

struct ExtractFunctionsBootcamp: View {
    
    @State var bgc:Color = .red
    
    var body: some View {
        normolView
    }
    
    var normolView : some View {
        setUI()// 函数调用
    }
    
    func chageColor() -> Void {
        self.bgc = .red
    }
    
    //函数定义
    func setUI()->some View {
        return ZStack{
            
            bgc
                .ignoresSafeArea(.all)
            
            VStack(spacing: 20, content: {
                
                Text("Hello, World!")
                    .font(.largeTitle)
                
                Button(action: {
                    bgc = .brown
                }, label: {
                    Text("Button")
                        .font(.largeTitle)
                        .foregroundStyle(.white)
                })
                
                Button {
                    self.chageColor()
                } label: {
                    Image(systemName: "button.horizontal.top.press")
                        .resizable()
                        .foregroundColor(.white)
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 50,height: 50)
                }
            })
        }
    }
}


#Preview {
    ExtractFunctionsBootcamp()
}

其实函数表达跟我们swift语法糖一样;func 命名;这点和swift语法糖没什么区别。

总结(说说我的感想)

SwiftUI优点:

简洁性:Swift,SwiftUI语法简洁,编写代码变得更加容易和快速。

安全性:是一种类型安全的编程语言,可以在编译时检测类型错误,这帮助我们避免许多常见的错误,提高代码的质量和可靠性。

互操作性:它与Objective-C语言无缝互衔接,是的OC与swift代码混编变的更加便捷。

SwiftUI缺点

功能限制:虽然SwiftUI提供了许多常见的UI组件,但与UIKit相比,功能仍然相对有限。在某些复杂的界面需求下,可能需要使用UIKit来实现。

错误提示不明确:有时SwiftUI, SwiftUI的错误提示可能不够明确,导致难以定位问题。

UIkit与SwiftUI缺乏无缝兼容:两者兼容性不够理想,这在业务开发中,你可能才能发现。

目前苹果与市面大量应用也在使用Swift,SwiftUI开发应用,这们语言在应用中占有读也是成倍增长。

路漫漫其修远兮,吾将上下而求索

提醒大家在的时候,注意身体劳逸结合;毕竟没有谁真的会在互联网这个行业干到退休,最终都会离开,把位置留给年轻人。

后续更新中..........

iOSload和initialize详解

load和initialize详解

在介绍之前,我们首先来了解一下类的使用,我们要使用一个类,大概要经过以下步骤
1启动App,程序开始加载类到内存中(代码区)+(void)load
2首次使用该类时,创建类对象(我们可以把它看作是一个单例,它在整个程序中只有一份)+(void)initialize
3通过类对象创建实例对象+(instancetype)alloc、-(instancetype)init
4通过实例对象,我们就可使用实例方法、类属性了
从上面的步骤我们也大概了解到load和initialize的调用时机了,下面在来详细说一下

一、+load类方法

当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要再调用[super load],否则父类的load函数会多次执行。
●1.当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
●2.当一个类未实现load方法时,不会调用父类load方法
●3.类中的load方法执行顺序要优先于类别(Category)
●4.当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
●5.当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致

二、+initialize类方法

●即使类文件被引用进项目,但是没有使用,initialize不会被调用
●假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。
●类或者其子类的第一个方法被调用前调用
●由于是系统自动调用,也不需要再调用 [super initialize],否则父类的initialize会被多次执行
●父类的initialize方法会比子类先执行
●子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
●当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

三、两者的异同
1、相同点
1load和initialize会被自动调用,不能手动调用它们。
2如果父类和子类都被调用,父类的调用一定在子类之前
3子类实现了load和initialize的话,会隐式调用父类的load和initialize方法。
4load和initialize方法内部使用了锁,因此它们是线程安全的。
2、不同点
1调用顺序不同,以main函数为分界,+load方法在main函数之前执行,+initialize在main函数之后执行。
2子类中没有实现+load方法的话,不会调用父类的+load方法;而子类如果没有实现+initialize方法的话,也会自动调用父类的+initialize方法。
3+load方法是在类被装在进来的时候就会调用,+initialize在第一次给某个类发送消息时调用(比如实例化一个对象),并且只会调用一次,是懒加载模式,如果这个类一直没有使用,就不会调用到+initialize方法。
四、使用场景
1.+load一般是用来交换方法Method Swizzle,由于它是线程安全的,而且一定会调用且只会调用一次,通常在使用UrlRouter的时候注册类的时候也在+load方法中注册。

+(void)load {

static dispatch_once_t oneToken;

dispatch_once(&oneToken, ^{

    Method imageNamed = class_getClassMethod(self,@selector(imageNamed:));

    Method mkeImageNamed =class_getClassMethod(self,@selector(swizze_imageNamed:));

    method_exchangeImplementations(imageNamed, mkeImageNamed);

    });

}

+ (instancetype)swizze_imageNamed:(NSString*)name {

//这个时候swizze_imageNamed已经和imageNamed交换imp,所以当你在调用swizze_imageNamed时候其实就是调用imageNamed

UIImage * image;

if( IS_IPHONE ){

// iphone处理

UIImage * image = [self swizze_imageNamed:name];

    if (image != nil) {

        return image;

    } else {

        return nil;

    }

} else {

    // ipad处理,_ipad是自己定义,~ipad是系统自己自定义。

    UIImage *image = [self swizze_imageNamed:[NSString stringWithFormat:@"%@_ipad",name]];

    if (image != nil) {

            return image;

        }else {

            image = [self swizze_imageNamed:name];

            return image;

    }

}

2.+initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值,或者说对一些静态常量进行初始化操作。


/ In Person.m

// int类型可以在编译期赋值

static int someNumber = 0;

static NSMutableArray *someArray;

+ (void)initialize {

if (self == [Person class]) {

    // 不方便编译期复制的对象在这里赋值

    someArray = [[NSMutableArray alloc] init];

    }

}

五、总结
1load和initialize方法都会在实例化对象之前调用
2load执行在main函数以前,initialize执行main函数之后
3这两个方法会被自动调用,不能手动调用它们。
4load和initialize方法都不用显示的调用父类的方法而是自动调用
5子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类
6initialize方法对一个类而言只会调用一次(Person、或者是Person+Category)都是一个Perosn类。load方法则是每个都会调用,只要你写了load方法,添加到工程都会实现。
7load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
8load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。

聊聊iOS自动释放池AutoreleasePool

自动释放池

●自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage
●调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
●是和线程一一对应的

@autoreleasepool {
    //...
}
// 编译器会将上面代码改写为一下代码
void *ctx = objc_autoreleasePoolPush();
//{}中的代码
objc_autoreleasePoolPop(ctx)

源码分析
●clang重写@autoreleasepool
●objc4源码:NSObject.mm

imag2212

AutoreleasePoolPage的结构 
●每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
●所有的AutoreleasePoolPage对象通过以栈为结点,双向链表的形式连接在一起

imag749274jhjdsng

●调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
●调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
●id *next指向了下一个能存放autorelease对象地址的区域  

Runloop和Autorelease 
App启动后,iOS在主线程的Runloop中注册了2个Observer管理和维护AutoreleasePool,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler(),打印currentRunLoop可以看到。

<CFRunLoopObserver 0x6080001246a0 [0x101f81df0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1020e07ce), context = <CFArray 0x60800004cae0 [0x101f81df0]>{type = mutable-small, count = 0, values = ()}}
<CFRunLoopObserver 0x608000124420 [0x101f81df0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1020e07ce), context = <CFArray 0x60800004cae0 [0x101f81df0]>{type = mutable-small, count = 0, values = ()}}

●第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()创建自动释放池,其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
●第二个 Observer 监视了两个事件,这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池发生在其他所有回调之后
○监听了kCFRunLoopBeforeWaiting(准备进入休眠)时,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()释放旧的池并创建新池;
○监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

dc6bff9c5a

主线程的其他操作通常均在这个AutoreleasePool之内(main函数中),以尽可能减少内存维护操作(当然你如果需要显式释放【例如循环】时可以自己创建AutoreleasePool否则一般不需要自己创建)。

常见面试题:

  1. 自动释放池是什么时候创建的?什么时候销毁的?

●创建,运行循环检测到事件并启动后,就会创建自动释放池
●销毁:一次完整的运行循环结束之前,会被销毁

  1. 以上代码是否有问题?如果有,如何解决?
for (long i = 0; i < largeNumber; ++i) {
    NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
    str = [str uppercaseString];
    str = [str stringByAppendingString:@" - world"];
}

解决方法:引入自动释放池

●1> 外面加自动释放池(快?):能够保证for循环结束后,内部产生的自动释放对象,都会被销毁,需要等到 for 结束后,才会释放内存
●2> 里面加自动释放池(慢?):能够每一次 for 都释放产生的自动释放对象!


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    NSLog(@"start");
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    [self answer1];
    NSLog(@"外 %f", CFAbsoluteTimeGetCurrent() - start);

    start = CFAbsoluteTimeGetCurrent();
    [self answer2];
    NSLog(@"内 %f", CFAbsoluteTimeGetCurrent() - start);
}

- (void)answer1 {
    @autoreleasepool {
        for (long i = 0; i < largeNumber; ++i) {
            NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
            str = [str uppercaseString];
            str = [str stringByAppendingString:@" - world"];
        }
    }
}

- (void)answer2 {
    for (long i = 0; i < largeNumber; ++i) {
        @autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
            str = [str uppercaseString];
            str = [str stringByAppendingString:@" - world"];
        }
    }
}

●实际测试结果,是运行循环放在内部的速度更快!
●日常开发中,如果遇到局部代码内存峰值很高,可以引入自动释放池及时释放延迟释放对象

深入了解swift函数派发机制

函数派发

Swift中函数的派发机制有三种:静态派发,函数表派发,消息派发。

静态派发

静态派发是指在运行时不需要查表,直接跳转到方法进行执行。静态派发的性能也是最高的。c语言采用的是直接派发。

函数表派发

class类型采用函数表派发。当一个对象调用一个函数时,会从对象的头8字节找到该对象的元信息。从元信息的函数表中找到执行的函数地址,并执行函数。

对象的元信息会在编译时写入macho文件中。

class Animal {
    func speak() {
        print("Animal speak")
    }
    func eat() {
        print("Animal eat")
    }
    func sleep() {
        print("Animal sleep")
    }
}
class Dog: Animal {
    override func speak() {
        print("Dog speak")
    }
    override func eat() {
        print("Dog eat")
    }
    func run() {
        print("Dog run")
    }
}class Animal {
    func speak() {
        print("Animal speak")
    }
    func eat() {
        print("Animal eat")
    }
    func sleep() {
        print("Animal sleep")
    }
}
class Dog: Animal {
    override func speak() {
        print("Dog speak")
    }
    override func eat() {
        print("Dog eat")
    }
    func run() {
        print("Dog run")
    }
}

Dog继承于Animal。let dog: Dog = Dog()dog 变量指向Dog对象。Dog对象在堆内存中。Dog对象的前8字节存有Dog对象的元信息。Dog对象的元信息中有该类的函数指针表。Dog中override父类Animal的函数会替换掉父类的函数存于Dogd的函数指针中。

swift### 消息派发

继承于NSObject的采用的是消息派发。Swift可以通过dynamic修饰来支持消息派发机制。

函数派发场景分析

在选择派发方式时,在编译期间就能决定执行哪个函数就采用静态派发。需要在运行期间决定执行哪个函数的就需要用函数表派发或者消息派发。不具动态性的的场景默认采用静态派发,这样派发效率更高。

struct无法继承,也就不具有动态性,函数派发在编译期间就能确定。

class和协议的 extension无法被子类继承,函数派发在编译期间就能确定。

class和协议的初始化方法,因为绝大多数时候需要被override的,所以采用函数表派发。

class的其他方法,如果没有被override,一般是函数表派发,但编译器也可能优化成直接派发。

class的 @objcextension能够继承,函数派发在执行期决定,并且是采用的是消息派发。

不继承NSObject的纯Swift,@objc的extension,采用的消息派发有点迷。消息派发机制需要有Objective-C的运行时,不继承与NSObject能有运行时的信息吗。该类的对象,

class FunctionDispatchObject {
     func test1() {
        print("test1")
    }
}
extension FunctionDispatchObject {
    @objc public func test2() {
        print("test2")
    }
}
// test1采用函数表派发
// test2采用消息机制派发:test2虽然写在extension里,当家里@objc,具有了动态性,可以继承了。class FunctionDispatchObject {
     func test1() {
        print("test1")
    }
}
extension FunctionDispatchObject {
    @objc public func test2() {
        print("test2")
    }
}
// test1采用函数表派发
// test2采用消息机制派发:test2虽然写在extension里,当家里@objc,具有了动态性,可以继承了。

iOS APP启动全流程

1.在用户点击屏幕上的icon时,iOS系统用户体验层进程SpringBoad调用fork创建进程(复制进程),并执行execl函数将新的可执行代码载入内存,执行loadMachine去加载主Mach-o,进行__TEXT,__DATA的映射,加载UUID,创建主线程,代码签名验证,加载动态链器。

2.动态连接器加载完成后,动态链接器先加载主程序,再加载所依赖的动态库,这个加载的过程。

3.主程序进行初始化时,是递归进行的,依赖关系呈现的是一个类似树的有向图。递归过程是树的深度遍历。在向下递归时,遇到image没有依赖了时,也就是叶子节点,树形结构的最左边的叶子节点就是libsystem,会通执行_dyld_objc_notify_register里注册的load_image方法,进而进行category的加载,+load的方法的执行,然后初始化C&C++的静态化变量,然后调用 constructor 函数。而不是像市面是上的千篇一律的+load方法执行后,在初始化C&C++静态变量,然后调用constructor函数。一定要注意是递归。递归到最上层了,就是主程序了,也就是主程序的+load方法,在初始化C&C++静态变量,然后调用constructor函数。

iOS

真心送你一份iOS核心动画总结篇,赶紧上车,学完它才觉得物有所值,还有实战案例!!!

本文案列代码在篇尾,有小伙伴需要代码可以移至篇尾获取。

在iOS开发中,动画是提高用户体验重要的环节之一。一个设计严谨、精细的动画效果能给用户耳目一新的效果,这对于app而言是非常重要的。

简介

iOS动画主要是指Core Animation框架。官方使用文档地址为:Core Animation Guide。Core Animation是iOS和macOS平台上负责图形渲染与动画的基础框架。Core Animation可以作用与动画视图或者其他可视元素,为你完成了动画所需的大部分绘帧工作。你只需要配置少量的动画参数(如开始点的位置和结束点的位置)即可使用Core Animation的动画效果。Core Animation将大部分实际的绘图任务交给了图形硬件来处理,图形硬件会加速图形渲染的速度。这种自动化的图形加速技术让动画拥有更高的帧率并且显示效果更加平滑,不会加重CPU的负担而影响程序的运行速度。

Core Animation

Core Animation是一组非常强大的动画处理API,它的子类主要有4个:CABasicAnimation、CAKeyframeAnimation、CATransition、CAAnimationGroup。 Core Animation类的继承关系图:

Animation

属性

duration:动画的持续时间 beginTime:动画的开始时间 repeatCount:动画的重复次数 autoreverses:动画按照原动画返回执行 timingFunction:控制动画的显示节奏系统提供五种值选择,分别是:

  • kCAMediaTimingFunctionLinear 线性动画
  • kCAMediaTimingFunctionEaseIn 先快后慢
  • kCAMediaTimingFunctionEaseOut 先慢后快
  • kCAMediaTimingFunctionEaseInEaseOut 先慢后快再慢
  • kCAMediaTimingFunctionDefault 默认,也属于中间比较快

delegate:动画代理。能够检测动画的执行和结束。 path:帧动画中的执行路径 type:过渡动画的动画类型。主要有以下4中类型:

  • kCATransitionFade 渐变效果
  • kCATransitionMoveIn 进入覆盖效果
  • kCATransitionPush 推出效果
  • kCATransitionReveal 离开效果

subtype:过渡动画的动画方向。

  • kCATransitionFromRight 从右侧进入
  • kCATransitionFromLeft 从左侧进入
  • kCATransitionFromTop 从顶部进入
  • kCATransitionFromBottom 从底部进入

动画的使用

动画使用步骤:

  1. 初始化一个动画对象(CAAnimation)并设置一些动画相关属性.
  2. 添加动画对象到层(CALayer)中,开始执行动画.

CALayer中很多属性都可以通过CAAnimation实现动画效果, 包括opacity, position, transform, bounds, contents等,具体可以在API文档中查找

通过调用CALayer的addAnimation:forKey:增加动画到层(CALayer)中,这样就能触发动画了.通过调用removeAnimationForKey:可以停止层中的动画.

UIView

_demoView.frame = CGRectMake(0, SCREEN_HEIGHT/2-50, 50, 50); 
[UIView animateWithDuration:1.0f animations:^{
    _demoView.frame = CGRectMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-50, 50, 50); 
} completion:^(BOOL finished) { 
    _demoView.frame = CGRectMake(SCREEN_WIDTH/2-25, SCREEN_HEIGHT/2-50, 50, 50); 
}];

UIView [begin commit]

_demoView.frame = CGRectMake(0, SCREEN_HEIGHT/2-50, 50, 50);
[UIView beginAnimations:nil context:nil]; 
[UIView setAnimationDuration:1.0f];
_demoView.frame = CGRectMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-50, 50, 50); 
[UIView commitAnimations];

Core Animation

CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"position"]; 
anima.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, SCREEN_HEIGHT/2-75)]; 
anima.toValue = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-75)];
anima.duration = 1.0f;
[_demoView.layer addAnimation:anima forKey:@"positionAnimation"];

动画详解

CABaseAnimation

基础动画主要提供了对于CALayer对象中的可变属性进行简单动画的操作。比如:位移、透明度、缩放、旋转、背景色等等。 主要提供如下属性: fromValue:keyPath对应的初始值 toValue:keyPath对应的结束值 示例:

CABaseAnimation

1.呼吸动画 
CABasicAnimation *animation =[CABasicAnimation animationWithKeyPath:@"opacity"]; 
animation.fromValue = [NSNumber numberWithFloat:1.0f]; 
animation.toValue = [NSNumber numberWithFloat:0.0f]; 
animation.autoreverses = YES; //回退动画(动画可逆,即循环) 
animation.duration = 1.0f; 
animation.repeatCount = MAXFLOAT; 
animation.removedOnCompletion = NO; 
animation.fillMode = kCAFillModeForwards;//removedOnCompletion,fillMode配合使用保持动画完成效果 
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[self.alphaTagButton.layer addAnimation:animation forKey:@"aAlpha"]; 
2.摇摆动画 //设置旋转原点 
self.sharkTagButton.layer.anchorPoint = CGPointMake(0.5, 0); 
CABasicAnimation* rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; //角度转弧度(这里用1,-1简单处理一下) 
rotationAnimation.toValue = [NSNumber numberWithFloat:1]; 
rotationAnimation.fromValue = [NSNumber numberWithFloat:-1]; 
rotationAnimation.duration = 1.0f;
rotationAnimation.repeatCount = MAXFLOAT; 
rotationAnimation.removedOnCompletion = NO; 
rotationAnimation.autoreverses = YES; 
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 
rotationAnimation.fillMode = kCAFillModeForwards; 
[self.sharkTagButton.layer addAnimation:rotationAnimation forKey:@"revItUpAnimation"];

注意: 如果fillMode=kCAFillModeForwards和removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。这就相当于Android早期的View动画。

CAKeyframeAnimation

CAKeyframeAnimation和CABaseAnimation都属于CAPropertyAnimatin的子类。CABaseAnimation只能从一个数值(fromValue)变换成另一个数值(toValue),而CAKeyframeAnimation则会使用一个NSArray保存一组关键帧。

主要属性: values:就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧 path:可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略。 keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的。 示例:

CAKeyframeAnimation

values属性应用

-(void)setUpCAKeyframeAnimationUseValues { 
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position";
    NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(50, 50)]; 
    NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(kWindowWidth - 50, 50)];
    NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(kWindowWidth - 50, kWindowHeight-50)]; 
    NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(50, kWindowHeight-50)];
    NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(50, 50)]; 
    animation.values = @[value1,value2,value3,value4,value5]; 
    animation.repeatCount = MAXFLOAT; animation.removedOnCompletion = NO; 
    animation.fillMode = kCAFillModeForwards; 
    animation.duration = 6.0f; animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 
    [self.keyButton.layer addAnimation:animation forKey:@"values"]; 
}

path方式应用

-(void)setUpCAKeyframeAnimationUsePath { 
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position";
    CGMutablePathRef path = CGPathCreateMutable(); 
    //矩形线路 
    CGPathAddRect(path, NULL, CGRectMake(50,50, kWindowWidth - 100,kWindowHeight - 100)); 
    animation.path=path; CGPathRelease(path);
    animation.repeatCount = MAXFLOAT; 
    animation.removedOnCompletion = NO; 
    animation.fillMode = kCAFillModeForwards;
    animation.duration = 10.0f; 
    animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 
    [self.keyButton.layer addAnimation:animation forKey:@"path"]; 
}

keyTimes属性使用

-(void)setUpCAKeyframeAnimationUsekeyTimes { 
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; 
    animation.keyPath = @"position.x"; 
    animation.values = @[@0, @20, @-20, @20, @0]; 
    animation.keyTimes = @[ @0, @(1 / 6.0), @(3 / 6.0), @(5 / 6.0), @1 ]; 
    animation.duration = 0.5;
    animation.additive = YES; 
    [self.sharkTagButton.layer addAnimation:animation forKey:@"keyTimes"];
}

CAAnimationGroup

CAAnimationGroup(组动画)是CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行。有点类似于Android的帧动画,不过这里的组动画是将一些基础的动画拼接而成的,比如同时缩小、旋转、渐变。

主要属性有: animations:用来保存一组动画对象的NSArray。 示例:

CAAnimationGroup

CABasicAnimation * animationScale = [CABasicAnimation animation]; 
animationScale.keyPath = @"transform.scale"; 
animationScale.toValue = @(0.1); 
CABasicAnimation *animationRota = [CABasicAnimation animation]; animationRota.keyPath = @"transform.rotation"; 
animationRota.toValue = @(M_PI_2); 
CAAnimationGroup * group = [[CAAnimationGroup alloc] init]; 
group.duration = 3.0; 
group.fillMode = kCAFillModeForwards; 
group.removedOnCompletion = NO; 
group.repeatCount = MAXFLOAT; 
group.animations = @[animationScale,animationRota]; 
[self.groupButton.layer addAnimation:group forKey:nil];

CATransition

CAAnimation的子类,用于做过渡动画或者转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。 重要属性有: type:动画过渡类型,官方提供了如下类型:

  • kCATransitionFade 渐变效果
  • kCATransitionMoveIn 进入覆盖效果
  • kCATransitionPush 推出效果
  • kCATransitionReveal 揭露离开效果

subtype:动画过渡方向。

  • kCATransitionFromRight 从右侧进入
  • kCATransitionFromLeft 从左侧进入
  • kCATransitionFromTop 从顶部进入
  • kCATransitionFromBottom 从底部进入
  • startProgress:动画起点(在整体动画的百分比)
  • endProgress:动画终点(在整体动画的百分比)

CATransition

示例:

MyViewController *myVC = [[MyViewController alloc]init]; 
CATransition *animation = [CATransition animation]; 
animation.timingFunction = UIViewAnimationCurveEaseInOut; 
animation.type = @"cube"; animation.duration =0.5f; 
animation.subtype =kCATransitionFromRight; //控制器间跳转动画 
[[UIApplication sharedApplication].keyWindow.layer addAnimation:animation forKey:nil]; 
[self presentViewController:myVC animated:NO completion:nil];

github.com/yongliangP

❌