zoukankan      html  css  js  c++  java
  • Django------model基础

    模型:

    模型是你的数据的唯一的、权威的信息源。

    • 每个模型都是django.db.models.Model 的一个Python 子类。
    • 模型的每个属性都表示为数据库中的一个字段。
    • Django 提供一套自动生成的用于数据库访问的API.

    使用模型:appy应用后 manage.py makemigrations;manage.py migrate (表或表字段有更改时也需要做数据迁移)

    字段:

    字段类型:

    模型中的每个字段都是 Field 子类的某个实例。( django.db.models.fields

    字段类型

    • AutoField:一个根据实际ID自动增长的IntegerField,通常不指定
      • 如果不指定,一个主键字段将自动添加到模型中(用的时候为防止和自定义的ID冲突,可以用PK)
    • BooleanField:true/false 字段,此字段的默认表单控制是CheckboxInput
    • NullBooleanField:支持null、true、false三种值
    • CharField(max_length=字符长度):字符串,默认的表单样式是 TextInput
    • TextField:大文本字段,一般超过4000使用,默认的表单控件是Textarea
    • IntegerField:整数
    • DecimalField(max_digits=None, decimal_places=None):使用python的Decimal实例表示的十进制浮点数
      • DecimalField.max_digits:位数总数
      • DecimalField.decimal_places:小数点后的数字位数
    • FloatField:用Python的float实例来表示的浮点数
    • DateField[auto_now=False, auto_now_add=False]):使用Python的datetime.date实例表示的日期
      • 参数DateField.auto_now:每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为false
      • 参数DateField.auto_now_add:当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false
      • 该字段默认对应的表单控件是一个TextInput. 在管理员站点添加了一个JavaScript写的日历控件,和一个“Today"的快捷按钮,包含了一个额外的invalid_date错误消息键
      • auto_now_add, auto_now, and default 这些设置是相互排斥的,他们之间的任何组合将会发生错误的结果
    • TimeField:使用Python的datetime.time实例表示的时间,参数同DateField
    • DateTimeField:使用Python的datetime.datetime实例表示的日期和时间,参数同DateField
    • FileField:一个上传文件的字段
    • ImageField:继承了FileField的所有属性和方法,但对上传的对象进行校验,确保它是个有效的image

    字段选项:

      通过字段选项,可以实现对字段的约束,在字段对象时通过关键字参数指定

    • null:如果为True,Django 将空值以NULL 存储到数据库中,默认值是 False
    • blank:如果为True,则该字段允许为空白,默认值是 False(django admin填充数据时允许为空)
    • 对比:null是数据库范畴的概念,blank是表单验证证范畴的
    • db_column:字段的名称,如果未指定,则使用属性的名称
    • db_index:若值为 True, 则在表中会为此字段创建索引
    • default:默认值
    • primary_key:若为 True, 则该字段会成为模型的主键字段
    • unique:如果为 True, 这个字段在表中必须有唯一值
    • choices 它是一个可迭代的结构(比如,列表或是元组),由可迭代的二元组组成(比如[(A, B), (A, B)...]),用来给这个字段提供选择项。如果设置了 choices ,默认表格样式就会显示选择框,而不是标准的文本框,而且这个选择框的选项就是 choices 中的元组。每个元祖的第一个值做为select标签下option的value,第二个值为text显示文本。如果设置了choices,查询时想显示元祖第二哥元素时可用get_A_display()。
    • error_messages 参数能够让你重写默认抛出的错误信息。通过指定 key 来确认你要重写的错误信息。
    • help_text  文本将被显示在表单控件form中

    关系

    ORM:objects relationship map 对象关系映射

      表名(app名_表名)--------类名

      字段--------属性

      表记录--------类实例对象

    关系的类型包括

    • ForeignKey:一对多,将字段定义在多的端中 
      • 若要创建一个递归的关联 —— 对象与自己具有多对一的关系 —— 请用models.ForeignKey('self')

      • 数据库中的表示:段名上添加"_id" 来创建数据库中的列名
      • 参数:
        • related_name:用于让关联的对象反查到源对
        • to_field:关联到的关联对象的字段名称
        • db_constraint:控制是否在数据库中为这个外键创建约束。默认值为True,如果被设置成False,访问一个不存在的关联对象将抛出 DoesNotExist 异常。
        • on_delete:当一个ForeignKey 引用的对象被删除时,Django 默认模拟SQL 的ON DELETE CASCADE 的约束行为,并且删除包含该ForeignKey的对象。 选项:CASCADE:级连删除,默认;PROTECT:抛出ProtectedError 以阻止被引用对象的删除。
    • ManyToManyField:多对多,将字段定义在两端中 
      • 建议:不建议从一个没有迁移的应用中创建一个ManyToManyField到一个具有迁移的应用
      • 数据库中的表示:默认情况下,会创建一张中间表,该表的名称使用多对多字段的名称和包含这张表的模型的名称生成,表名:app名_关联表1名_关联表2名,字段:自增主键,关联表1名_id,关联表2名_id
      • 参数:
        • related_name;
        • through:through 选项来指定自己手动创建的第三张表
        • through_field:指定表后可以指定字段
             members = models.ManyToManyField(Person, through='Membership', through_fields=('group', 'person'))
    • OneToOneField:一对一,将字段定义在任意一端中

    选项:

    使用内部的class Meta 定义模型的元数据,可用的参数:

    • ordering:对象默认的顺序,获取一个对象的列表时使用,符串前加-表示倒序,不加-表示正序
    • unique_togethe:联合主键;

    • verbose_name:对象的一个易于理解的名称,为单数;
      from django.db import models
      
      class Ox(models.Model):
          horn_length = models.IntegerField()
      
          class Meta:
              ordering = ['-order_date']
              unique_together = ("driver", "restaurant")
          
          
      示例

    模型的方法:

      __str__():返回对象的字符串表达式(unicode格式

      get_absolute_url():计算一个对象的URL

    管理器Manager:

    • 管理器是Django的模型进行数据库的查询操作的接口,Django应用的每个模型都拥有至少一个管理器objects
    • 自定义管理器类主要用于两种情况
      • 情况一:向管理器类中添加额外的方法;
        from django.db import models
        
        class PollManager(models.Manager):
            def with_counts(self):
                from django.db import connection
                cursor = connection.cursor()
                cursor.execute("""
                    SELECT p.id, p.question, p.poll_date, COUNT(*)
                    FROM polls_opinionpoll p, polls_response r
                    WHERE p.id = r.poll_id
                    GROUP BY p.id, p.question, p.poll_date
                    ORDER BY p.poll_date DESC""")
                result_list = []
                for row in cursor.fetchall():
                    p = self.model(id=row[0], question=row[1], poll_date=row[2])
                    p.num_responses = row[3]
                    result_list.append(p)
                return result_list
        
        class OpinionPoll(models.Model):
            question = models.CharField(max_length=200)
            poll_date = models.DateField()
            objects = PollManager()
        
        class Response(models.Model):
            poll = models.ForeignKey(OpinionPoll)
            person_name = models.CharField(max_length=50)
            response = models.TextField()
        示例
      • 情况二:修改管理器返回的原始查询集:重写get_queryset()方法
        # First, define the Manager subclass.
        class DahlBookManager(models.Manager):
            def get_queryset(self):
                return super(DahlBookManager, self).get_queryset().filter(author='Roald Dahl')
        
        # Then hook it into the Book model explicitly.
        class Book(models.Model):
            title = models.CharField(max_length=100)
            author = models.CharField(max_length=50)
        
            objects = models.Manager() # The default manager.
            dahl_objects = DahlBookManager() # The Dahl-specific manager.
        ##Book.objects.all()将返回数据库中所有的图书。而 Book.dahl_objects.all() 只返回作者#是 Roald Dahl 的图书。
        示例 

    创建表对象:

      当创建对象时,django不会对数据库进行读写操作,调用save()方法才与数据库交互,将对象保存到数据库中

    示例化方法一:

    • 模型类上添加一个类方法:
    from django.db import models
    
    class Book(models.Model):
        title = models.CharField(max_length=100)
    
        @classmethod
        def create(cls, title):
            book = cls(title=title)
            # do something with the book
            return book
    
    book = Book.create("Pride and Prejudice")
    • 在自定义管理器中添加一个方法(推荐)
      class BookManager(models.Manager):
          def create_book(self, title):
              book = self.create(title=title)
              # do something with the book
              return book
      
      class Book(models.Model):
          title = models.CharField(max_length=100)
      
          objects = BookManager()
      
      book = Book.objects.create_book("Pride and Prejudice")
    • 根据类属性直接实例化和save方法,示例如下:

    普通字段:

    #方式1
    publish_obj=Publish(name="人民出版社",city="北京",email="renMin@163.com")
    publish_obj.save() # 将数据保存到数据库
    
    #方式2    
    #返回值publish_obj是添加的记录对象
    publish_obj=Publish.objects.create(name="人民出版社",city="北京",email="renMin@163.com")

    外键字段:

    方式1:
       publish_obj=Publish.objects.get(nid=1)
       Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=665,pageNum=334,publish=publish_obj)
    
    方式2:
       Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=665,pageNum=334,publish_id=1)

    多对的字段:

    book_obj=Book.objects.create(title="追风筝的人",publishDate="2012-11-12",price=69,pageNum=314,publish_id=1)
     
    author_yuan=Author.objects.create(name="yuan",age=23,authorDetail_id=1)
    author_egon=Author.objects.create(name="egon",age=32,authorDetail_id=2)
     
    book_obj.authors.add(author_egon,author_yuan)    #  将某个特定的 model 对象添加到被关联对象集合中。   =======    book_obj.authors.add(*[])
     
    book_obj.save()      

    删除对象:

    Book.objects.filter(id=1).delete()

    #remove(obj1, obj2, ...)从关联的对象集中删除指定的模型对象。
    #clear()从关联的对象集中删除所有的对象。

    多对多关联的第三张表中的字段也一起删除了,默认的级联删除。

    修改对象:

     author = models.Author.objects.filter(id=1)
        author.name = 'mona'
        author.save()

    ---------------- update方法直接设定对应属性----------------
        models.Book.objects.filter(id=3).update(title="PHP")
        ##sql:
        ##UPDATE "app01_book" SET "title" = 'PHP' WHERE "app01_book"."id" = 3; args=('PHP', 3)
    
    

    查寻对象----查询集:

    查询集表示从数据库中取出来的对象的集合,它可以含有零个、一个或者多个过滤器。

    查询集是惰性执行的:创建查询集不会带来任何数据库的访问。

    all(),获取一个表中所有对象

    all_entries = Entry.objects.all()

    filter(**kwargs)返回一个新的查询集,它包含满足查询参数的对象

    Entry.objects.filter(pub_date__year=2006)
    F对象:
    >>> from django.db.models import F
    >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
    #添加  操作符
    >>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
    #Q对象
    list.filter(Q(pk_ _lt=6))
    #字典;
    filter可以引用的模型字段

    链式过滤:

     Entry.objects.filter(
    ...     headline__startswith='What'
    ... ).exclude(
    ...     pub_date__gte=datetime.date.today()
    ... ).filter(
    ...     pub_date__gte=datetime(2005, 1, 30)
    ... )

    get(),返回对象,没有报错

    one_entry = Entry.objects.get(pk=1)

    exclude:返回一个新的QuerySet,它包含不满足给定的查找参数的对象。

    Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
    
    #等同于
    SELECT ...
    WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')

    annotate,查询表达式可以是一个简单的值、模型(或关联模型)字段的一个引用或对查询集中的对象一个聚合函数(平均值、和等)。

    >>> from django.db.models import Count
    >>> q = Blog.objects.annotate(Count('entry'))
    # The name of the first blog
    >>> q[0].name
    'Blogasaurus'

    order_by,默认情况下,QuerySet 根据模型Meta 类的ordering 选项排序,你可以使用order_by 方法给每个QuerySet 指定特定的排序。

    Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')

    distinc,将去除查询结果中重复的行

    values,返回一个QuerySet,每个字典表示一个对象,键对应于模型对象的属性名称。

    # This list contains a dictionary.
    >>> Blog.objects.filter(name__startswith='Beatles').values()
    [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]

    values_list:迭代时返回的是元组,每个元组包含传递给values_list()调用的字段的值 ,第一个元素为第一个字段

    Entry.objects.values_list('id', 'headline')
    [(1, 'First entry'), ...]

    datesdates(field, kind, order='ASC');field应为表中dateField的字段名。 kind应为"year""month""day"隐式的是升序排序

    >>> Entry.objects.dates('pub_date', 'day', order='DESC')
    [datetime.date(2005, 3, 20), datetime.date(2005, 2, 20)]

    select_related返回一个QuerySet,当执行它的查询时它沿着外键关系查询关联的对象的数据。它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询。select_related限于单值关系 - 外键和一对一关系。

    # Hits the database.
    e = Entry.objects.select_related('blog').get(id=5)
    
    # Doesn't hit the database, because e.blog has been prepopulated
    # in the previous query.
    b = e.blog

    prefetch_related,返回QuerySet,它将在单个批处理中自动检索每个指定查找的相关对象。

    from django.db import models
    
    class Topping(models.Model):
        name = models.CharField(max_length=30)
    
    class Pizza(models.Model):
        name = models.CharField(max_length=50)
        toppings = models.ManyToManyField(Topping)
    
        def __str__(self):              # __unicode__ on Python 2
            return "%s (%s)" % (self.name, ", ".join(topping.name
                                                     for topping in self.toppings.all()))
    
    
    Pizza.objects.all().prefetch_related('toppings')
    
    ‘’’
    Pizza .__ str __()要求self.toppings.all()它必须查询数据库,因此Pizza.objects .all()将在Pizza QuerySet中的每个项目的Toppings表上运行查询。
    Pizza.objects.all().prefetch_related('toppings')
    这意味着检索到的每个Pizza都会执行self.toppings.all();现在每次调用self.toppings.all(),而不是去数据库的项目,它会在预取的QuerySet缓存中找到它们填充在单个查询中。
    ‘’‘
    示例

    extra:extra() QuerySet 修改机制,它能在 QuerySet生成的SQL从句中注入新子句

    extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

    Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
    #select语句
    SELECT blog_entry.*, (pub_date > '2006-01-01') AS is_recent
    FROM blog_entry;
    Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
    #select语句

    SELECT * FROM blog_entry WHERE (foo='a' OR bar='a') AND (baz='a')

    defer()具有延迟字段的查询集。

    only()

    Person.objects.defer("age", "biography")
    Person.objects.only("name")

    get_or_create 一个通过给出的kwargs 来查询对象的便捷方法(如果你的模型中的所有字段都有默认值,可以为空),需要的话创建一个对象。

    try:
        obj = Person.objects.get(first_name='John', last_name='Lennon')
    except Person.DoesNotExist:
        obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
        obj.save()
    
    
    ###############
    obj, created = Person.objects.get_or_create(first_name='John', last_name='Lennon',
                      defaults={'birthday': date(1940, 10, 9)})

    update_or_creat,通过给出的kwargs 来更新对象的便捷方法, 如果没有则创建一个新的对象

    try:
        obj = Person.objects.get(first_name='John', last_name='Lennon')
        for key, value in updated_values.iteritems():
            setattr(obj, key, value)
        obj.save()
    except Person.DoesNotExist:
        updated_values.update({'first_name': 'John', 'last_name': 'Lennon'})
        obj = Person(**updated_values)
        obj.save()
    
    ###########################
    obj, created = Person.objects.update_or_create(
        first_name='John', last_name='Lennon', defaults=updated_values)

    count()

    Entry.objects.filter(headline__contains='Lennon').count()

    first()&last():返回结果集的第一个对象, 当没有找到时返回None.

    p = Article.objects.order_by('title', 'pub_date').first()

    exists()如果QuerySet 包含任何结果,则返回True,否则返回False

    if some_queryset.exists():
        print("There is at least one object in some_queryset")

    #它快于下面这样的允许
    if some_queryset:
        print("There is at least one object in some_queryset")

    limit限制查询:

    • 查询集返回列表,可以使用下标的方式进行限制,等同于sql中的limit和offset子句
    • 注意:不支持负数索引
    • 使用下标后返回一个新的查询集,不会立即执行查询
    • 如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()引发DoesNotExist异常
    Entry.objects.all()[5:10]

    字段查找:(了不起的双下划线)

    字段查询是指如何指定SQL WHERE子句的内容. 它们通过查询集filter()exclude() and get()的关键字参数指定.

    exact:精确匹配,iexact:不区分大小写的精确匹配

    Entry.objects.get(id__exact=14)

    contains:区分大小写的包含例子。icontains:不区分大小写

    Entry.objects.get(headline__contains='Lennon')
    
    #sql语句
    SELECT ... WHERE headline LIKE '%Lennon%'

    in:在给定的列表。

    Entry.objects.filter(id__in=[1, 3, 4])
    
    #SELECT ... WHERE id IN (1, 3, 4);

    gt(greater than):大于;gte:大于或等于;lt:小于;lte:小于等于

    Entry.objects.filter(id__gt=4)
    #SELECT ... WHERE id > 4;

    startswith:区分大小写,以...开头;istartswith:不区分大小写

    Entry.objects.filter(headline__istartswith='will')
    #SELECT ... WHERE headline ILIKE 'Will%';

    endswith:区分大小写,以...结束;iendswith:不区分大小写;

    range:包含于之中

    import datetime
    start_date = datetime.date(2005, 1, 1)
    end_date = datetime.date(2005, 3, 31)
    Entry.objects.filter(pub_date__range=(start_date, end_date))
    
    SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';

    year:对于日期和日期时间字段,确切的年匹配。整数年。

    month:对于日期和日期时间字段,确切的月份匹配。取整数1(1月)至12(12月)。

    day:对于日期和日期时间字段,具体到某一天的匹配。取一个整数的天数。

    week_day:对于日期和日期时间字段,“星期几”匹配。取整数值,表示星期几从1(星期日)到7(星期六)。

    hour:对于日期时间字段,精确的小时匹配。取0和23之间的整数。

    Entry.objects.filter(pub_date__year=2005)
    #SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31';
    regex:正则表达式,区分大小写的正则表达式匹配。正则表达式语法是正在使用的数据库后端的语法。iregex:不区分大小写
    Entry.objects.get(title__regex=r'^(An?|The) +')
    #SELECT ... WHERE title REGEXP BINARY '^(An?|The) +'; -- MySQL

    查寻相关的类:

    Q()对象:Q() 对象 使得定义查询条件然后重用成为可能,使用| (OR) 和 &(AND) 操作符; 否则QuerySets中使用不了OR

    #Q对象可以使用&(and)、|(or)操作符组合起来,当操作符应用在两个Q对象时,会产生一个新的Q对象
    list.filter(pk_ _lt=6).filter(bcommet_ _gt=10)
    list.filter(Q(pk_ _lt=6) | Q(bcommet_ _gt=10))
    
    #使用~(not)操作符在Q对象前表示取反
    list.filter(~Q(pk__lt=6))
    
    #可以使用&|~结合括号进行分组,构造做生意复杂的Q对象
    #过滤器函数可以传递一个或多个Q对象作为位置参数,如果有多个Q对象,这些参数的逻辑为and
    #过滤器函数可以混合使用Q对象和关键字参数,所有参数都将and在一起,Q对象必须位于关键字参数的前面

    F对象:可以使用模型的字段A与字段B进行比较,如果A写在了等号的左边,则B出现在等号的右边,需要通过F对象构造

    list.filter(bread__gte=F('bcommet'))

     #F()对象中还可以写作“模型类__列名”进行关联查询

    list.filter(isDelete=F('heroinfo__isDelete'))

     #对于date/time和数字字段进行运算

    list.filter(bpub_date__lt=F('bpub_date') + timedelta(days=1))

    基于对象的跨表查询 :

    一对多:

      正向查询(按字段:publish):

    book_obj=Book.objects.get(nid=1)
    print(book_obj.publish) # book_obj.publish 是nid=1的书籍对象关联的出版社对象  

      反向查询(按表名:book_set)

    publish=Publish.objects.get(name="人民出版社")
      book_list=publish.book_set.all()  # 与人民出版社关联的所有书籍对象集合
     for book_obj in book_list:
            print(book_obj.title)

    一对一查询:

      正向查询

       author_egon=Author.objects.get(name="egon")
       print(author_egon.authorDetail.telephone)

      反向查询

    # 查询所有住址在北京的作者的姓名   
    authorDetail_list=AuthorDetail.objects.filter(addr="beijing")
     for obj in authorDetail_list:
           print(obj.author.name)

    多对多查询:

      正向查询(按字段:authors):

    # 金瓶眉所有作者的名字以及手机号
        book_obj=Book.objects.filter(title="金瓶眉").first()
     
        authors=book_obj.authors.all()
     
        for author_obj in authors:
     
            print(author_obj.name,author_obj.authorDetail.telephone)

      反向查询:

    # 查询egon出过的所有书籍的名字
     
        author_obj=Author.objects.get(name="egon")
        book_list=author_obj.book_set.all() #与egon作者相关的所有书籍
     
        for book_obj in book_list:
            print(book_obj.title)

    基于双下划线的跨表查询:

       # 正向查询 按字段:publish
    
        queryResult=Book.objects.filter(publish__name="人民出版社")
                .values_list("title","price")
    
        # 反向查询 按表名:book
    
        queryResult=Publish.objects.filter(name="人民出版社")
                  .values_list("book__title","book__price")
    # 练习2: 查询egon出过的所有书籍的名字(多对多)
    
        # 正向查询 按字段:authors:
        queryResult=Book.objects
                .filter(authors__name="yuan")
                .values_list("title") # 反向查询 按表名:book queryResult=Author.objects
                  .filter(name="yuan")
                  .values_list("book__title","book__price")


    # 练习3: 查询人民出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 queryResult=Book.objects
                .filter(publish__name="人民出版社")
                .values_list("title","authors__name") # 反向查询 queryResult=Publish.objects
                  .filter(name="人民出版社")
                  .values_list("book__title","book__authors__age","book__authors__name")

    聚合查询:

    from django.db.models import Avg, Max, Min
    
    >>> from django.db.models import Avg, Max, Min
    >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
    {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
    #可以连表,可以反向
     Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))

    聚合和其他查询集字句:

    聚合也可以在过滤器中使用。 作用于普通模型字段的任何 filter()(或 exclude()) 都会对聚合涉及的对象进行限制。

    使用annotate() 子句时,过滤器有限制注解对象的作用

    >>> from django.db.models import Count, Avg
    >>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))

    更多关于聚合的请参考:http://python.usyiyi.cn/documents/django_182/topics/db/aggregation.html

     

  • 相关阅读:
    office 365 sharepoint online 学习资料
    NPOI 导入 导出 Excel
    800703fa Illegal operation attempted on a registry key that has been marked for deletion
    js 获取身份证号码出生日期,籍贯等信息
    直接在浏览器中修改网页内容
    asp.net 按指定模板导出word,pdf
    StackPanel在增加控件的问题
    解决iOS工程被Xcode识别成Mac工程的问题
    Swift之异常处理
    Swift中的协议和闭包
  • 原文地址:https://www.cnblogs.com/mona524/p/7766065.html
Copyright © 2011-2022 走看看