Some of the skills learned from working, referring to source code, looking at various materials, and being bat tered
I have a summary of my own may be more detailed, but it seems too verbose too much, it also lists some of the most important, some specific details are not detailed, if necessary, you can leave a message to discuss. The article will be constantly updated, adding some skills.
I've been doing ios for more than two years, and it's just a little summary. I haven't got the open source code yet. Hey, I really think I'm rubbish.
Fantasize about a day like yykit. Ha-ha. Step by step. If you walk more, the road will come out. I think there are goals.
Common time-consuming code types on the main thread:
-
(1) Creation, adjustment and abandonment of various NSObject objects
- UIKit Obejcts UI object
- Property Value Adjustment of UI Objects
- Creation of UI Objects
- Destruction (obsolescence) of UI objects
- Discarding other file data and cached objects
-
(2) Layout layout calculation
- Computing width and height of text content
- frmae calculation
- Frmae settings, frmae adjustments
-
(3) Rendering display data rendering
- Rendering of Text Content
- Decoding of Pictures
- Drawing of Graphics
As far as possible, as far as possible, as far as possible, asynchronous execution of sub-threads, but the operation of UI objects must be placed in the main thread.
Asynchronous Release of Discarded Objects in YYMemoryCache in Subthreads
- (void)removeAll { _totalCost = 0; _totalCount = 0; _head = nil; _tail = nil; if (CFDictionaryGetCount(_nodeMap) > 0) { CFMutableDictionaryRef holder = _nodeMap; _nodeMap = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (_releaseAsynchronously) { if (_releaseOnMainThread && !pthread_main_np()) { dispatch_async(dispatch_get_main_queue(), ^{ CFRelease(holder); }); } else { dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : XZHMemoryCacheGetReleaseQueue(); dispatch_async(queue, ^{ CFRelease(holder); }); } } else { CFRelease(holder); } } }
Previously, I had a small try in some places in the project, and one person saw that you would crash if you wrote like this. I laughed. Forget it, don't write it, just release it synchronously so that they won't find this little trick.
Summary of the implementation of KVO
-
(1) When the attributes of an object are added to the attributes observer
-
(2) When the program runs, the system creates a subclass of the class to which the object belongs through the runtime function. The name format is NSKVONotifying_original class name.
-
(3) At this time, the objc class object - > isa pointer, which will be observed by adding attributes, will point to the objc class created above (2), and will no longer point to the previous objc class.
-
(4) The class NSVONotifying_original class name created at runtime overrides the setter method implementation of the observed property in the original parent class. For example:
- (void)setType:(NSSting *)type { //1. Implementing method of setting attribute value by calling parent Cat [super setType:type]; //2. Notify the observer of the Cat object to perform callbacks (synchronous execution from the breakpoint effect) [cat Observers of objects observeValueForKeyPath:@"type" ofObject:self change:@{} context:nil]; }
-
(5) When the value of the observed attribute of an object changes (the setter method implementation of the intermediate class is called), the observeValueForKeyPath: ofObject:change:context: method implementation of the observer is called back, and it is called synchronously.
-
(6) The objc_class structure instance returned by the following two methods is different
Object_getClass > > > returns the replaced `intermediate Class'> > > because it reads lass pointed to by isa
[Observed Object Class] > > > > > Still will be before the `primitive class', this Class should be standby
- (7) When the attribute observer is removed from the object, the isa pointer of the object will be restored to the original class.
IMP Caching
First, there is a test class:
@interface Person : NSObject + (void)logName1:(NSString *)name; - (void)logName2:(NSString *)name; @end @implementation Person + (void)logName1:(NSString *)name { NSLog(@"log1 name = %@", name); } - (void)logName2:(NSString *)name { NSLog(@"log2 name = %@", name); } @end
Then ViewController tests IMP Caching:
#import <objc/runtime.h> static id PersonClass = nil; static SEL PersonSEL1; static SEL PersonSEL2; static IMP PersonIMP1; static IMP PersonIMP2; @implementation ViewController + (void)initialize { PersonClass = [Person class]; PersonSEL1 = @selector(logName1:); PersonSEL2 = @selector(logName2:); PersonIMP1 = [PersonClass methodForSelector:PersonSEL1]; PersonIMP2 = method_getImplementation(class_getInstanceMethod(PersonClass, PersonSEL2)); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { ((void (*)(id, SEL, NSString*)) (void *) PersonIMP1)(PersonClass, PersonSEL1, @"I am a parameter."); ((void (*)(id, SEL, NSString*)) (void *) PersonIMP2)([Person new], PersonSEL2, @"I am a parameter."); NSLog(@""); } @end
Output results
2017-02-08 22:47:46.586 Test[805:25490] log1 name = I am a parameter 2017-02-08 22:47:46.587 Test[805:25490] log2 name = I am a parameter
When encountering multiple selection conditions, we should try our best to use look-up table method to realize it.
For example, switch/case, C Array, if the lookup table condition is an object, it can be implemented with NSDictionary.
For example, there are many if-elseif judgments as follows:
NSString *name = @"XIONG"; if ([name isEqualToString:@"XIAOMING"]) { NSLog(@"task 1"); } else if ([name isEqualToString:@"LINING"]) { NSLog(@"task 2"); } else if ([name isEqualToString:@"MAHANG"]) { NSLog(@"task 3"); } else if ([name isEqualToString:@"YHAHA"]) { NSLog(@"task 4"); }
Using NSDictionary+Block to encapsulate the key-value pair relationship between the condition and the execution code block:
NSDictionary *map = @{ // Key of if condition: block encapsulated by code to be executed by if condition @"XIONG1" : ^() {NSLog(@"task 1");}, @"XIONG2" : ^() {NSLog(@"task 2");}, @"XIONG3" : ^() {NSLog(@"task 3");}, @"XIONG4" : ^() {NSLog(@"task 4");}, };
When building key s, be careful not to resemble each other as much as possible. The bigger the gap, the better.
The NSObject class we wrote, the process of loading when the program is running
//1. Create a runtime-aware Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes); //2. Adding instance variables BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types); //3. Adding Instance Method BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types); //4. Adding Implementation Protocol BOOL class_addProtocol(Class cls, Protocol *protocol); //5. [Important] Register the processed lass into the runtime system, and then you can't modify [Ivar] void objc_registerClassPair(Class cls);
Once objc_registerClassPair() is executed, the layout of Ivar can no longer be changed.
Read and write Ivar > Send getter/setter message > KVC
@interface Dog : NSObject @property (nonatomic, copy) NSString *name; @end @implementation Dog @end
- (void)test { //1. Get Ivar. Note: Names start with underscores by default. // eg: _name,_age ... Ivar ivar = class_getInstanceVariable([Dog class], "_name"); //2. Dog *dog = [Dog new]; //3. Write Ivar directly object_setIvar(dog, ivar, @"I'm your mother."); //4. Read Ivar directly NSLog(@"name = %@", object_getIvar(dog, ivar)); }
Direct manipulation of Ivar completely bypasses the objc messaging process. But when I encapsulated Runtime's code, I tried to set the Ivar value by sending getter/setter messages. I changed all the code to read and write Ivar directly. Unexpectedly, the result was 3-4 times slower than before, and the more times I repeated it, the slower it would be.
Suddenly I remembered that objc's message delivery is cache optimized, and direct reading and writing Ivar is called directly by c method every time. I guess that's where the problem lies. Here are the tests:
- (void)testKVC4 { Dog *dog = [Dog new]; dog.name = @"name1111"; dog.uid = 1111; dog.age = 19; dog.url = [NSURL URLWithString:@"www.baidu.com"]; int count = 1000000; double date_s = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < count; i++) { NSString *name = [NSString stringWithFormat:@"name_%d", i]; //1. stters messgae dog.name = name; //2. Ivar Ivar ivar = class_getInstanceVariable([dog class], "_name"); object_setIvar(dog, ivar, name); } double date_current = CFAbsoluteTimeGetCurrent() - date_s; NSLog(@"consumeTime: %f μs",date_current * 11000 * 1000); }
The time consumed by sending getter/setter messages is:
consumeTime: 10366509.914398 μs
With Ivar, the time consumed is:
consumeTime: 12702172.517776 μs
This is still the simplest case, only repeat the value setting of the property Ivar, if many Ivar settings, and repeat many times, the difference will be greater.
Therefore, for Ivar that reads and writes repeatedly, it is faster to read and write by sending getter/setter messages.
When traversing NSArray/NSSet/NSDictionary container objects, it will be more efficient to turn them into CoreFoundation container objects and traverse them again.
struct Context { void *info; //Note: objc object types cannot be defined in c struct }; void XZHCFDictionaryApplierFunction(const void *key, const void *value, void *context) { struct Context *ctx = (struct Context *)context; NSMutableString *info = (__bridge NSMutableString*)(ctx->info); NSString *key_ = (__bridge NSString*)(key); NSString *value_ = (__bridge NSString*)(value); [info appendFormat:@"%@=%@", key_, value_]; } @implementation ViewController - (void)test { NSDictionary *map = @{ @"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3", @"key4" : @"value3", }; NSMutableString *info = [[NSMutableString alloc] init]; struct Context ctx = {0}; ctx.info = (__bridge void*)(info); CFDictionaryApplyFunction((CFDictionaryRef)map, XZHCFDictionaryApplierFunction, &ctx); } @end
It also completely bypasses objc messaging.
Put some less important code into idle to execute
- (void)idleNotificationMethod { // do something here } - (void)registerForIdleNotification { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(idleNotificationMethod) name:@"Custom Notification key" object:nil]; NSNotification *notification = [NSNotification notificationWithName:@"Custom Notification key" object:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle]; }
Use release pools on custom threads
+ (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; } + (void)networkRequestThreadEntryPoint:(id)__unused object { // Use release pool to package @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } }
For self-created NSThread s, it is important to create release pools manually. You can also listen for runloop status changes of subthreads, create release pools at initialization, rebuild release pools at rest, and discard release pools at exit.
Interface Rendering Mechanism Referring to YYaynac Llayer Source Understanding
-
(1) Registration focuses on the following state changes of MainRunLoop, which is the most idle time of MainRunLoop.
- KCFRun Loop BeforeWaiting is about to take a break
- KCFRun LoopExit is about to quit
-
(2) The UIView object to be redrawn, which attribute values (font, textColor, backgroudColor...) are packaged into a CATransaction object by CoreAnimation
- (2.1) CALayer specifying which UIView object
- (2.2) Which attribute values (font, textColor, backgroudColor...) are to be triggered for redrawing
- (2.3) Submitted by [CATransaction commit]
-
(3) After the CATransaction submission, it is temporarily saved to a temporary buffer, similar to the NSSet collection.
-
(4) Waiting for MainRunLoop to be in kCFRunLoopBeforeWaiting or kCFRunLoopExit state callback, then registering the message sending event of the CATransaction object stored in the temporary buffer to MainRunLoop
- Just sign up to MainRunLoop, and MainRunLoop doesn't process messaging immediately
- After registration, MainRun Loop rested.
-
(5) Waiting for MainRunLoop to wake up and start a new round of loops, the message sending event of the last registered CATransaction object will be handled.
Using RunLoop in the most leisure time, multiple redrawing operations are cut into batches to redraw according to each cycle of RunLoop.
Use _unsafe_unretained to modify the pointer variables pointing to objects that will not be abandoned, and ARC system will not be attached to retain/release processing, which improves the speed of operation.
-
(1) When using _weak modified pointer variable to point to the object, the pointed object will be automatically registered into the automatic release pool to prevent it from being discarded when used, but it affects the efficiency of code execution.
-
(2) If an object is certain that it will not be discarded, or that it will not be discarded before the call is completed, use _unsafe_unretained to modify pointer variables
-
(3) _unsafe_unretained is a simple copy address, without any object memory management, that is, without modifying retainCount
_ c function pointer for message forwarding in objc_msgForward iOS system
There's something similar to that:
_objc_msgForward_stret
jspatch is precisely realized by using this _objc_msgForwardc Method to exchange the SEL-pointed IMP of any Method.
When an oc class cannot find an IMP corresponding to a SEL, it enters the message forwarding function of the system.
In the following test, how does _objc_msgForward forward messages?
First, there are the following test classes:
@interface Person : NSObject + (void)logName1:(NSString *)name; - (void)logName2:(NSString *)name; @end @implementation Person + (void)logName1:(NSString *)name { NSLog(@"log1 name = %@", name); } - (void)logName2:(NSString *)name { NSLog(@"log2 name = %@", name); } @end
The SEL message in ViewController that executes a Perosn object arbitrarily without implementation:
@implementation ViewController - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // Break a breakpoint here, then execute the following lldb debugging command, and then execute the code down static Person *person; person = [Person new]; [person performSelector:@selector(hahahaaha)]; NSLog(@""); } @end
After the breakpoint, enter the following debugging command in lldb and print out all messages sent at runtime:
(lldb) call (void)instrumentObjcMessageSends(YES)
After the program crashes, enter the following directory of the Mac computer system:
cd /tmp/
Find a file in the directory that looks like the following structure, and open it
msgSends-901
When you open the file, just look at the information related to Person as follows:
+ Person NSObject initialize + Person NSObject new - Person NSObject init - Person NSObject performSelector: + Person NSObject resolveInstanceMethod: + Person NSObject resolveInstanceMethod: - Person NSObject forwardingTargetForSelector: - Person NSObject forwardingTargetForSelector: - Person NSObject methodSignatureForSelector: - Person NSObject methodSignatureForSelector: - Person NSObject class - Person NSObject doesNotRecognizeSelector: - Person NSObject doesNotRecognizeSelector: - Person NSObject class
From Person NSObject performance Selector: Start executing a SEL message that does not exist, and then proceed in turn:
- (1) resolveInstanceMethod: or resolveClassMethod:
- (2) forwardingTargetForSelector:
- (3) methodSignatureForSelector:
- (4) forwardInvocation:
So, the pointer _objc_msgForward points to the c function implementation responsible for completing the entire objc message forwarding, including stage 1 and stage 2.
hitTest:withEvent: and pointInside:withEvent:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { // 1. Determine whether you can receive touch events (whether to open event interaction, whether to hide, whether transparent) if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil; // 2. Call pointInside:withEvent:, and determine that the touchpoint is not in its own frame. if (![self pointInside:point withEvent:event]) return nil; // 3. Walk through all your child controls from `top to bottom'(top to start) to see if any child controls are better suited to respond to this event. int count = self.subviews.count; for (int i = count - 1; i >= 0; i--) { UIView *childView = self.subviews[i]; // Converts the coordinates of the event to the coordinates of the origin of the current relative subview's own coordinates CGPoint childPoint = [self convertPoint:point toView:childView]; // Continue to give each subview hitTest UIView *fitView = [childView hitTest:childPoint withEvent:event]; // If the child View subviews exist that can handle events, the child View object currently traversed is returned as the event processing object. if (fitView) { return fitView; } } //4. Not finding a better view than yourself return self; }
The main thing is to test whether the UIView object can handle the UI touch event.
-[NSObject class],+[NSObject class],objc_getClass("class_name")
- [NSObject class] source code implementation
- (Class) class { return object_getClass(self); }
+ [NSObject class] source code implementation
+ (Class) class { return self; }
Source code implementation of objc_getClass (< const # char * name #>)
Class object_getClass(id obj) { if (obj) return obj->getIsa();//Read the isa pointer, pointing to the objc_class instance else return Nil; }
Category does not override the original method implementation and multiple Categories rewrite the same method implementation invocation order
Main issues involved
1. Will Category override Method in the original Class? 2. Multiple Categories add the same Method. What is the order of invocation?
1. When Category overrides existing method implementations in the original class
-
(1) The overridden Method in Category will definitely be at the top of the Method in the primitive class
-
(2) Whenever a Category rewrite Method is read from the compilation path, the rewritten Method is inserted into the first place of the method_list of the original class by header insertion.
That is to say, the Method rewritten in Category compiled later will appear at the first place of method_list.
2. In multiple Categories, the same method implementation is rewritten
The Method is headed into the method_list single list of the original class in the order of Category in the compilation path.
So, the later Category appears, the rewritten Method will appear in the first place.
Scene triggering off-screen rendering of CPU and GPU
CPU triggers off-screen rendering
-
(1) Drawing images using CoreGraphics library functions
-
(2) Rewrite any drawing code written in the implementation of the -[UIView drawRect] method
- Even empty method implementations can trigger
GPU triggered off-screen rendering
-
(1) CALayer object setting shouldRasterize (rasterization)
-
(2) CALayer object setting masks (masks)
-
(3) CALayer object setting shadows (shadows)
-
(4) CALayer object set group opacity (opacity)
-
(5) Drawing all text (UILabel, UITextView...), including CoreText drawing text, TextKit drawing text
It's better not to rewrite - [UIView drawRect:] to render text and graphics, but to use a dedicated layer for rendering.
Why not rewrite drawRect: Draw it?
-
(1) Just rewrite drawRect:, creating an empty host image for layer wastes memory
-
(2) Image rendering of CoreGraphics triggers off-screen rendering of CPU, and the poor image rendering ability of CPU will affect the efficiency of CPU execution (but can be optimized asynchronously in sub-threads).
-
(3) Special layer uses OpenGL to operate GPU to complete image rendering and memory optimization.
Below is a code example that uses a dedicated layer to draw custom paths:
@implementation XingNengVC { UIView *_bottomView; } - (void)viewDidLoad { [super viewDidLoad]; //1. Create UIView containers _bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 200)]; [self.view addSubview:_bottomView]; //2. Create specific layers for drawing images CAShapeLayer *layer = [CAShapeLayer layer]; layer.frame = _bottomView.bounds; //3. Setting the image path to be drawn UIBezierPath *path = [[UIBezierPath alloc] init]; [path moveToPoint:CGPointMake(175, 100)]; [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES]; [path moveToPoint:CGPointMake(150, 125)]; [path addLineToPoint:CGPointMake(150, 175)]; [path addLineToPoint:CGPointMake(125, 225)]; [path moveToPoint:CGPointMake(150, 175)]; [path addLineToPoint:CGPointMake(175, 225)]; [path moveToPoint:CGPointMake(100, 150)]; [path addLineToPoint:CGPointMake(200, 150)]; //4. Set the path to be drawn to layer layer.path = path.CGPath; //3. [_bottomView.layer addSublayer:layer]; } @end
The release and abandonment of objc objects are two different stages
release
It should release the holding of the object, that is, send retain release autorelase and other messages to the objc object, modify the retainCount value of the objc object, but the memory of the object always exists.
The operation of releasing holdings is synchronous.
Discard
When a certain idle time, the system will erase all the data in memory, and then merge this memory into unused memory of the system. At this time, if the program continues to access the memory block, it will cause the program to crash.
The complete abandonment of memory is asynchronous, that is to say, there is a certain delay.
The execution of -[NSObject dealloc] does not mean that the memory in which the object resides is discarded. It's just common sense that this object has been marked as being discarded and should not be used again in the program.
- (void)testMRC { _mrc = [[MRCTest alloc] init]; NSLog(@"[_mrc retainCount] = %lu", [_mrc retainCount]); MRCTest *tmp1 = [_mrc retain]; NSLog(@"[_mrc retainCount] = %lu", [_mrc retainCount]); [_mrc release]; NSLog(@"[_mrc retainCount] = %lu", [_mrc retainCount]); [tmp1 release]; NSLog(@"[_mrc retainCount] = %lu", [_mrc retainCount]); //[Important] Attempt to output retainCount multiple times for (NSInteger i = 0; i < 10; i++) { NSLog(@"[_mrc retainCount] = %lu", [_mrc retainCount]);//[Important] After several cycles of execution, it crashes to this trip. } }
After running, the result crashes to the second or third cycle in the for loop, and the program crashes with the following errors:
thread 1:EXC_BAD_ACCESS ....
So abandonment is a delayed stage.
Implementation of weak reference for objc object
- (1) NSValue
- (2) block + __weak
- (3) Message forwarding of NSProxy or NSObject
Specific will not start.
FMDatabaseQueue resolves dispatch_sync(queue, ^() {}); may cause multithreaded deadlocks
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
- (void)inDatabase:(void (^)(FMDatabase *db))block { FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock"); // The code for dispatch_sync() follows... //....... }
Something similar to NSThread.threadDictionary.
Simulated multiple inheritance
There are three abstract interfaces
@protocol SayChinese <NSObject> - (void)sayChinese; @end
@protocol SayEnglish <NSObject> - (void)sayEnglish; @end
@protocol SayFranch <NSObject> - (void)sayFranch; @end
There are three implementations in-house, but I hope I don't expose the implementations, but I know the implementations internally.
@interface __SayChineseImpl : NSObject <SayChinese> @end @implementation __SayChineseImpl - (void)sayChinese { NSLog(@"Speak Chinese"); } @end
@interface __SayEnglishImpl : NSObject <SayEnglish> @end @implementation __SayEnglishImpl - (void)sayEnglish { NSLog(@"Speak English"); } @end
@interface __SayFranchImpl : NSObject <SayFranch> @end @implementation __SayFranchImpl - (void)sayFranch { NSLog(@"Speaking French"); } @end
Exposing a class to manipulate all methods of the above three classes, i.e. the effect of multiple inheritance
/** * Simulated inheritance of multiple languages */ @interface SpeckManager : NSObject <SayChinese, SayEnglish, SayFranch> @end @implementation SpeckManager { id<SayChinese> _sayC; id<SayEnglish> _sayE; id<SayFranch> _sayF; } - (instancetype)init { self = [super init]; if (self) { _sayC = [__SayChineseImpl new]; _sayE = [__SayEnglishImpl new]; _sayF = [__SayFranchImpl new]; } return self; } - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(sayChinese)) { return _sayC; } if (aSelector == @selector(sayEnglish)) { return _sayE; } if (aSelector == @selector(sayFranch)) { return _sayF; } return [super forwardingTargetForSelector:aSelector]; } @end
Isomorphic Judgment Writing Template for objc Objects
Instance Method of Rewriting NSObject
- (1) isEqual:
- (2) hash
@implementation Person - (instancetype)initWithPid:(NSString *)pid Age:(NSInteger)age Name:(NSString *)name { self = [super init]; if (self) { _pid = [pid copy]; _name = [name copy]; _age = age; } return self; } - (BOOL)isEqualToPerson:(Person *)person { if (self == person) return YES; if (_age != person.age) return NO; if (![_name isEqualToString:person.name]) return NO; if (![_pid isEqualToString:person.pid]) return NO; return YES; } - (BOOL)isEqual:(id)object { if ([self class] == [object class]) { isEqualToPerson: return [self isEqualToPerson:(Person *)object]; } else { return [super isEqual:object]; } } - (NSUInteger)hash { NSInteger ageHash = _age; NSUInteger nameHash = [_name hash]; NSUInteger pidHash = [_pid hash]; return ageHash ^ nameHash ^ pidHash; } @end
After such rewriting, adding to NSSet only saves the same hash worth object, which can be used to filter duplicate values.
Moreover, it can be realized by using runtime to parse json, Ivar, and automatically rewrite the above functions.
The Use of Class Clusters in NSArray and NSMutable Array
- (void)testArrayAllocInit { id obj1 = [NSArray alloc]; id obj2 = [NSMutableArray alloc]; id obj3 = [obj1 init]; //id obj4 = [obj1 initWithCapacity:16]; // crash, because obj1 is not _NSArrayM id obj5 = [obj1 initWithObjects:@"1", nil]; id obj6 = [obj2 init]; id obj7 = [obj2 initWithCapacity:16]; id obj8 = [obj2 initWithObjects:@"1", nil]; NSLog(@""); }
The output is as follows
(__NSPlaceholderArray *) obj1 = 0x00007ff79bc04b20 (__NSPlaceholderArray *) obj2 = 0x00007ff79bc06d00 (__NSArray0 *) obj3 = 0x00007ff79bc02eb0 (__NSArrayI *) obj5 = 0x00007ff79be0fb60 @"1 object" (__NSArrayM *) obj6 = 0x00007ff79be1e350 @"0 objects" (__NSArrayM *) obj7 = 0x00007ff79be1e380 @"0 objects" (__NSArrayM *) obj8 = 0x00007ff79be22db0 @"1 object"
As you can see, the final printed Class is not NSArray, NSMutable Array, which is the cluster pattern.
But one obvious feature is that both [NSArray alloc] and [NSMuatble Array alloc] get objects of type _NSPlaceholder Array
Looking at the iOS Advanced Memory Management Programming Guide about class object clusters, we get the following pseudo-code implementations:
static __NSPlacehodlerArray *GetPlaceholderForNSArray() { static __NSPlacehodlerArray *instanceForNSArray; if (!instanceForNSArray) { instanceForNSArray = [__NSPlacehodlerArray alloc]; } return instanceForNSArray; }
static __NSPlacehodlerArray *GetPlaceholderForNSMutableArray() { static __NSPlacehodlerArray *instanceForNSMutableArray; if (!instanceForNSMutableArray) { instanceForNSMutableArray = [__NSPlacehodlerArray alloc]; } return instanceForNSMutableArray; }
@implementation NSArray + (id)alloc { if (self == [NSArray class]) { return GetPlaceholderForNSArray(); } } @end
@implementation NSMutableArray + (id)alloc { if (self == [NSMutableArray class]) { return GetPlaceholderForNSMutableArray(); } } @end
@implementation __NSPlacehodlerArray - (id)init { if (self == GetPlaceholderForNSArray()) { self = [[__NSArrayI alloc] init]; } else if (self == GetPlaceholderForNSMutableArray()) { self = [[__NSArrayM alloc] init]; } return self; } @end
You should understand. Look at the inheritance structure of these array s:
- NSArray - NSMutableArray - __NSPlaceholderArray - __NSArrayM - __NSArrayI - __NSArray0
Therefore, one of the core of the cluster pattern is to implement different implementations based on Inheritance and rewriting subclasses. Then, different implementations are registered with the outermost cluster classes.
When locking in loops such as for/whil e, you need to use tryLock instead of using lock directly, otherwise thread deadlock may occur.
while (!finish) { if (pthread_mutex_trylock(&_lock) == 0) { // Cached data read and write //..... pthread_mutex_unlock(&_lock); } else { usleep(10 * 1000); } pthread_mutex_unlock(&_lock); }
Deep copy, shallow copy, variable, immutable
I won't go into details. I wrote a specific analysis before, and then deleted it. I'll send you a diagram of the relationship between the four that I finally summarized.
Getting block's Cluster Class
static Class GetNSBlock() { static Class _cls = Nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ id block = ^() {}; _cls = [block class]; while (_cls && (class_getSuperclass(_cls) != [NSObject class])) { _cls = class_getSuperclass(_cls); } }); return _cls; }
It can also be used to obtain other types of cluster classes.
block Loop References Resolved without _weak
Testing Entity Classes
@interface MyDog : NSObject @property (nonatomic, strong) NSString *name; @end @implementation MyDog - (void)dealloc { NSLog(@"%@ - %@ dealloc", self, _name); } @end
The first is to automatically release the discarded block and capture external objects.
@implementation BlockViewController - (void)test9 { //1. MyDog *dog = [MyDog new]; dog.name = @"ahahha"; //2. void (^block)(void) = ^() { NSLog(@"name = %@", dog.name); }; //3. block(); } @end
Output after operation
2017-03-22 13:33:42.665 Demos[8797:111915] name = ahahha 2017-03-22 13:33:42.665 Demos[8797:111915] <MyDog: 0x600000007ca0> - ahahha dealloc
It is possible to discard the objects held outside normally.
Second, when a block is held by another object, it captures the external object.
@implementation BlockViewController { void (^_ivar_block)(); } - (void)test10 { //1. MyDog *dog = [MyDog new]; dog.name = @"ahahha"; //2. _ivar_block = ^() { NSLog(@"name = %@", dog.name); }; //3. _ivar_block(); } @end
Output after operation
2017-03-22 13:37:55.803 Demos[8844:114742] name = ahahha
The dealloc information of the MyDog object is not output.
To solve the problem that MyDog objects are not discarded, we do not use _weak to modify the pointers of external objects:
@implementation BlockViewController { void (^_ivar_block)(); } - (void)test11 { //1. MyDog *dog = [MyDog new]; dog.name = @"ahahha"; //2. _ivar_block = ^() { NSLog(@"name = %@", dog.name); }; //3. _ivar_block(); //4. Must be executed in the end _ivar_block = nil; } @end
Look at the run output first
2017-03-22 13:39:21.803 Demos[8877:116163] name = ahahha 2017-03-22 13:39:21.803 Demos[8877:116163] <MyDog: 0x600000203480> - ahahha dealloc
Basically, it's clear that _weak is not used to solve the problem of block s that strongly refer to external objects, resulting in no release.
Three Timers: 1)NSTimer 2)CADisplayLink 3)dispatch_source_t
Simple enumeration, specific can be their own search.
1) and 2) both must be registered manually to a certain RunLoop mode l, affected by RunLoop state changes and reincarnation. The biggest difference between them is that NSTimer specifies an interval manually and is influenced by Run Loop, which is not necessarily accurate. CADisplayLink is a timer that can perform some tasks synchronously with the screen drawing frequency.
3) At least during our coding period, we did not operate on any RunLoop, and directly dispatch_resume(source), and the source can be accurately interval callback, not affected by the main thread RunLoop.
However, I think it should be registered with RunLoop. It may be Common Modes or higher order. Personal guesses need to be verified.
Refer to the company's modular development to roughly achieve
Modularization I. Abstraction of each module that can be accessed
@protocol NVMModuleManager <NSObject> /// Implement this method and register the current module into the module manager - (void)registerServices; @optional
Modularization 2. Use objc_getClassList to read all objc classes in the current project and find objc classes that implement protocols that become separate modules
int numClasses; Class *classes = NULL; classes = NULL; numClasses = objc_getClassList(NULL, 0); NSLog(@"Number of classes: %d", numClasses); if (numClasses > 0 ) { // Open up memory space classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses); // Read all Class data to memory objc_getClassList(classes, numClasses); // Traversing to get each Class, for (int i = 0; i < numClasses; i++) { Class cls = classes[i]; // See if Module Module Module Protocol is implemented. if (class_conformsToProtocol(cls, @protocol(NVMModuleManager))) { // If the protocol is implemented, it is a Module. id<NVMModuleManager> module = [cls alloc]; // The Method of Callback Its Registered Module [module registerServices]; // Save the registered Module [modules addObject:module]; } } // Release system memory free(classes); }
Modularization 3. Adding a specific module
Firstly, we propose a separate Manager entry class for this Module and implement the Module protocol NVMModule Manager, which has the ability to be a Module.
// Implement the above module protocol to complete module registration @interface NVMBreakfastModuleManager : NSObject <NVMModuleManager> @end @implementation NVMBreakfastModuleManager - (void)registerServices { // Call the registration method of the module manager Router object to complete the registration of the current sub-module //1. Each module has a unique url //2. Set the block when the current module is called by the main other module or the main App [NVMExternalRouter() registerRoute:@"eleme://breakfast" forBlock:^(NVMRouteURL * _Nonnull routeURL) { // Create ViewController for the current module NVMRetailHomeViewController *vc = [[NVMRetailHomeViewController alloc] init]; // Call encapsulated, get topViewController and complete pushVC code to complete VC rendering [NVMUIKit showViewController:vc animationType:NVMAnimationTypePush]; }]; }
Modularization 4. Router singleton object manages all modules
- (1) Each ModuleManager implementation class object must correspond to a unique url
- (2) Router finds the corresponding callback block through url, and then executes the call
- (3) Between different modules, the corresponding url can also be transferred through Router to complete mutual calls.
- (4) The parameters passed follow the url. eg: eleme://breakfast/sharing? id=111
- (5) Implementation of jumps between App s on similar URLs Scheme
The general idea of implementation is this, and there may be some specific details. For example, the implementation of Router and VC jump logic between each module.
Compressed pictures such as PNG are decompressed, decoded and pre-processed by sub-threads in advance, and the processed images can be cached.
@implementation UIImageHelper - (void)decompressImageNamed2:(NSString *)name ofType:(NSString *)type completion:(void (^)(UIImage *image))block { XZHDispatchQueueAsyncBlockWithQOSBackgroud(^{ NSDictionary *dict = @{ (id)kCGImageSourceShouldCache : @(YES) }; NSString *filepath = [[NSBundle mainBundle] pathForResource:name ofType:type]; NSURL *url = [NSURL fileURLWithPath:filepath]; CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, NULL); CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)dict); size_t width = CGImageGetWidth(cgImage); size_t height = CGImageGetHeight(cgImage); size_t bytesPerRow = roundUp(width * 4, 16); size_t byteCount = roundUp(height * bytesPerRow, 16); if (width == 0 || height == 0) { CGImageRelease(cgImage); dispatch_async(dispatch_get_main_queue(), ^{ block(nil); }); return; } void *imageBuffer = malloc(byteCount); CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef imageContext = CGBitmapContextCreate(imageBuffer, width, height, 8, bytesPerRow, colourSpace, kCGImageAlphaPremultipliedLast); CGColorSpaceRelease(colourSpace); CGContextDrawImage(imageContext, CGRectMake(0, 0, width, height), cgImage); CGImageRelease(cgImage); CGImageRef outputImage = CGBitmapContextCreateImage(imageContext); CGContextRelease(imageContext); free(imageBuffer); dispatch_async(dispatch_get_main_queue(), ^{ UIImage *image = [UIImage imageWithCGImage:outputImage]; block(image); CGImageRelease(outputImage); }); }); } @end
For UI with less user interaction and less complex typesetting, it is accomplished directly in the form of CALayer+CoreText+CoreGrapgics+sub-threads.
CALayer, you can consider using YYAsync Layer
YYAsyncLayer cuts and redraws the screen several times based on RunLoop cycle, and a CGContext has been created internally. We can draw arbitrarily in this CGContext (text, image, custom path, image...). The process of drawing includes the final rendering to CGImageRef. Processes are completed asynchronously on sub-threads and have nothing to do with the main thread.
CoreText Completes Mixing of Graphics and Texts
The main purpose of this paper is to calculate NSAttributed String, which involves the following main api s:
- (1) CTFrameSetterRef
- (2) CTFrameRef
- (3) CTLineRef
- (4) CTRunRef
- (5) CTRunDelegate
We can cache the computed CTFrameRef instance for the same text. Note that CoreText's drawing functions can be done concurrently in sub-threads.
CoreGrapgics
It mainly completes the drawing of some custom images and paths. Note that it's best not to rewrite drawRect of UIView: to achieve rendering, specifically why Baidu.
After the final rendering, CGImageRef is rendered for CALayer direct display.
dispatch_async(dispatch_get_global_queue(0, 0), ^{ UIGraphicsBeginImageContextWithOptions(imageV.frame.size, NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); // Start drawing arbitrarily in context //............ UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); dispatch_async(dispatch_get_main_queue(), ^{ xxxLayer.contents = image.CGImage; }); UIGraphicsEndImageContext(); });
Loading optimization of network super-large and high-definition images
Scheme 1. Draw images of different specifications from the web several times for overlay display
- (1) Loading pictures of different sizes from the web in turn, from small to large
- (2) Draw a small thumbnail for stretching at first.
- (3) Then pull out the medium-sized drawings and cover the display directly after pulling out.
- (4) Draw the original picture at last, and display the original picture after the completion of the drawing.
Shortcomings, need to be divided into many network requests.
Scheme 2: Draw the original image directly from the web and load it step by step
- (1) Draw the original large and high-definition images directly from the web, which is sure to be incomplete at one time.
- (2) When a portion of the image data transmitted from the web is received, a portion of the image data is displayed.
- (3) A large, high-definition image file is loaded bit by bit.
Disadvantage: Only one request, but it takes a long time to load a complete image.
Method 3. Combination of scheme 1 and scheme 2
- (1) Draw a small thumbnail for stretching display (first request)
- (2) Then, scheme 2 is adopted to pull out the original large image and high definition image directly (second request), and load part of the image progressively.
This scheme combines the advantages of the previous two schemes, which not only reduces the number of requests of scheme 1, but also makes use of the progressive loading of image file data of scheme 2. For example:
The main steps of progressive image file data loading are as follows:
- (1) Create an empty progressive ImageSource >> CGImageSource Create Incremental (NULL);
- (2) Continuous stitching of image data NSData
- (3) Update progressive ImageSource using some of the currently available data NSData
- (4) CGImageRef is obtained from rendering in progressive ImageSource, which can be used for display.
- (5) After loading, release the discarded progressive ImageSource