First programmer, then iOS programmer — an interview summary for the vast number of non-professional iOS developers. The questions in the interview summary are really good, so I looked up the resources to learn the next, here is the answer (link -_-), as well as some other principles and divergence.

The problem

  1. If you were asked to implement the weak attribute, how would you do that?
  • If you were to implement atomic properties, how would you do that?
  • Why did KVO create a subclass to implement it?
  • The composition of the class structure, what does the ISA pointer point to? (Metaclass and root metaclass should also be mentioned here.)
  • How many event sources does RunLoop have? How many modes are there?
  • What is the data structure of the method list?
  • How is classification implemented? Why does it override the original method?

1. The principle of the weak

Weak How weak references are implemented

I think this article is very good, I use my own words to summarize: weak is what? When an object is released, all weak Pointers to it are set to nil, so the key is to find all weak Pointers to it from this object.

The system uses a table with the address of the object as the key. The value is the reference count of the object and the weak pointer table. When __weak SomeClass *obj = otherObj is used, the storeWeak method is used to associate the new pointer obj with the otherObj object.

  • Remove the pointer from the weak table of the old object where the old object is fetched
  • Get the weak table of the new object and add the pointer to the weak table

2. Implement atomic

This is a good question from StackOverflow

Simply put, in the getter/setter implementation of a property, the lock is first followed by access to the variable

- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [[userName retain] autorelease];
    }
    return retval;
}

- (void) setUserName:(UITextField *)userName_ { @synchronized(self) { [userName_ retain]; [userName release]; userName = userName_; }}Copy the code

Under the divergent

  • First of all, it’s expensive because you unlock it
  • Doing so, in fact, does not guarantee thread synchronization in many cases, except for the stackOverflow problem aboveThe first answermentionedfirstname+secondnameFor example, there are 5 bags of rice in the warehouse and 10 people go to get it. Each person is equivalent to each thread. Each thread first checks whether there are any more rice and then decides to take use. Atomic can only ensure that you check independently and use independently. What might happen? When the five people checked, the first person did not use the rice, so when the sixth person checked, he thought there were still 5 bags of rice, and then he also went to get the rice, and finally the number of rice became a negative number.

Check and use should be locked as a whole.

lock->check->use->unlock
Copy the code

Atomic is a lock implemented inside a property, which is equivalent to: lock->check->unlock-> other threads may insert… The lock – > use – > – > unlock.

  • Then mentioned@synchronizedJust to explain how it works,Reference here. Simple said.
@synchronized(obj) {
    // do work
}
Copy the code

Again, with a hash table, when you enter the block, you use obj to get the corresponding recursive lock, and then you lock it and unlock it when you exit the block. So this lock is unique to obj’s address.

3. Principle of KVO

The principle reference implements its own KVO reference

  • After you set the observer to object A, assuming that object A is of type ClassA, you will create a temporary subclass subClassA from ClassA, and then override the methods of the property you are observing to change object A’s type to subClassA.
  • The subclass method uses the isa pointer in runtime
  • Back to the question, why implement a subclass?
    • Override properties, how do you override them? For example, setName would become:
void setName:(NSString *)name{
    [self willChangeValueForKey:@"name"];
     [super setName:name];
    [self didChangeValueForKey:@"name"];
}
Copy the code

So it’s going to be willChangeValueForKey and didChangeValueForKey, so you’re going to have to override the original setter method, otherwise the world won’t get the message

  • Then there are two options for rewriting: changing the class and changing the subclass. If you change this class, you will pollute the methods of all other objects in the class
  • I also thought that the rewrite method would be rewritten repeatedly, resulting inwillChangeValueForKeyRepeated nesting, but it is possible to avoid this by setting the representation, such as creating a table in the class to store KVO overridden methods
  • Actually here is a very good idea, I have seen usemethod swizzlingTo pollute the rest of the class, just like in KVO, you can automatically create a subclass, and then your current object method is modified, so that you don’t have to worry about location bugs caused by method tampering elsewhere

4. Isa pointer problem

Just look at this graph:

Ok, next problem! -_ –

5. RunLoop

There are several modes of RunLoop: public kCFRunLoopDefaultMode and UITrackingRunLoopMode the latter is switched to when scrollView is rolled. Here’s a classic question: scrolling causes NSTimer to fail. The above article makes it clear. The events are source, Timer, and Observer

struct __CFRunLoopMode { CFStringRef _name; // Mode Name, for example, @”kCFRunLoopDefaultMode” CFMutableSetRef _sources0; // Set CFMutableSetRef _sources1; // Set CFMutableArrayRef _observers; // Array CFMutableArrayRef _timers; // Array … };

6. Structure of method list

First look at the structure of the class:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if ! __OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
Copy the code

Struct objc_method_list **methodLists struct objc_method_list **methodLists So, the answer to this question says a little bit, but it’s simple: objc_method_list * represents a method chain, which is supposed to be enough for a class, and objc_method_list ** represents n method chains, but that’s because of categories.

When you merge a Category and a class, you can put the Category methods directly in without changing the original method chain.

while(cats->list[I].cat, isMeta);ifMlists [mlist ++] = mlist; mlist [mlist ++] = mlist; fromBundle |= cats->list[i].fromBundle; } } attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected);Copy the code

Personally, I think this is for:

  • Keep the tables of methods separate, such as categories that define the same methods as the class itself and can coexist
  • If you have only one table, you have to add and remove a whole bunch of nodes, and you have to maintain which ones are categories and which ones are classes.

Then there is the structure of the objc_method_list:

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}
Copy the code

There is no reference here, just look at the runtime open source code.

/* These next three functions are the heart of ObjC method lookup. */
static inline Method _findMethodInList(struct objc_method_list * mlist, SEL sel) {
    int i;
    if(! mlist)return NULL;
    for (i = 0; i < mlist->method_count; i++) {
	Method m = &mlist->method_list[i];
	if (m->method_name == sel) {
	    returnm; }}return NULL;
}
Copy the code

The above function finds the corresponding Method from objc_method_list, which shows that the Method is stored in method_list. Before I looked at the code, I thought objc_method_list was actually a node in a linked list, and each method_list only stores one method, and then I obsolete the next method.

7. Principle of Category

Refer to this article

  • Merge the methods, attributes, and protocols of a category with the original class;
  • For properties and protocols, it’s ok to concatenate lists
newproperties = buildPropertyList(NULL, cats, isMeta);
        if (newproperties) {
            newproperties->next = cls->data()->properties;
            cls->data()->properties = newproperties;
        }

        newprotos = buildProtocolList(cats, NULL, cls->data()->protocols);
        if(cls->data()->protocols && cls->data()->protocols ! = newprotos) { _free_internal(cls->data()->protocols); } cls->data()->protocols = newprotos;Copy the code
  • For methods, put the list of methods for all categories in the list of lists (method_list_t **), and then put the class’s original list of methods in it
// Copy old methods to the method list array
    for (i = 0; i < oldCount; i++) {
        newLists[newCount++] = oldLists[i];
    }
Copy the code
  • So the question of why it is overwritten is solved: it is not overwritten, but the list of methods in the class itself is put behind, hidden by hysteresis. As you can probably guess, you can’t get rid of the methods of the class, otherwise the methods are lost, and now, when the category is removed, the methods of the class are exposed again.

  • There is a problem with loading categories in static libraries, and this answer is very good. Simply put, a category is not an identifier that the compiler uses to confirm loading

Categories are a runtime-only feature, categories aren’t symbols like classes or functions and that also means a linker cannot determine if a category is in use or not.

The solution is to add -objc,-force_load or -all_load to Other Linker Flags. -objc is to load all OC code files,-force_load is to load all files, -all_load is to load all files.

Other related questions

  • Principle of automatic release pool: see this article

To start, create a bidirectional list of type AutoreleasePoolPage, which will hold all objects that use the __autoRELEASING flag (MRC calls autoRelease directly). In effect, create a new node to add to the list

static inline id *autoreleaseFast(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    if(page && ! page->full()) {return page->add(obj);
    } else if (page) {
        return autoreleaseFullPage(obj, page);
    } else {
        returnautoreleaseNoPage(obj); }}Copy the code

Release each object after the pool is finished.

  • Associated Objects also uses hash tables.
  • For some debugging, LLDB can achieve special effects, see this article